diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35bbf0a657a..5f1199a3d8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -287,7 +287,7 @@ jobs: run: | cargo install --locked --debug --path ./forc-plugins/forc-doc - name: Build sway-lib-std docs - run: forc doc --manifest-path ./sway-lib-std + run: forc doc --path ./sway-lib-std build-forc-test-project: runs-on: buildjet-4vcpu-ubuntu-2204 @@ -470,6 +470,10 @@ jobs: run: cargo run --locked --release -p forc -- build --release --locked --path ./test/src/sdk-harness - name: Cargo Test sway-lib-std run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture + - name: Build All Tests - Experimental Feature 'storage_domains' + run: cargo run --locked --release -p forc -- build --experimental storage_domains --release --locked --path ./test/src/sdk-harness + - name: Cargo Test sway-lib-std - Experimental Feature 'storage_domains' + run: cargo test --locked --release --manifest-path ./test/src/sdk-harness/Cargo.toml -- --nocapture forc-run-benchmarks: runs-on: buildjet-4vcpu-ubuntu-2204 @@ -525,10 +529,16 @@ jobs: run: cargo install --locked --debug --path ./forc - name: Run Core Unit Tests run: forc build --path sway-lib-core && forc test --path sway-lib-core + - name: Run Core Unit Tests - Experimental feature 'storage_domains' + run: forc build --experimental storage_domains --path sway-lib-core && forc test --experimental storage_domains --path sway-lib-core - name: Run Std Unit Tests run: forc build --path sway-lib-std && forc test --path sway-lib-std + - name: Run Std Unit Tests - Experimental feature 'storage_domains' + run: forc build --experimental storage_domains --path sway-lib-std && forc test --experimental storage_domains --path sway-lib-std - name: Run In Language Unit Tests run: forc build --path test/src/in_language_tests && forc test --path test/src/in_language_tests + - name: Run In Language Unit Tests - Experimental feature 'storage_domains' + run: forc build --experimental storage_domains --path test/src/in_language_tests && forc test --experimental storage_domains --path test/src/in_language_tests forc-pkg-fuels-deps-check: runs-on: buildjet-4vcpu-ubuntu-2204 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index fa2ac16b866..7e3201451c5 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -50,7 +50,7 @@ jobs: run: mdbook build docs/reference - name: Build Sway std library - run: forc doc --manifest-path ./sway-lib-std + run: forc doc --path ./sway-lib-std - name: Deploy master std uses: peaceiris/actions-gh-pages@v3 diff --git a/Cargo.lock b/Cargo.lock index 159d6d5f8cd..d3c21b0d08b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd58d377699e6cfeab52c4a9d28bdc4ef37e2bd235ff2db525071fe37a2e9af5" +checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" dependencies = [ "alloy-rlp", "bytes", @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53dcf5e7d9bd1517b8b998e170e650047cea8a2b85fe1835abe3210713e541b7" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -965,9 +965,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5327f6c99920069d1fe374aa743be1af0031dea9f250852cdf1ae6a0861ee24" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases", @@ -975,9 +975,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10aedd8f1a81a8aafbfde924b0e3061cd6fedd6f6bbcfc6a76e6fd426d7bfe26" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", @@ -998,9 +998,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1113,9 +1113,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.37" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -1206,9 +1206,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -1216,9 +1216,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -1229,9 +1229,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11611dca53440593f38e6b25ec629de50b14cdfa63adc0fb856115a2c6d97595" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ "clap", ] @@ -1260,9 +1260,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clipboard-win" @@ -2640,9 +2640,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2662,7 +2662,7 @@ checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "forc" -version = "0.66.4" +version = "0.66.5" dependencies = [ "annotate-snippets", "ansiterm", @@ -2673,7 +2673,7 @@ dependencies = [ "completest-pty", "forc-pkg", "forc-test", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "fs_extra", "fuel-asm", @@ -2699,7 +2699,7 @@ dependencies = [ [[package]] name = "forc-client" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "async-trait", @@ -2711,7 +2711,7 @@ dependencies = [ "dialoguer", "forc", "forc-pkg", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-tx", "forc-util", "forc-wallet", @@ -2747,13 +2747,13 @@ dependencies = [ [[package]] name = "forc-crypto" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "async-trait", "clap", "criterion", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "fuel-core-types", "fuel-crypto", @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "forc-debug" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "clap", @@ -2785,7 +2785,7 @@ dependencies = [ "escargot", "forc-pkg", "forc-test", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "fuel-core-client", "fuel-types", "fuel-vm", @@ -2804,7 +2804,7 @@ dependencies = [ [[package]] name = "forc-doc" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "clap", @@ -2812,7 +2812,7 @@ dependencies = [ "dir_indexer", "expect-test", "forc-pkg", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "horrorshow", "include_dir", @@ -2830,12 +2830,12 @@ dependencies = [ [[package]] name = "forc-fmt" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "clap", "forc-pkg", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "prettydiff", "sway-core", @@ -2847,7 +2847,7 @@ dependencies = [ [[package]] name = "forc-lsp" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "clap", @@ -2858,13 +2858,13 @@ dependencies = [ [[package]] name = "forc-pkg" -version = "0.66.4" +version = "0.66.5" dependencies = [ "ansiterm", "anyhow", "byte-unit", "cid", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "fuel-abi-types", "futures", @@ -2896,7 +2896,7 @@ dependencies = [ [[package]] name = "forc-test" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "forc-pkg", @@ -2924,7 +2924,7 @@ dependencies = [ [[package]] name = "forc-tracing" -version = "0.66.4" +version = "0.66.5" dependencies = [ "ansiterm", "tracing", @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "forc-tx" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "clap", @@ -2949,7 +2949,7 @@ dependencies = [ [[package]] name = "forc-util" -version = "0.66.4" +version = "0.66.5" dependencies = [ "annotate-snippets", "ansiterm", @@ -2957,7 +2957,8 @@ dependencies = [ "clap", "dirs 5.0.1", "fd-lock", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", + "fuel-asm", "fuel-tx", "hex", "paste", @@ -2965,6 +2966,7 @@ dependencies = [ "serde", "serde_json", "serial_test", + "sha2 0.10.8", "sway-core", "sway-error", "sway-types", @@ -3796,9 +3798,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -4092,14 +4094,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -4148,9 +4150,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.16", + "rustls 0.23.17", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -4177,7 +4179,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -4196,7 +4198,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -4659,9 +4661,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "7a73e9fe3c49d7afb2ace819fa181a287ce54a0983eda4e0eb05c22f82ffe534" [[package]] name = "jobserver" @@ -4745,9 +4747,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libdbus-sys" @@ -5549,9 +5551,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] @@ -5626,28 +5628,29 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.87", ] [[package]] @@ -6166,11 +6169,11 @@ dependencies = [ [[package]] name = "publicsuffix" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "idna 0.3.0", + "idna 1.0.3", "psl-types", ] @@ -6468,11 +6471,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -6806,9 +6809,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -6831,9 +6834,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" dependencies = [ "once_cell", "rustls-pki-types", @@ -6974,18 +6977,18 @@ dependencies = [ [[package]] name = "scc" -version = "2.2.4" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d25269dd3a12467afe2e510f69fb0b46b698e5afb296b59f2145259deaf8e8" +checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -7141,9 +7144,9 @@ dependencies = [ [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] @@ -7179,9 +7182,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap 2.6.0", "itoa", @@ -7633,7 +7636,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sway-ast" -version = "0.66.4" +version = "0.66.5" dependencies = [ "extension-trait", "num-bigint", @@ -7645,7 +7648,7 @@ dependencies = [ [[package]] name = "sway-core" -version = "0.66.4" +version = "0.66.5" dependencies = [ "clap", "derivative", @@ -7691,7 +7694,7 @@ dependencies = [ [[package]] name = "sway-error" -version = "0.66.4" +version = "0.66.5" dependencies = [ "either", "in_definite", @@ -7704,7 +7707,7 @@ dependencies = [ [[package]] name = "sway-features" -version = "0.66.4" +version = "0.66.5" dependencies = [ "clap", "paste", @@ -7712,7 +7715,7 @@ dependencies = [ [[package]] name = "sway-ir" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "downcast-rs", @@ -7733,7 +7736,7 @@ dependencies = [ [[package]] name = "sway-ir-macros" -version = "0.66.4" +version = "0.66.5" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -7743,7 +7746,7 @@ dependencies = [ [[package]] name = "sway-lsp" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", "assert-json-diff", @@ -7754,7 +7757,7 @@ dependencies = [ "dirs 4.0.0", "fd-lock", "forc-pkg", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "forc-util", "futures", "indexmap 2.6.0", @@ -7794,7 +7797,7 @@ dependencies = [ [[package]] name = "sway-lsp-test-utils" -version = "0.66.4" +version = "0.66.5" dependencies = [ "assert-json-diff", "futures", @@ -7809,7 +7812,7 @@ dependencies = [ [[package]] name = "sway-parse" -version = "0.66.4" +version = "0.66.5" dependencies = [ "assert_matches", "extension-trait", @@ -7827,7 +7830,7 @@ dependencies = [ [[package]] name = "sway-types" -version = "0.66.4" +version = "0.66.5" dependencies = [ "bytecount", "fuel-asm", @@ -7846,7 +7849,7 @@ dependencies = [ [[package]] name = "sway-utils" -version = "0.66.4" +version = "0.66.5" dependencies = [ "serde", "walkdir", @@ -7854,10 +7857,10 @@ dependencies = [ [[package]] name = "swayfmt" -version = "0.66.4" +version = "0.66.5" dependencies = [ "anyhow", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "indoc", "paste", "prettydiff", @@ -7866,7 +7869,6 @@ dependencies = [ "serde_ignored", "similar", "sway-ast", - "sway-core", "sway-error", "sway-parse", "sway-types", @@ -8155,7 +8157,7 @@ dependencies = [ "forc-client", "forc-pkg", "forc-test", - "forc-tracing 0.66.4", + "forc-tracing 0.66.5", "fuel-vm", "futures", "gag", @@ -8425,7 +8427,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.16", + "rustls 0.23.17", "rustls-pki-types", "tokio", ] @@ -8749,9 +8751,9 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" diff --git a/Cargo.toml b/Cargo.toml index 31bc6386951..402b7679c42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ exclude = ["examples/*", "swayfmt/test_macros", "forc-test/test_data"] [workspace.package] edition = "2021" -version = "0.66.4" +version = "0.66.5" authors = ["Fuel Labs "] homepage = "https://fuel.network/" license = "Apache-2.0" @@ -42,35 +42,35 @@ repository = "https://github.com/FuelLabs/sway" # Internal dependencies in order to propagate `workspace.version` # -forc = { path = "forc/", version = "0.66.4" } -forc-pkg = { path = "forc-pkg/", version = "0.66.4" } -forc-test = { path = "forc-test/", version = "0.66.4" } -forc-tracing = { path = "forc-tracing/", version = "0.66.4" } -forc-util = { path = "forc-util/", version = "0.66.4" } +forc = { path = "forc/", version = "0.66.5" } +forc-pkg = { path = "forc-pkg/", version = "0.66.5" } +forc-test = { path = "forc-test/", version = "0.66.5" } +forc-tracing = { path = "forc-tracing/", version = "0.66.5" } +forc-util = { path = "forc-util/", version = "0.66.5" } # Forc plugins -forc-plugins = { path = "forc-plugins/", version = "0.66.4" } -forc-client = { path = "forc-plugins/forc-client/", version = "0.66.4" } -forc-crypto = { path = "forc-plugins/forc-crypto/", version = "0.66.4" } -forc-debug = { path = "forc-plugins/forc-debug/", version = "0.66.4" } -forc-doc = { path = "forc-plugins/forc-doc/", version = "0.66.4" } -forc-fmt = { path = "forc-plugins/forc-fmt/", version = "0.66.4" } -forc-lsp = { path = "forc-plugins/forc-lsp/", version = "0.66.4" } -forc-tx = { path = "forc-plugins/forc-tx/", version = "0.66.4" } +forc-plugins = { path = "forc-plugins/", version = "0.66.5" } +forc-client = { path = "forc-plugins/forc-client/", version = "0.66.5" } +forc-crypto = { path = "forc-plugins/forc-crypto/", version = "0.66.5" } +forc-debug = { path = "forc-plugins/forc-debug/", version = "0.66.5" } +forc-doc = { path = "forc-plugins/forc-doc/", version = "0.66.5" } +forc-fmt = { path = "forc-plugins/forc-fmt/", version = "0.66.5" } +forc-lsp = { path = "forc-plugins/forc-lsp/", version = "0.66.5" } +forc-tx = { path = "forc-plugins/forc-tx/", version = "0.66.5" } -sway-ast = { path = "sway-ast/", version = "0.66.4" } -sway-core = { path = "sway-core/", version = "0.66.4" } -sway-error = { path = "sway-error/", version = "0.66.4" } -sway-features = { path = "sway-features/", version = "0.66.4" } -sway-lsp = { path = "sway-lsp/", version = "0.66.4" } -sway-parse = { path = "sway-parse/", version = "0.66.4" } -sway-types = { path = "sway-types/", version = "0.66.4" } -sway-utils = { path = "sway-utils/", version = "0.66.4" } -swayfmt = { path = "swayfmt/", version = "0.66.4" } +sway-ast = { path = "sway-ast/", version = "0.66.5" } +sway-core = { path = "sway-core/", version = "0.66.5" } +sway-error = { path = "sway-error/", version = "0.66.5" } +sway-features = { path = "sway-features/", version = "0.66.5" } +sway-lsp = { path = "sway-lsp/", version = "0.66.5" } +sway-parse = { path = "sway-parse/", version = "0.66.5" } +sway-types = { path = "sway-types/", version = "0.66.5" } +sway-utils = { path = "sway-utils/", version = "0.66.5" } +swayfmt = { path = "swayfmt/", version = "0.66.5" } # Sway IR -sway-ir = { path = "sway-ir/", version = "0.66.4" } -sway-ir-macros = { path = "sway-ir/sway-ir-macros", version = "0.66.4" } +sway-ir = { path = "sway-ir/", version = "0.66.5" } +sway-ir-macros = { path = "sway-ir/sway-ir-macros", version = "0.66.5" } # # External Fuel dependencies diff --git a/docs/book/spell-check-custom-words.txt b/docs/book/spell-check-custom-words.txt index 6e18232eaf0..3b51147b9ee 100644 --- a/docs/book/spell-check-custom-words.txt +++ b/docs/book/spell-check-custom-words.txt @@ -227,4 +227,8 @@ predicate-root forc-client crypto doc -fmt \ No newline at end of file +fmt +deallocated +deallocate +destructors +destructor \ No newline at end of file diff --git a/docs/book/src/forc/manifest_reference.md b/docs/book/src/forc/manifest_reference.md index e9be681e5c8..0757f13e213 100644 --- a/docs/book/src/forc/manifest_reference.md +++ b/docs/book/src/forc/manifest_reference.md @@ -11,6 +11,7 @@ The `Forc.toml` (the _manifest_ file) is a compulsory file for each package and * For the recommended way of selecting an entry point of large libraries please take a look at: [Libraries](./../sway-program-types/libraries.md) * `implicit-std` - Controls whether provided `std` version (with the current `forc` version) will get added as a dependency _implicitly_. _Unless you know what you are doing, leave this as default._ * `forc-version` - The minimum forc version required for this project to work properly. + * `metadata` - Metadata for the project; can be used by tools which would like to store package configuration in `Forc.toml`. * [`[dependencies]`](#the-dependencies-section) — Defines the dependencies. * `[network]` — Defines a network for forc to interact with. @@ -41,8 +42,85 @@ entry = "main.sw" organization = "Fuel_Labs" license = "Apache-2.0" name = "wallet_contract" + +[project.metadata] +indexing = { namespace = "counter-contract", schema_path = "out/release/counter-contract-abi.json" } +``` + +### Metadata Section in `Forc.toml` + +The `[project.metadata]` section provides a dedicated space for external tools and plugins to store their configuration in `Forc.toml`. The metadata key names are arbitrary and do not need to match the tool's name. + +#### Workspace vs Project Metadata + +Metadata can be defined at two levels: + +Workspace level - defined in the workspace\'s root `Forc.toml`: + +```toml +[workspace.metadata] +my_tool = { shared_setting = "value" } +``` + +Project level - defined in individual project\'s `Forc.toml`: + +```toml +[project.metadata.any_name_here] +option1 = "value" +option2 = "value" + +[project.metadata.my_custom_config] +setting1 = "value" +setting2 = "value" +``` + +Example for an indexing tool: + +```toml +[project.metadata.indexing] +namespace = "counter-contract" +schema_path = "out/release/counter-contract-abi.json" ``` +When both workspace and project metadata exist: + +* Project-level metadata should take precedence over workspace metadata +* Tools can choose to merge workspace and project settings +* Consider documenting your tool's metadata inheritance behavior + +#### Guidelines for Plugin Developers + +Best Practices + +* Choose clear, descriptive metadata key names +* Document the exact metadata key name your tool expects +* Don't require `Forc.toml` if tool can function without it +* Consider using TOML format for dedicated config files +* Specify how your tool handles workspace vs project metadata + +Implementation Notes + +* The metadata section is optional +* Forc does not parse metadata contents +* Plugin developers handle their own configuration parsing +* Choose unique metadata keys to avoid conflicts with other tools + +#### Example Use Cases + +* Documentation generation settings +* Formatter configurations +* Debugger options +* Wallet integration +* Contract indexing +* Testing frameworks + +This allows for a streamlined developer experience while maintaining clear separation between core Forc functionality and third-party tools. + +#### External Tooling Examples + +* [forc-index-ts](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-ts): A TypeScript CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file. +* [forc-index-rs](https://github.com/FuelLabs/example-forc-plugins/tree/master/forc-index-rs): A Rust CLI tool for parsing `Forc.toml` metadata to read contract ABI JSON file. + ## The `[dependencies]` section The following fields can be provided with a dependency: diff --git a/docs/book/src/reference/rust_differences.md b/docs/book/src/reference/rust_differences.md index c9505814b20..abd7d7b736d 100644 --- a/docs/book/src/reference/rust_differences.md +++ b/docs/book/src/reference/rust_differences.md @@ -34,3 +34,9 @@ struct MyStruct { field_two: bool, } ``` + +## Memory Allocation + +In Rust, the borrow checker implements Rust's [ownership system](https://doc.rust-lang.org/1.8.0/book/ownership.html) + +In Sway, there is no borrow checker. This means there is no concept of ownership, borrowing, or lifetimes. Instead, objects are copied and moved similar to C++. Also Sway does not have any destructors nor `Drop` traits. This means allocated memory lives for the entire transaction and is not deallocated until the end of the transaction. A transaction may allocate up to [64 MB](https://github.com/FuelLabs/fuel-vm/blob/a80f82ed7c793763de6a73ca72d946b311b0fd0b/fuel-vm/src/consts.rs#L26) of memory. diff --git a/docs/reference/src/documentation/language/control-flow/match/single-line.md b/docs/reference/src/documentation/language/control-flow/match/single-line.md index 376b226b387..04b308c883f 100644 --- a/docs/reference/src/documentation/language/control-flow/match/single-line.md +++ b/docs/reference/src/documentation/language/control-flow/match/single-line.md @@ -10,6 +10,6 @@ The left side of the arrow `=>` is the pattern that we are matching on and the r We check each arm starting from `0` and make our way down until we either find a match on our pattern or we reach the `catch_all` case. -The `|` operator can be used to produce a pattern that is a disjuction of other patterns. +The `|` operator can be used to produce a pattern that is a disjunction of other patterns. The `catch_all` case is equivalent to an `else` in [if expressions](../if-expressions.md) and it does not have to be called `catch_all`. Any pattern declared after a `catch_all` case will not be matched because once the compiler sees the first `catch_all` it stop performing further checks. diff --git a/docs/reference/src/documentation/operations/namespace/index.md b/docs/reference/src/documentation/operations/namespace/index.md index 171f78629a2..773caebffa0 100644 --- a/docs/reference/src/documentation/operations/namespace/index.md +++ b/docs/reference/src/documentation/operations/namespace/index.md @@ -1,6 +1,6 @@ # Address Namespace -Sway utilizies namespaces to distinguish between address types. +Sway utilizes namespaces to distinguish between address types. Having multiple address types enforces type-safety and expands the range of values that an address can take because the same value can be used across multiple types. diff --git a/examples/advanced_storage_variables/src/main.sw b/examples/advanced_storage_variables/src/main.sw index 75054ab7149..c1afb9f4790 100644 --- a/examples/advanced_storage_variables/src/main.sw +++ b/examples/advanced_storage_variables/src/main.sw @@ -1,6 +1,6 @@ contract; -use std::{bytes::Bytes, string::String,}; +use std::{bytes::Bytes, string::String}; // ANCHOR: temp_hash_import use std::hash::Hash; diff --git a/examples/converting_types/src/byte_arrays.sw b/examples/converting_types/src/byte_arrays.sw index d98931c163f..41c1ef543ba 100644 --- a/examples/converting_types/src/byte_arrays.sw +++ b/examples/converting_types/src/byte_arrays.sw @@ -1,7 +1,7 @@ library; // ANCHOR: to_byte_array_import -use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*,}; +use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}; // ANCHOR_END: to_byte_array_import pub fn to_byte_array() { diff --git a/examples/converting_types/src/bytes.sw b/examples/converting_types/src/bytes.sw index 5460352b10d..3537eca8a51 100644 --- a/examples/converting_types/src/bytes.sw +++ b/examples/converting_types/src/bytes.sw @@ -1,7 +1,7 @@ library; // ANCHOR: to_bytes_import -use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*,}}; +use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}}; // ANCHOR_END: to_bytes_import pub fn convert_to_bytes() { diff --git a/examples/wallet_smart_contract/src/main.sw b/examples/wallet_smart_contract/src/main.sw index 9343e818bdf..5ab406a6f3c 100644 --- a/examples/wallet_smart_contract/src/main.sw +++ b/examples/wallet_smart_contract/src/main.sw @@ -1,7 +1,7 @@ // ANCHOR: full_wallet contract; -use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount,}; +use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount}; // ANCHOR: abi_import use wallet_abi::Wallet; diff --git a/forc-pkg/src/manifest/mod.rs b/forc-pkg/src/manifest/mod.rs index 5ce50d65804..c20effec0fb 100644 --- a/forc-pkg/src/manifest/mod.rs +++ b/forc-pkg/src/manifest/mod.rs @@ -166,7 +166,7 @@ impl TryInto for ManifestFile { type PatchMap = BTreeMap; /// A [PackageManifest] that was deserialized from a file at a particular path. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq)] pub struct PackageManifestFile { /// The deserialized `Forc.toml`. manifest: PackageManifest, @@ -175,7 +175,7 @@ pub struct PackageManifestFile { } /// A direct mapping to a `Forc.toml`. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct PackageManifest { pub project: Project, @@ -189,7 +189,7 @@ pub struct PackageManifest { pub proxy: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct Project { pub authors: Option>, @@ -202,6 +202,7 @@ pub struct Project { pub forc_version: Option, #[serde(default)] pub experimental: HashMap, + pub metadata: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -850,6 +851,7 @@ pub struct WorkspaceManifest { #[serde(rename_all = "kebab-case")] pub struct Workspace { pub members: Vec, + pub metadata: Option, } impl WorkspaceManifestFile { @@ -1330,4 +1332,287 @@ mod tests { assert!(dependency_details_git_rev.validate().is_ok()); assert!(dependency_details_ipfs.validate().is_ok()); } + + #[test] + fn test_project_with_null_metadata() { + let project = Project { + authors: Some(vec!["Test Author".to_string()]), + name: "test-project".to_string(), + organization: None, + license: "Apache-2.0".to_string(), + entry: "main.sw".to_string(), + implicit_std: None, + forc_version: None, + experimental: HashMap::new(), + metadata: Some(toml::Value::from(toml::value::Table::new())), + }; + + let serialized = toml::to_string(&project).unwrap(); + let deserialized: Project = toml::from_str(&serialized).unwrap(); + + assert_eq!(project.name, deserialized.name); + assert_eq!(project.metadata, deserialized.metadata); + } + + #[test] + fn test_project_without_metadata() { + let project = Project { + authors: Some(vec!["Test Author".to_string()]), + name: "test-project".to_string(), + organization: None, + license: "Apache-2.0".to_string(), + entry: "main.sw".to_string(), + implicit_std: None, + forc_version: None, + experimental: HashMap::new(), + metadata: None, + }; + + let serialized = toml::to_string(&project).unwrap(); + let deserialized: Project = toml::from_str(&serialized).unwrap(); + + assert_eq!(project.name, deserialized.name); + assert_eq!(project.metadata, deserialized.metadata); + assert_eq!(project.metadata, None); + } + + #[test] + fn test_project_metadata_from_toml() { + let toml_str = r#" + name = "test-project" + license = "Apache-2.0" + entry = "main.sw" + authors = ["Test Author"] + + [metadata] + description = "A test project" + version = "1.0.0" + homepage = "https://example.com" + documentation = "https://docs.example.com" + repository = "https://github.com/example/test-project" + keywords = ["test", "project"] + categories = ["test"] + "#; + + let project: Project = toml::from_str(toml_str).unwrap(); + assert!(project.metadata.is_some()); + + let metadata = project.metadata.unwrap(); + let table = metadata.as_table().unwrap(); + + assert_eq!( + table.get("description").unwrap().as_str().unwrap(), + "A test project" + ); + assert_eq!(table.get("version").unwrap().as_str().unwrap(), "1.0.0"); + assert_eq!( + table.get("homepage").unwrap().as_str().unwrap(), + "https://example.com" + ); + + let keywords = table.get("keywords").unwrap().as_array().unwrap(); + assert_eq!(keywords[0].as_str().unwrap(), "test"); + assert_eq!(keywords[1].as_str().unwrap(), "project"); + } + + #[test] + fn test_project_with_invalid_metadata() { + // Test with invalid TOML syntax - unclosed table + let invalid_toml = r#" + name = "test-project" + license = "Apache-2.0" + entry = "main.sw" + + [metadata + description = "Invalid TOML" + "#; + + let result: Result = toml::from_str(invalid_toml); + assert!(result.is_err()); + + // Test with invalid TOML syntax - invalid key + let invalid_toml = r#" + name = "test-project" + license = "Apache-2.0" + entry = "main.sw" + + [metadata] + ] = "Invalid key" + "#; + + let result: Result = toml::from_str(invalid_toml); + assert!(result.is_err()); + + // Test with duplicate keys + let invalid_toml = r#" + name = "test-project" + license = "Apache-2.0" + entry = "main.sw" + + [metadata] + nested = { key = "value1" } + + [metadata.nested] + key = "value2" + "#; + + let result: Result = toml::from_str(invalid_toml); + assert!(result.is_err()); + assert!(result + .err() + .unwrap() + .to_string() + .contains("duplicate key `nested` in table `metadata`")); + } + + #[test] + fn test_metadata_roundtrip() { + let original_toml = r#" + name = "test-project" + license = "Apache-2.0" + entry = "main.sw" + + [metadata] + boolean = true + integer = 42 + float = 3.12 + string = "value" + array = [1, 2, 3] + mixed_array = [1, "two", true] + + [metadata.nested] + key = "value2" + "#; + + let project: Project = toml::from_str(original_toml).unwrap(); + let serialized = toml::to_string(&project).unwrap(); + let deserialized: Project = toml::from_str(&serialized).unwrap(); + + // Verify that the metadata is preserved + assert_eq!(project.metadata, deserialized.metadata); + + // Verify all types were preserved + let table_val = project.metadata.unwrap(); + let table = table_val.as_table().unwrap(); + assert!(table.get("boolean").unwrap().as_bool().unwrap()); + assert_eq!(table.get("integer").unwrap().as_integer().unwrap(), 42); + assert_eq!(table.get("float").unwrap().as_float().unwrap(), 3.12); + assert_eq!(table.get("string").unwrap().as_str().unwrap(), "value"); + assert_eq!(table.get("array").unwrap().as_array().unwrap().len(), 3); + assert!(table.get("nested").unwrap().as_table().is_some()); + } + + #[test] + fn test_workspace_with_metadata() { + let toml_str = r#" + [workspace] + members = ["package1", "package2"] + + [workspace.metadata] + description = "A test workspace" + version = "1.0.0" + authors = ["Test Author"] + homepage = "https://example.com" + + [workspace.metadata.ci] + workflow = "main" + timeout = 3600 + "#; + + let manifest: WorkspaceManifest = toml::from_str(toml_str).unwrap(); + assert!(manifest.workspace.metadata.is_some()); + + let metadata = manifest.workspace.metadata.unwrap(); + let table = metadata.as_table().unwrap(); + + assert_eq!( + table.get("description").unwrap().as_str().unwrap(), + "A test workspace" + ); + assert_eq!(table.get("version").unwrap().as_str().unwrap(), "1.0.0"); + + let ci = table.get("ci").unwrap().as_table().unwrap(); + assert_eq!(ci.get("workflow").unwrap().as_str().unwrap(), "main"); + assert_eq!(ci.get("timeout").unwrap().as_integer().unwrap(), 3600); + } + + #[test] + fn test_workspace_without_metadata() { + let toml_str = r#" + [workspace] + members = ["package1", "package2"] + "#; + + let manifest: WorkspaceManifest = toml::from_str(toml_str).unwrap(); + assert!(manifest.workspace.metadata.is_none()); + } + + #[test] + fn test_workspace_empty_metadata() { + let toml_str = r#" + [workspace] + members = ["package1", "package2"] + + [workspace.metadata] + "#; + + let manifest: WorkspaceManifest = toml::from_str(toml_str).unwrap(); + assert!(manifest.workspace.metadata.is_some()); + let metadata = manifest.workspace.metadata.unwrap(); + assert!(metadata.as_table().unwrap().is_empty()); + } + + #[test] + fn test_workspace_complex_metadata() { + let toml_str = r#" + [workspace] + members = ["package1", "package2"] + + [workspace.metadata] + numbers = [1, 2, 3] + strings = ["a", "b", "c"] + mixed = [1, "two", true] + + [workspace.metadata.nested] + key = "value" + + [workspace.metadata.nested.deep] + another = "value" + "#; + + let manifest: WorkspaceManifest = toml::from_str(toml_str).unwrap(); + let metadata = manifest.workspace.metadata.unwrap(); + let table = metadata.as_table().unwrap(); + + assert!(table.get("numbers").unwrap().as_array().is_some()); + assert!(table.get("strings").unwrap().as_array().is_some()); + assert!(table.get("mixed").unwrap().as_array().is_some()); + + let nested = table.get("nested").unwrap().as_table().unwrap(); + assert_eq!(nested.get("key").unwrap().as_str().unwrap(), "value"); + + let deep = nested.get("deep").unwrap().as_table().unwrap(); + assert_eq!(deep.get("another").unwrap().as_str().unwrap(), "value"); + } + + #[test] + fn test_workspace_metadata_roundtrip() { + let original = WorkspaceManifest { + workspace: Workspace { + members: vec![PathBuf::from("package1"), PathBuf::from("package2")], + metadata: Some(toml::Value::Table({ + let mut table = toml::value::Table::new(); + table.insert("key".to_string(), toml::Value::String("value".to_string())); + table + })), + }, + patch: None, + }; + + let serialized = toml::to_string(&original).unwrap(); + let deserialized: WorkspaceManifest = toml::from_str(&serialized).unwrap(); + + assert_eq!(original.workspace.members, deserialized.workspace.members); + assert_eq!(original.workspace.metadata, deserialized.workspace.metadata); + } } diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index ab4733949b8..49829a2a321 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -46,7 +46,7 @@ use sway_core::{ transform::AttributeKind, write_dwarf, BuildTarget, Engines, FinalizedEntry, LspConfig, }; -use sway_core::{PrintAsm, PrintIr}; +use sway_core::{set_bytecode_configurables_offset, PrintAsm, PrintIr}; use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning}; use sway_features::ExperimentalFeatures; use sway_types::constants::{CORE, PRELUDE, STD}; @@ -1928,7 +1928,7 @@ pub fn compile( let errored = handler.has_errors() || (handler.has_warnings() && profile.error_on_warnings); - let compiled = match bc_res { + let mut compiled = match bc_res { Ok(compiled) if !errored => compiled, _ => return fail(handler), }; @@ -1937,9 +1937,12 @@ pub fn compile( print_warnings(engines.se(), terse_mode, &pkg.name, &warnings, &tree_type); + // Metadata to be placed into the binary. + let mut md = [0u8, 0, 0, 0, 0, 0, 0, 0]; // TODO: This should probably be in `fuel_abi_json::generate_json_abi_program`? // If ABI requires knowing config offsets, they should be inputs to ABI gen. if let ProgramABI::Fuel(ref mut program_abi) = program_abi { + let mut configurables_offset = compiled.bytecode.len() as u64; if let Some(ref mut configurables) = program_abi.configurables { // Filter out all dead configurables (i.e. ones without offsets in the bytecode) configurables.retain(|c| { @@ -1948,12 +1951,22 @@ pub fn compile( .contains_key(&c.name) }); // Set the actual offsets in the JSON object - for (config, offset) in compiled.named_data_section_entries_offsets { - if let Some(idx) = configurables.iter().position(|c| c.name == config) { - configurables[idx].offset = offset; + for (config, offset) in &compiled.named_data_section_entries_offsets { + if *offset < configurables_offset { + configurables_offset = *offset; + } + if let Some(idx) = configurables.iter().position(|c| &c.name == config) { + configurables[idx].offset = *offset; } } } + + md = configurables_offset.to_be_bytes(); + } + + // We know to set the metadata only for fuelvm right now. + if let BuildTarget::Fuel = pkg.target { + set_bytecode_configurables_offset(&mut compiled, &md); } metrics.bytecode_size = compiled.bytecode.len(); @@ -2034,11 +2047,10 @@ fn report_assembly_information( used: compiled_asm .0 .data_section - .value_pairs - .iter() - .map(calculate_entry_size) + .iter_all_entries() + .map(|entry| calculate_entry_size(&entry)) .sum(), - value_pairs: compiled_asm.0.data_section.value_pairs.clone(), + value_pairs: compiled_asm.0.data_section.iter_all_entries().collect(), }, }; diff --git a/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json b/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json index 81b65c43cd7..c51c80ae2e9 100644 --- a/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json +++ b/forc-plugins/forc-client/test/data/deployed_script/deployed_script-loader-abi.json @@ -251,62 +251,62 @@ { "name": "BOOL", "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", - "offset": 136 + "offset": 240 }, { "name": "U8", "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", - "offset": 248 + "offset": 352 }, { "name": "U16", "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", - "offset": 192 + "offset": 296 }, { "name": "U32", "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", - "offset": 232 + "offset": 336 }, { "name": "U64", "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0", - "offset": 240 + "offset": 344 }, { "name": "U256", "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", - "offset": 200 + "offset": 304 }, { "name": "B256", "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", - "offset": 104 + "offset": 208 }, { "name": "STR_4", "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", - "offset": 176 + "offset": 280 }, { "name": "TUPLE", "concreteTypeId": "e0128f7be9902d1fe16326cafe703b52038064a7997b03ebfc1c9dd607e1536c", - "offset": 184 + "offset": 288 }, { "name": "ARRAY", "concreteTypeId": "d9fac01ab38fe10950758ae9604da330d6406a71fda3ef1ea818121261132d56", - "offset": 88 + "offset": 192 }, { "name": "STRUCT", "concreteTypeId": "563310524b4f4447a10d0e50556310253dfb3b5eb4b29c3773222b737c8b7075", - "offset": 160 + "offset": 264 }, { "name": "ENUM", "concreteTypeId": "37cd1cba311039a851ac8bfa614cc41359b4ad95c8656fcef2e8f504fe7a1272", - "offset": 144 + "offset": 248 } ] } \ No newline at end of file diff --git a/forc-plugins/forc-client/tests/deploy.rs b/forc-plugins/forc-client/tests/deploy.rs index b06d6aa96ed..7f881bb15d9 100644 --- a/forc-plugins/forc-client/tests/deploy.rs +++ b/forc-plugins/forc-client/tests/deploy.rs @@ -373,7 +373,7 @@ async fn test_simple_deploy() { node.kill().unwrap(); let expected = vec![DeployedPackage::Contract(DeployedContract { id: ContractId::from_str( - "5a4d19b92e784817f3e4f7d7c9961c5cba3069e53dcee2a93e8cd723e555c5f3", + "02a7e78ef0514b80ab56b409f06f895d8939640b6f6d746fcbb15d3e0c6a1a3b", ) .unwrap(), proxy: None, @@ -416,7 +416,7 @@ async fn test_deploy_submit_only() { node.kill().unwrap(); let expected = vec![DeployedPackage::Contract(DeployedContract { id: ContractId::from_str( - "5a4d19b92e784817f3e4f7d7c9961c5cba3069e53dcee2a93e8cd723e555c5f3", + "02a7e78ef0514b80ab56b409f06f895d8939640b6f6d746fcbb15d3e0c6a1a3b", ) .unwrap(), proxy: None, @@ -462,12 +462,12 @@ async fn test_deploy_fresh_proxy() { node.kill().unwrap(); let impl_contract = DeployedPackage::Contract(DeployedContract { id: ContractId::from_str( - "5a4d19b92e784817f3e4f7d7c9961c5cba3069e53dcee2a93e8cd723e555c5f3", + "02a7e78ef0514b80ab56b409f06f895d8939640b6f6d746fcbb15d3e0c6a1a3b", ) .unwrap(), proxy: Some( ContractId::from_str( - "f0641246d72044059de56248becf345bd8553c7892df8c12d7df23f461a7f95b", + "6eb0db0e120222a4ac3ced8dfbf15ae56753b852aa7989849fa20e5aca47af44", ) .unwrap(), ), @@ -1303,7 +1303,7 @@ async fn offset_shifted_abi_works() { let loader_with_configs_from_sdk = call_with_sdk_generated_overrides(&node_url, contract_id).await; - // Genearating the forc-deploy loader bytecode and loader abi. + // Generating the forc-deploy loader bytecode and loader abi. let loader_with_configs_from_forc = call_with_forc_generated_overrides(&node_url, contract_id).await; pretty_assertions::assert_eq!(loader_with_configs_from_forc, loader_with_configs_from_sdk); diff --git a/forc-plugins/forc-debug/src/cli.rs b/forc-plugins/forc-debug/src/cli.rs new file mode 100644 index 00000000000..6705c5b8b8f --- /dev/null +++ b/forc-plugins/forc-debug/src/cli.rs @@ -0,0 +1,332 @@ +use crate::{ + error::{ArgumentError, Error, Result}, + names::{register_index, register_name}, + ContractId, FuelClient, RunResult, Transaction, +}; +use fuel_vm::consts::{VM_MAX_RAM, VM_REGISTER_COUNT, WORD_SIZE}; +use shellfish::{handler::DefaultAsyncHandler, input_handler::IO, Command as ShCommand, Shell}; + +pub struct State { + client: FuelClient, + session_id: String, +} + +/// Start the CLI debug interface +pub async fn start_cli(api_url: &str) -> Result<()> { + let mut shell = Shell::new_async( + State { + client: FuelClient::new(api_url).map_err(|e| Error::FuelClientError(e.to_string()))?, + session_id: String::new(), // Placeholder + }, + ">> ", + ); + + register_commands(&mut shell); + + let session_id = shell + .state + .client + .start_session() + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + shell.state.session_id.clone_from(&session_id); + + shell + .run_async() + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + shell + .state + .client + .end_session(&session_id) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + Ok(()) +} + +fn register_commands(shell: &mut Shell<'_, State, &str, DefaultAsyncHandler, IO>) { + // Registers an async command by wrapping the handler function `$f`, + // converting its error type into `Box`, and + // associating it with the provided command names. + macro_rules! command { + ($f:ident, $help:literal, $names:expr) => { + for c in $names { + shell.commands.insert( + c, + ShCommand::new_async($help.to_string(), |state, args| { + Box::pin(async move { + $f(state, args) + .await + .map_err(|e| Box::new(e) as Box) + }) + }), + ); + } + }; + } + + command!( + cmd_start_tx, + "path/to/tx.json -- start a new transaction", + ["n", "tx", "new_tx", "start_tx"] + ); + command!( + cmd_reset, + "-- reset, removing breakpoints and other state", + ["reset"] + ); + command!( + cmd_continue, + "-- run until next breakpoint or termination", + ["c", "continue"] + ); + command!( + cmd_step, + "[on|off] -- turn single-stepping on or off", + ["s", "step"] + ); + command!( + cmd_breakpoint, + "[contract_id] offset -- set a breakpoint", + ["b", "breakpoint"] + ); + command!( + cmd_registers, + "[regname ...] -- dump registers", + ["r", "reg", "register", "registers"] + ); + command!(cmd_memory, "[offset] limit -- dump memory", ["m", "memory"]); +} + +async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + ArgumentError::ensure_arg_count(&args, 1, 1)?; // Ensure exactly one argument + + let path_to_tx_json = args.pop().unwrap(); // Safe due to arg count check + + // Read and parse the transaction JSON + let tx_json = std::fs::read(&path_to_tx_json).map_err(Error::IoError)?; + let tx: Transaction = serde_json::from_slice(&tx_json).map_err(Error::JsonError)?; + + // Start the transaction + let status = state + .client + .start_tx(&state.session_id, &tx) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + pretty_print_run_result(&status); + Ok(()) +} + +async fn cmd_reset(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + ArgumentError::ensure_arg_count(&args, 0, 0)?; // Ensure no extra arguments + + // Reset the session + state + .client + .reset(&state.session_id) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + Ok(()) +} + +async fn cmd_continue(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + ArgumentError::ensure_arg_count(&args, 0, 0)?; // Ensure no extra arguments + + // Continue the transaction + let status = state + .client + .continue_tx(&state.session_id) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + pretty_print_run_result(&status); + Ok(()) +} + +async fn cmd_step(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + ArgumentError::ensure_arg_count(&args, 0, 1)?; // Ensure the argument count is at most 1 + + // Determine whether to enable or disable single stepping + let enable = args + .first() + .map_or(true, |v| !["off", "no", "disable"].contains(&v.as_str())); + + // Call the client + state + .client + .set_single_stepping(&state.session_id, enable) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + Ok(()) +} + +async fn cmd_breakpoint(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove command name + ArgumentError::ensure_arg_count(&args, 1, 2)?; + + let offset_str = args.pop().unwrap(); // Safe due to arg count check + let offset = parse_int(&offset_str).ok_or(ArgumentError::InvalidNumber(offset_str))?; + + let contract = if let Some(contract_id) = args.pop() { + contract_id + .parse::() + .map_err(|_| ArgumentError::Invalid(format!("Invalid contract ID: {}", contract_id)))? + } else { + ContractId::zeroed() + }; + + // Call client + state + .client + .set_breakpoint(&state.session_id, contract, offset as u64) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + Ok(()) +} + +async fn cmd_registers(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + + if args.is_empty() { + // Print all registers + for r in 0..VM_REGISTER_COUNT { + let value = state + .client + .register(&state.session_id, r as u32) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + println!("reg[{:#x}] = {:<8} # {}", r, value, register_name(r)); + } + } else { + // Process specific registers provided as arguments + for arg in &args { + if let Some(v) = parse_int(arg) { + if v < VM_REGISTER_COUNT { + let value = state + .client + .register(&state.session_id, v as u32) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + println!("reg[{:#02x}] = {:<8} # {}", v, value, register_name(v)); + } else { + return Err(ArgumentError::InvalidNumber(format!( + "Register index too large: {v}" + )) + .into()); + } + } else if let Some(index) = register_index(arg) { + let value = state + .client + .register(&state.session_id, index as u32) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + println!("reg[{index:#02x}] = {value:<8} # {arg}"); + } else { + return Err(ArgumentError::Invalid(format!("Unknown register name: {arg}")).into()); + } + } + } + Ok(()) +} + +async fn cmd_memory(state: &mut State, mut args: Vec) -> Result<()> { + args.remove(0); // Remove the command name + + // Parse limit argument or use the default + let limit = args + .pop() + .map(|a| parse_int(&a).ok_or(ArgumentError::InvalidNumber(a))) + .transpose()? + .unwrap_or(WORD_SIZE * (VM_MAX_RAM as usize)); + + // Parse offset argument or use the default + let offset = args + .pop() + .map(|a| parse_int(&a).ok_or(ArgumentError::InvalidNumber(a))) + .transpose()? + .unwrap_or(0); + + // Ensure no extra arguments + ArgumentError::ensure_arg_count(&args, 0, 0)?; + + // Fetch memory from the client + let mem = state + .client + .memory(&state.session_id, offset as u32, limit as u32) + .await + .map_err(|e| Error::FuelClientError(e.to_string()))?; + + // Print memory contents + for (i, chunk) in mem.chunks(WORD_SIZE).enumerate() { + print!(" {:06x}:", offset + i * WORD_SIZE); + for byte in chunk { + print!(" {byte:02x}"); + } + println!(); + } + Ok(()) +} + +/// Pretty-prints the result of a run, including receipts and breakpoint information. +/// +/// Outputs each receipt in the `RunResult` and details about the breakpoint if present. +/// If the execution terminated without hitting a breakpoint, it prints "Terminated". +fn pretty_print_run_result(rr: &RunResult) { + for receipt in rr.receipts() { + println!("Receipt: {receipt:?}"); + } + if let Some(bp) = &rr.breakpoint { + println!( + "Stopped on breakpoint at address {} of contract {}", + bp.pc.0, bp.contract + ); + } else { + println!("Terminated"); + } +} + +/// Parses a string representing a number and returns it as a `usize`. +/// +/// The input string can be in decimal or hexadecimal format: +/// - Decimal numbers are parsed normally (e.g., `"123"`). +/// - Hexadecimal numbers must be prefixed with `"0x"` (e.g., `"0x7B"`). +/// - Underscores can be used as visual separators (e.g., `"1_000"` or `"0x1_F4"`). +/// +/// If the input string is not a valid number in the specified format, `None` is returned. +/// +/// # Examples +/// +/// ``` +/// use forc_debug::cli::parse_int; +/// /// Use underscores as separators in decimal and hexadecimal numbers +/// assert_eq!(parse_int("123"), Some(123)); +/// assert_eq!(parse_int("1_000"), Some(1000)); +/// +/// /// Parse hexadecimal numbers with "0x" prefix +/// assert_eq!(parse_int("0x7B"), Some(123)); +/// assert_eq!(parse_int("0x1_F4"), Some(500)); +/// +/// /// Handle invalid inputs gracefully +/// assert_eq!(parse_int("abc"), None); +/// assert_eq!(parse_int("0xZZZ"), None); +/// assert_eq!(parse_int(""), None); +/// ``` +/// +/// # Errors +/// +/// Returns `None` if the input string contains invalid characters, +/// is not properly formatted, or cannot be parsed into a `usize`. +pub fn parse_int(s: &str) -> Option { + let (s, radix) = s.strip_prefix("0x").map_or((s, 10), |s| (s, 16)); + usize::from_str_radix(&s.replace('_', ""), radix).ok() +} diff --git a/forc-plugins/forc-debug/src/error.rs b/forc-plugins/forc-debug/src/error.rs new file mode 100644 index 00000000000..8f9f76fe114 --- /dev/null +++ b/forc-plugins/forc-debug/src/error.rs @@ -0,0 +1,106 @@ +use crate::types::Instruction; +use dap::requests::Command; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + ArgumentError(#[from] ArgumentError), + + #[error(transparent)] + AdapterError(#[from] AdapterError), + + #[error("VM error: {0}")] + VMError(String), + + #[error("Fuel Client error: {0}")] + FuelClientError(String), + + #[error("Session error: {0}")] + SessionError(String), + + #[error("I/O error")] + IoError(std::io::Error), + + #[error("Json error")] + JsonError(#[from] serde_json::Error), + + #[error("Server error: {0}")] + DapServerError(#[from] dap::errors::ServerError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ArgumentError { + #[error("Invalid argument: {0}")] + Invalid(String), + + #[error("Not enough arguments, expected {expected} but got {got}")] + NotEnough { expected: usize, got: usize }, + + #[error("Too many arguments, expected {expected} but got {got}")] + TooMany { expected: usize, got: usize }, + + #[error("Invalid number format: {0}")] + InvalidNumber(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum AdapterError { + #[error("Unhandled command")] + UnhandledCommand { command: Command }, + + #[error("Missing command")] + MissingCommand, + + #[error("Missing configuration")] + MissingConfiguration, + + #[error("Missing source path argument")] + MissingSourcePathArgument, + + #[error("Missing breakpoint location")] + MissingBreakpointLocation, + + #[error("Missing source map")] + MissingSourceMap { pc: Instruction }, + + #[error("Unknown breakpoint")] + UnknownBreakpoint { pc: Instruction }, + + #[error("Build failed")] + BuildFailed { reason: String }, + + #[error("No active test executor")] + NoActiveTestExecutor, + + #[error("Test execution failed")] + TestExecutionFailed { + #[from] + source: anyhow::Error, + }, +} + +impl ArgumentError { + /// Ensures argument count falls within [min, max] range. + pub fn ensure_arg_count( + args: &[String], + min: usize, + max: usize, + ) -> std::result::Result<(), ArgumentError> { + let count = args.len(); + if count < min { + Err(ArgumentError::NotEnough { + expected: min, + got: count, + }) + } else if count > max { + Err(ArgumentError::TooMany { + expected: max, + got: count, + }) + } else { + Ok(()) + } + } +} diff --git a/forc-plugins/forc-debug/src/lib.rs b/forc-plugins/forc-debug/src/lib.rs index e611ae3052c..a98036d815e 100644 --- a/forc-plugins/forc-debug/src/lib.rs +++ b/forc-plugins/forc-debug/src/lib.rs @@ -1,3 +1,5 @@ +pub mod cli; +pub mod error; pub mod names; pub mod server; pub mod types; diff --git a/forc-plugins/forc-debug/src/main.rs b/forc-plugins/forc-debug/src/main.rs index 7bf6a13b81a..da60d35a70b 100644 --- a/forc-plugins/forc-debug/src/main.rs +++ b/forc-plugins/forc-debug/src/main.rs @@ -1,13 +1,5 @@ use clap::Parser; -use forc_debug::{ - names::{register_index, register_name}, - server::DapServer, - ContractId, FuelClient, RunResult, Transaction, -}; -use forc_tracing::{init_tracing_subscriber, println_error}; -use fuel_vm::consts::{VM_MAX_RAM, VM_REGISTER_COUNT, WORD_SIZE}; -use shellfish::{async_fn, Command as ShCommand, Shell}; -use std::error::Error; +use forc_tracing::{init_tracing_subscriber, println_error, TracingSubscriberOptions}; #[derive(Parser, Debug)] #[clap(name = "forc-debug", version)] @@ -23,273 +15,17 @@ pub struct Opt { #[tokio::main] async fn main() { - init_tracing_subscriber(Default::default()); + init_tracing_subscriber(TracingSubscriberOptions::default()); let config = Opt::parse(); - if let Err(err) = run(&config).await { - println_error(&format!("{}", err)); - std::process::exit(1); - } -} - -async fn run(config: &Opt) -> Result<(), Box> { - if config.serve { - return DapServer::default().start(); - } - - let mut shell = Shell::new_async( - State { - client: FuelClient::new(&config.api_url)?, - session_id: String::new(), // Placeholder - }, - ">> ", - ); - - macro_rules! command { - ($f:ident, $help:literal, $names:expr) => { - for c in $names { - shell.commands.insert( - c, - ShCommand::new_async($help.to_string(), async_fn!(State, $f)), - ); - } - }; - } - - command!( - cmd_start_tx, - "path/to/tx.json -- start a new transaction", - ["n", "tx", "new_tx", "start_tx"] - ); - command!( - cmd_reset, - "-- reset, removing breakpoints and other state", - ["reset"] - ); - command!( - cmd_continue, - "-- run until next breakpoint or termination", - ["c", "continue"] - ); - command!( - cmd_step, - "[on|off] -- turn single-stepping on or off", - ["s", "step"] - ); - command!( - cmd_breakpoint, - "[contract_id] offset -- set a breakpoint", - ["b", "breakpoint"] - ); - command!( - cmd_registers, - "[regname ...] -- dump registers", - ["r", "reg", "register", "registers"] - ); - command!(cmd_memory, "[offset] limit -- dump memory", ["m", "memory"]); - - let session_id = shell.state.client.start_session().await?; - shell.state.session_id.clone_from(&session_id); - shell.run_async().await?; - shell.state.client.end_session(&session_id).await?; - Ok(()) -} - -struct State { - client: FuelClient, - session_id: String, -} - -#[derive(Debug, thiserror::Error)] -enum ArgError { - #[error("Invalid argument")] - Invalid, - #[error("Not enough arguments")] - NotEnough, - #[error("Too many arguments")] - TooMany, -} - -fn pretty_print_run_result(rr: &RunResult) { - for receipt in rr.receipts() { - println!("Receipt: {:?}", receipt); - } - if let Some(bp) = &rr.breakpoint { - println!( - "Stopped on breakpoint at address {} of contract {}", - bp.pc.0, bp.contract - ); + let result = if config.serve { + forc_debug::server::DapServer::default().start() } else { - println!("Terminated"); - } -} - -async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - let path_to_tx_json = args.pop().ok_or_else(|| Box::new(ArgError::NotEnough))?; - if !args.is_empty() { - return Err(Box::new(ArgError::TooMany)); - } - - let tx_json = std::fs::read(path_to_tx_json)?; - let tx: Transaction = serde_json::from_slice(&tx_json)?; - let status = state.client.start_tx(&state.session_id, &tx).await?; - pretty_print_run_result(&status); - - Ok(()) -} - -async fn cmd_reset(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - if !args.is_empty() { - return Err(Box::new(ArgError::TooMany)); - } - - let _ = state.client.reset(&state.session_id).await?; - - Ok(()) -} - -async fn cmd_continue(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - if !args.is_empty() { - return Err(Box::new(ArgError::TooMany)); - } - - let status = state.client.continue_tx(&state.session_id).await?; - pretty_print_run_result(&status); - - Ok(()) -} - -async fn cmd_step(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - if args.len() > 1 { - return Err(Box::new(ArgError::TooMany)); - } - - state - .client - .set_single_stepping( - &state.session_id, - args.first() - .map(|v| !["off", "no", "disable"].contains(&v.as_str())) - .unwrap_or(true), - ) - .await?; - Ok(()) -} - -async fn cmd_breakpoint(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - let offset = args.pop().ok_or_else(|| Box::new(ArgError::NotEnough))?; - let contract_id = args.pop(); - - if !args.is_empty() { - return Err(Box::new(ArgError::TooMany)); - } - - let offset = if let Some(offset) = parse_int(&offset) { - offset as u64 - } else { - return Err(Box::new(ArgError::Invalid)); + forc_debug::cli::start_cli(&config.api_url).await }; - let contract = if let Some(contract_id) = contract_id { - if let Ok(contract_id) = contract_id.parse::() { - contract_id - } else { - return Err(Box::new(ArgError::Invalid)); - } - } else { - ContractId::zeroed() // Current script - }; - - state - .client - .set_breakpoint(&state.session_id, contract, offset) - .await?; - - Ok(()) -} - -async fn cmd_registers(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - - if args.is_empty() { - for r in 0..VM_REGISTER_COUNT { - let value = state.client.register(&state.session_id, r as u32).await?; - println!("reg[{:#x}] = {:<8} # {}", r, value, register_name(r)); - } - } else { - for arg in &args { - if let Some(v) = parse_int(arg) { - if v < VM_REGISTER_COUNT { - let value = state.client.register(&state.session_id, v as u32).await?; - println!("reg[{:#02x}] = {:<8} # {}", v, value, register_name(v)); - } else { - println!("Register index too large {}", v); - return Ok(()); - } - } else if let Some(index) = register_index(arg) { - let value = state - .client - .register(&state.session_id, index as u32) - .await?; - println!("reg[{:#02x}] = {:<8} # {}", index, value, arg); - } else { - println!("Unknown register name {}", arg); - return Ok(()); - } - } - } - - Ok(()) -} - -async fn cmd_memory(state: &mut State, mut args: Vec) -> Result<(), Box> { - args.remove(0); - - let limit = args - .pop() - .map(|a| parse_int(&a).ok_or(ArgError::Invalid)) - .transpose()? - .unwrap_or(WORD_SIZE * (VM_MAX_RAM as usize)); - - let offset = args - .pop() - .map(|a| parse_int(&a).ok_or(ArgError::Invalid)) - .transpose()? - .unwrap_or(0); - - if !args.is_empty() { - return Err(Box::new(ArgError::TooMany)); - } - - let mem = state - .client - .memory(&state.session_id, offset as u32, limit as u32) - .await?; - - for (i, chunk) in mem.chunks(WORD_SIZE).enumerate() { - print!(" {:06x}:", offset + i * WORD_SIZE); - for byte in chunk { - print!(" {:02x}", byte); - } - println!(); + if let Err(err) = result { + println_error(&format!("{err}")); + std::process::exit(1); } - - Ok(()) -} - -fn parse_int(s: &str) -> Option { - let (s, radix) = if let Some(stripped) = s.strip_prefix("0x") { - (stripped, 16) - } else { - (s, 10) - }; - - let s = s.replace('_', ""); - - usize::from_str_radix(&s, radix).ok() } diff --git a/forc-plugins/forc-debug/src/names.rs b/forc-plugins/forc-debug/src/names.rs index 91e8833cbf4..548305e9ba9 100644 --- a/forc-plugins/forc-debug/src/names.rs +++ b/forc-plugins/forc-debug/src/names.rs @@ -1,8 +1,22 @@ +/// A list of predefined register names mapped to their corresponding indices. pub const REGISTERS: [&str; 16] = [ "zero", "one", "of", "pc", "ssp", "sp", "fp", "hp", "err", "ggas", "cgas", "bal", "is", "ret", "retl", "flag", ]; +/// Returns the name of a register given its index. +/// +/// If the index corresponds to a predefined register, the corresponding name +/// from `REGISTERS` is returned. Otherwise, it returns a formatted name +/// like `"reg{index}"`. +/// +/// # Examples +/// +/// ``` +/// use forc_debug::names::register_name; +/// assert_eq!(register_name(0), "zero".to_string()); +/// assert_eq!(register_name(15), "flag".to_string()); +/// ``` pub fn register_name(index: usize) -> String { if index < REGISTERS.len() { REGISTERS[index].to_owned() @@ -11,6 +25,19 @@ pub fn register_name(index: usize) -> String { } } +/// Returns the index of a register given its name. +/// +/// If the name matches a predefined register in `REGISTERS`, the corresponding +/// index is returned. Otherwise, returns `None`. +/// +/// # Examples +/// +/// ``` +/// use forc_debug::names::register_index; +/// assert_eq!(register_index("zero"), Some(0)); +/// assert_eq!(register_index("flag"), Some(15)); +/// assert_eq!(register_index("unknown"), None); +/// ``` pub fn register_index(name: &str) -> Option { REGISTERS.iter().position(|&n| n == name) } diff --git a/forc-plugins/forc-debug/src/server/error.rs b/forc-plugins/forc-debug/src/server/error.rs deleted file mode 100644 index 1307e57bdd1..00000000000 --- a/forc-plugins/forc-debug/src/server/error.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::types::Instruction; -use dap::requests::Command; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum AdapterError { - #[error("Unhandled command")] - UnhandledCommand { command: Command }, - - #[error("Missing command")] - MissingCommand, - - #[error("Missing configuration")] - MissingConfiguration, - - #[error("Missing source path argument")] - MissingSourcePathArgument, - - #[error("Missing breakpoint location")] - MissingBreakpointLocation, - - #[error("Missing source map")] - MissingSourceMap { pc: Instruction }, - - #[error("Unknown breakpoint")] - UnknownBreakpoint { pc: Instruction }, - - #[error("Build failed")] - BuildFailed { reason: String }, - - #[error("No active test executor")] - NoActiveTestExecutor, - - #[error("Test execution failed")] - TestExecutionFailed { - #[from] - source: anyhow::Error, - }, -} diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_breakpoint_locations.rs b/forc-plugins/forc-debug/src/server/handlers/handle_breakpoint_locations.rs index 8d7e37259ab..4d41dd32604 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_breakpoint_locations.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_breakpoint_locations.rs @@ -1,13 +1,28 @@ -use crate::server::AdapterError; -use crate::server::DapServer; -use dap::requests::BreakpointLocationsArguments; -use dap::types::BreakpointLocation; +use crate::server::{AdapterError, DapServer, HandlerResult}; +use dap::{ + requests::BreakpointLocationsArguments, responses::ResponseBody, types::BreakpointLocation, +}; use std::path::PathBuf; impl DapServer { /// Handles a `breakpoint_locations` request. Returns the list of [BreakpointLocation]s. - pub(crate) fn handle_breakpoint_locations( - &mut self, + pub(crate) fn handle_breakpoint_locations_command( + &self, + args: &BreakpointLocationsArguments, + ) -> HandlerResult { + let result = self.breakpoint_locations(args).map(|breakpoints| { + ResponseBody::BreakpointLocations(dap::responses::BreakpointLocationsResponse { + breakpoints, + }) + }); + match result { + Ok(result) => HandlerResult::ok(result), + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + fn breakpoint_locations( + &self, args: &BreakpointLocationsArguments, ) -> Result, AdapterError> { let source_path = args @@ -62,7 +77,7 @@ mod tests { }, ..Default::default() }; - let result = server.handle_breakpoint_locations(&args).expect("success"); + let result = server.breakpoint_locations(&args).expect("success"); assert_eq!(result.len(), 1); assert_eq!(result[0].line, MOCK_LINE); } @@ -70,15 +85,15 @@ mod tests { #[test] #[should_panic(expected = "MissingSourcePathArgument")] fn test_handle_breakpoint_locations_missing_argument() { - let mut server = DapServer::default(); + let server = DapServer::default(); let args = BreakpointLocationsArguments::default(); - server.handle_breakpoint_locations(&args).unwrap(); + server.breakpoint_locations(&args).unwrap(); } #[test] #[should_panic(expected = "MissingBreakpointLocation")] fn test_handle_breakpoint_locations_missing_breakpoint() { - let mut server = DapServer::default(); + let server = DapServer::default(); let args = BreakpointLocationsArguments { source: dap::types::Source { path: Some(MOCK_SOURCE_PATH.into()), @@ -86,6 +101,6 @@ mod tests { }, ..Default::default() }; - server.handle_breakpoint_locations(&args).unwrap(); + server.breakpoint_locations(&args).unwrap(); } } diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_continue.rs b/forc-plugins/forc-debug/src/server/handlers/handle_continue.rs deleted file mode 100644 index 200c7091a9a..00000000000 --- a/forc-plugins/forc-debug/src/server/handlers/handle_continue.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::server::AdapterError; -use crate::server::DapServer; - -impl DapServer { - /// Handles a `continue` request. Returns true if the server should continue running. - pub(crate) fn handle_continue(&mut self) -> Result { - self.continue_debugging_tests(false) - } -} diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs b/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs deleted file mode 100644 index 3ae3c2f3539..00000000000 --- a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::server::{AdapterError, DapServer}; -use crate::types::Instruction; -use forc_pkg::manifest::GenericManifestFile; -use forc_pkg::{self, BuildProfile, Built, BuiltPackage, PackageManifestFile}; -use forc_test::execute::TestExecutor; -use forc_test::setup::TestSetup; -use forc_test::BuiltTests; -use std::{collections::HashMap, sync::Arc}; -use sway_types::LineCol; - -impl DapServer { - /// Handles a `launch` request. Returns true if the server should continue running. - pub fn handle_launch(&mut self) -> Result { - // Build tests for the given path. - let (pkg_to_debug, test_setup) = self.build_tests()?; - let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| { - if let Some(test_entry) = entry.kind.test() { - return Some((entry, test_entry)); - } - None - }); - - // Construct a TestExecutor for each test and store it - let executors: Vec = entries - .filter_map(|(entry, test_entry)| { - let offset = u32::try_from(entry.finalized.imm) - .expect("test instruction offset out of range"); - let name = entry.finalized.fn_name.clone(); - if test_entry.file_path.as_path() != self.state.program_path.as_path() { - return None; - } - - TestExecutor::build( - &pkg_to_debug.bytecode.bytes, - offset, - test_setup.clone(), - test_entry, - name.clone(), - ) - .ok() - }) - .collect(); - self.state.init_executors(executors); - - // Start debugging - self.start_debugging_tests(false) - } - - /// Builds the tests at the given [PathBuf] and stores the source maps. - pub(crate) fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> { - if let Some(pkg) = &self.state.built_package { - if let Some(setup) = &self.state.test_setup { - return Ok((pkg.clone(), setup.clone())); - } - } - - // 1. Build the packages - let manifest_file = forc_pkg::manifest::ManifestFile::from_dir(&self.state.program_path) - .map_err(|err| AdapterError::BuildFailed { - reason: format!("read manifest file: {:?}", err), - })?; - let pkg_manifest: PackageManifestFile = - manifest_file - .clone() - .try_into() - .map_err(|err: anyhow::Error| AdapterError::BuildFailed { - reason: format!("package manifest: {:?}", err), - })?; - let member_manifests = - manifest_file - .member_manifests() - .map_err(|err| AdapterError::BuildFailed { - reason: format!("member manifests: {:?}", err), - })?; - let lock_path = manifest_file - .lock_path() - .map_err(|err| AdapterError::BuildFailed { - reason: format!("lock path: {:?}", err), - })?; - let build_plan = forc_pkg::BuildPlan::from_lock_and_manifests( - &lock_path, - &member_manifests, - false, - false, - &Default::default(), - ) - .map_err(|err| AdapterError::BuildFailed { - reason: format!("build plan: {:?}", err), - })?; - - let project_name = pkg_manifest.project_name(); - - let outputs = std::iter::once(build_plan.find_member_index(project_name).ok_or( - AdapterError::BuildFailed { - reason: format!("find built project: {}", project_name), - }, - )?) - .collect(); - - let built_packages = forc_pkg::build( - &build_plan, - Default::default(), - &BuildProfile { - optimization_level: sway_core::OptLevel::Opt0, - include_tests: true, - ..Default::default() - }, - &outputs, - &[], - &[], - ) - .map_err(|err| AdapterError::BuildFailed { - reason: format!("build packages: {:?}", err), - })?; - - // 2. Store the source maps - let mut pkg_to_debug: Option<&BuiltPackage> = None; - built_packages.iter().for_each(|(_, built_pkg)| { - if built_pkg.descriptor.manifest_file == pkg_manifest { - pkg_to_debug = Some(built_pkg); - } - let source_map = &built_pkg.source_map; - - let paths = &source_map.paths; - source_map.map.iter().for_each(|(instruction, sm_span)| { - if let Some(path_buf) = paths.get(sm_span.path.0) { - let LineCol { line, .. } = sm_span.range.start; - let (line, instruction) = (line as i64, *instruction as Instruction); - - self.state - .source_map - .entry(path_buf.clone()) - .and_modify(|new_map| { - new_map - .entry(line) - .and_modify(|val| { - // Store the instructions in ascending order - match val.binary_search(&instruction) { - Ok(_) => {} // Ignore duplicates - Err(pos) => val.insert(pos, instruction), - } - }) - .or_insert(vec![instruction]); - }) - .or_insert(HashMap::from([(line, vec![instruction])])); - } else { - self.error(format!( - "Path missing from source map: {:?}", - sm_span.path.0 - )); - } - }); - }); - - // 3. Build the tests - let built_package = pkg_to_debug.ok_or(AdapterError::BuildFailed { - reason: format!("find package: {}", project_name), - })?; - - let built = Built::Package(Arc::from(built_package.clone())); - - let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| { - AdapterError::BuildFailed { - reason: format!("build tests: {:?}", err), - } - })?; - - let pkg_tests = match built_tests { - BuiltTests::Package(pkg_tests) => pkg_tests, - BuiltTests::Workspace(_) => { - return Err(AdapterError::BuildFailed { - reason: "package tests: workspace tests not supported".into(), - }) - } - }; - let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed { - reason: format!("test setup: {:?}", err), - })?; - self.state.built_package = Some(built_package.clone()); - self.state.test_setup = Some(test_setup.clone()); - Ok((built_package.clone(), test_setup)) - } -} diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_next.rs b/forc-plugins/forc-debug/src/server/handlers/handle_next.rs deleted file mode 100644 index 484228494e6..00000000000 --- a/forc-plugins/forc-debug/src/server/handlers/handle_next.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::server::AdapterError; -use crate::server::DapServer; - -impl DapServer { - /// Handles a `next` request. Returns true if the server should continue running. - pub(crate) fn handle_next(&mut self) -> Result { - self.continue_debugging_tests(true) - } -} diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_set_breakpoints.rs b/forc-plugins/forc-debug/src/server/handlers/handle_set_breakpoints.rs index 63390fb458e..d1b62c269cd 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_set_breakpoints.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_set_breakpoints.rs @@ -1,12 +1,27 @@ -use crate::server::AdapterError; -use crate::server::DapServer; -use dap::requests::SetBreakpointsArguments; -use dap::types::{Breakpoint, StartDebuggingRequestKind}; +use crate::server::{AdapterError, DapServer, HandlerResult}; +use dap::{ + requests::SetBreakpointsArguments, + responses::ResponseBody, + types::{Breakpoint, StartDebuggingRequestKind}, +}; use std::path::PathBuf; impl DapServer { /// Handles a `set_breakpoints` request. Returns the list of [Breakpoint]s for the path provided in `args`. - pub(crate) fn handle_set_breakpoints( + pub(crate) fn handle_set_breakpoints_command( + &mut self, + args: &SetBreakpointsArguments, + ) -> HandlerResult { + let result = self.set_breakpoints(args).map(|breakpoints| { + ResponseBody::SetBreakpoints(dap::responses::SetBreakpointsResponse { breakpoints }) + }); + match result { + Ok(result) => HandlerResult::ok(result), + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + fn set_breakpoints( &mut self, args: &SetBreakpointsArguments, ) -> Result, AdapterError> { @@ -44,24 +59,22 @@ impl DapServer { .iter() .map(|source_bp| { let verified = source_map.contains_key(&source_bp.line); - - match existing_breakpoints.iter().find(|bp| match bp.line { - Some(line) => line == source_bp.line, - None => false, - }) { - Some(existing_bp) => Breakpoint { + if let Some(existing_bp) = existing_breakpoints + .iter() + .find(|bp| bp.line.map_or(false, |line| line == source_bp.line)) + { + Breakpoint { verified, ..existing_bp.clone() - }, - None => { - let id = Some(self.breakpoint_id_gen.next()); - Breakpoint { - id, - verified, - line: Some(source_bp.line), - source: Some(args.source.clone()), - ..Default::default() - } + } + } else { + let id = Some(self.breakpoint_id_gen.next()); + Breakpoint { + id, + verified, + line: Some(source_bp.line), + source: Some(args.source.clone()), + ..Default::default() } } }) @@ -131,7 +144,7 @@ mod tests { fn test_handle_set_breakpoints_existing_verified() { let mut server = get_test_server(true, true); let args = get_test_args(); - let result = server.handle_set_breakpoints(&args).expect("success"); + let result = server.set_breakpoints(&args).expect("success"); assert_eq!(result.len(), 1); assert_eq!(result[0].line, Some(MOCK_LINE)); assert_eq!(result[0].id, Some(MOCK_BP_ID)); @@ -146,7 +159,7 @@ mod tests { fn test_handle_set_breakpoints_existing_unverified() { let mut server = get_test_server(false, true); let args = get_test_args(); - let result = server.handle_set_breakpoints(&args).expect("success"); + let result = server.set_breakpoints(&args).expect("success"); assert_eq!(result.len(), 1); assert_eq!(result[0].line, Some(MOCK_LINE)); assert_eq!(result[0].id, Some(MOCK_BP_ID)); @@ -161,7 +174,7 @@ mod tests { fn test_handle_set_breakpoints_new() { let mut server = get_test_server(true, false); let args = get_test_args(); - let result = server.handle_set_breakpoints(&args).expect("success"); + let result = server.set_breakpoints(&args).expect("success"); assert_eq!(result.len(), 1); assert_eq!(result[0].line, Some(MOCK_LINE)); assert_eq!( @@ -176,6 +189,6 @@ mod tests { fn test_handle_breakpoint_locations_missing_argument() { let mut server = get_test_server(true, true); let args = SetBreakpointsArguments::default(); - server.handle_set_breakpoints(&args).unwrap(); + server.set_breakpoints(&args).unwrap(); } } diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_stack_trace.rs b/forc-plugins/forc-debug/src/server/handlers/handle_stack_trace.rs index fd26ff067af..0ee70988626 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_stack_trace.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_stack_trace.rs @@ -1,12 +1,25 @@ -use crate::server::util; -use crate::server::AdapterError; -use crate::server::DapServer; -use dap::types::StackFrame; -use dap::types::StackFramePresentationhint; +use crate::server::{util, AdapterError, DapServer, HandlerResult}; +use dap::{ + responses::ResponseBody, + types::{StackFrame, StackFramePresentationhint}, +}; impl DapServer { /// Handles a `stack_trace` request. Returns the list of [StackFrame]s for the current execution state. - pub(crate) fn handle_stack_trace(&self) -> Result, AdapterError> { + pub(crate) fn handle_stack_trace_command(&self) -> HandlerResult { + let result = self.stack_trace().map(|stack_frames| { + ResponseBody::StackTrace(dap::responses::StackTraceResponse { + stack_frames, + total_frames: None, + }) + }); + match result { + Ok(result) => HandlerResult::ok(result), + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + fn stack_trace(&self) -> Result, AdapterError> { let executor = self .state .executors diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_variables.rs b/forc-plugins/forc-debug/src/server/handlers/handle_variables.rs index 1b4db95eab6..498413a0a80 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_variables.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_variables.rs @@ -1,21 +1,26 @@ -use crate::names::register_name; -use crate::server::AdapterError; -use crate::server::DapServer; -use crate::server::INSTRUCTIONS_VARIABLE_REF; -use crate::server::REGISTERS_VARIABLE_REF; -use dap::requests::VariablesArguments; -use dap::types::Variable; -use fuel_vm::fuel_asm::Imm06; -use fuel_vm::fuel_asm::Imm12; -use fuel_vm::fuel_asm::Imm18; -use fuel_vm::fuel_asm::Imm24; -use fuel_vm::fuel_asm::Instruction; -use fuel_vm::fuel_asm::RawInstruction; -use fuel_vm::fuel_asm::RegId; +use crate::{ + names::register_name, + server::{ + AdapterError, DapServer, HandlerResult, INSTRUCTIONS_VARIABLE_REF, REGISTERS_VARIABLE_REF, + }, +}; +use dap::{requests::VariablesArguments, responses::ResponseBody, types::Variable}; +use fuel_vm::fuel_asm::{Imm06, Imm12, Imm18, Imm24, Instruction, RawInstruction, RegId}; impl DapServer { - /// Handles a `variables` request. Returns the list of [Variable]s for the current execution state. - pub(crate) fn handle_variables( + /// Processes a variables request, returning all variables and their current values. + pub(crate) fn handle_variables_command(&self, args: &VariablesArguments) -> HandlerResult { + let result = self.get_variables(args).map(|variables| { + ResponseBody::Variables(dap::responses::VariablesResponse { variables }) + }); + match result { + Ok(result) => HandlerResult::ok(result), + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + /// Returns the list of [Variable]s for the current execution state. + pub(crate) fn get_variables( &self, args: &VariablesArguments, ) -> Result, AdapterError> { @@ -32,7 +37,7 @@ impl DapServer { .enumerate() .map(|(index, value)| Variable { name: register_name(index), - value: format!("0x{:X?}", value), + value: format!("0x{value:X?}"), ..Default::default() }) .collect::>(); @@ -56,7 +61,7 @@ impl DapServer { .iter() .filter_map(|(name, value)| { value.as_ref().map(|value| Variable { - name: name.to_string(), + name: (*name).to_string(), value: value.to_string(), ..Default::default() }) diff --git a/forc-plugins/forc-debug/src/server/handlers/mod.rs b/forc-plugins/forc-debug/src/server/handlers/mod.rs index 66ef3f3aebb..601844a343f 100644 --- a/forc-plugins/forc-debug/src/server/handlers/mod.rs +++ b/forc-plugins/forc-debug/src/server/handlers/mod.rs @@ -1,7 +1,130 @@ +use crate::{ + error::AdapterError, + server::{ + AdditionalData, DapServer, HandlerResult, INSTRUCTIONS_VARIABLE_REF, + REGISTERS_VARIABLE_REF, THREAD_ID, + }, +}; +use dap::{ + prelude::*, + types::{Scope, StartDebuggingRequestKind}, +}; +use requests::{EvaluateArguments, LaunchRequestArguments}; +use std::path::PathBuf; + pub(crate) mod handle_breakpoint_locations; -pub(crate) mod handle_continue; -pub(crate) mod handle_launch; -pub(crate) mod handle_next; pub(crate) mod handle_set_breakpoints; pub(crate) mod handle_stack_trace; pub(crate) mod handle_variables; + +impl DapServer { + pub(crate) fn handle_attach(&mut self) -> HandlerResult { + self.state.mode = Some(StartDebuggingRequestKind::Attach); + self.error("This feature is not currently supported.".into()); + HandlerResult::ok_with_exit(ResponseBody::Attach, 0) + } + + pub(crate) fn handle_initialize(&mut self) -> HandlerResult { + HandlerResult::ok(ResponseBody::Initialize(types::Capabilities { + supports_breakpoint_locations_request: Some(true), + supports_configuration_done_request: Some(true), + ..Default::default() + })) + } + + pub(crate) fn handle_configuration_done(&mut self) -> HandlerResult { + self.state.configuration_done = true; + HandlerResult::ok(ResponseBody::ConfigurationDone) + } + + pub(crate) fn handle_launch(&mut self, args: &LaunchRequestArguments) -> HandlerResult { + self.state.mode = Some(StartDebuggingRequestKind::Launch); + if let Some(additional_data) = &args.additional_data { + if let Ok(data) = serde_json::from_value::(additional_data.clone()) { + self.state.program_path = PathBuf::from(data.program); + return HandlerResult::ok(ResponseBody::Launch); + } + } + HandlerResult::err_with_exit(AdapterError::MissingConfiguration, 1) + } + + /// Handles a `next` request. Returns true if the server should continue running. + pub(crate) fn handle_next(&mut self) -> HandlerResult { + match self.continue_debugging_tests(true) { + Ok(true) => HandlerResult::ok(ResponseBody::Next), + Ok(false) => { + // The tests finished executing + HandlerResult::ok_with_exit(ResponseBody::Next, 0) + } + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + /// Handles a `continue` request. Returns true if the server should continue running. + pub(crate) fn handle_continue(&mut self) -> HandlerResult { + match self.continue_debugging_tests(false) { + Ok(true) => HandlerResult::ok(ResponseBody::Continue(responses::ContinueResponse { + all_threads_continued: Some(true), + })), + Ok(false) => HandlerResult::ok_with_exit( + ResponseBody::Continue(responses::ContinueResponse { + all_threads_continued: Some(true), + }), + 0, + ), + Err(e) => HandlerResult::err_with_exit(e, 1), + } + } + + pub(crate) fn handle_evaluate(&mut self, args: &EvaluateArguments) -> HandlerResult { + let result = match args.context { + Some(types::EvaluateArgumentsContext::Variables) => args.expression.clone(), + _ => "Evaluate expressions not supported in this context".into(), + }; + HandlerResult::ok(ResponseBody::Evaluate(responses::EvaluateResponse { + result, + ..Default::default() + })) + } + + pub(crate) fn handle_pause(&mut self) -> HandlerResult { + // TODO: interpreter pause function + if let Some(executor) = self.state.executor() { + executor.interpreter.set_single_stepping(true); + } + HandlerResult::ok(ResponseBody::Pause) + } + + pub(crate) fn handle_restart(&mut self) -> HandlerResult { + self.state.reset(); + HandlerResult::ok(ResponseBody::Restart) + } + + pub(crate) fn handle_scopes(&mut self) -> HandlerResult { + HandlerResult::ok(ResponseBody::Scopes(responses::ScopesResponse { + scopes: vec![ + Scope { + name: "Current VM Instruction".into(), + presentation_hint: Some(types::ScopePresentationhint::Registers), + variables_reference: INSTRUCTIONS_VARIABLE_REF, + ..Default::default() + }, + Scope { + name: "Registers".into(), + presentation_hint: Some(types::ScopePresentationhint::Registers), + variables_reference: REGISTERS_VARIABLE_REF, + ..Default::default() + }, + ], + })) + } + + pub(crate) fn handle_threads(&mut self) -> HandlerResult { + HandlerResult::ok(ResponseBody::Threads(responses::ThreadsResponse { + threads: vec![types::Thread { + id: THREAD_ID, + name: "main".into(), + }], + })) + } +} diff --git a/forc-plugins/forc-debug/src/server/mod.rs b/forc-plugins/forc-debug/src/server/mod.rs index 968b1cf4ec1..e06959c5e7b 100644 --- a/forc-plugins/forc-debug/src/server/mod.rs +++ b/forc-plugins/forc-debug/src/server/mod.rs @@ -1,25 +1,36 @@ -mod error; mod handlers; mod state; mod util; -use self::error::AdapterError; -use self::state::ServerState; -use self::util::IdGenerator; -use crate::types::DynResult; -use crate::types::Instruction; -use dap::events::OutputEventBody; -use dap::events::{ExitedEventBody, StoppedEventBody}; -use dap::prelude::*; -use dap::types::{Scope, StartDebuggingRequestKind}; -use forc_test::execute::DebugResult; +use crate::{ + error::{self, AdapterError, Error}, + server::{state::ServerState, util::IdGenerator}, + types::{ExitCode, Instruction}, +}; +use dap::{ + events::{ExitedEventBody, OutputEventBody, StoppedEventBody}, + prelude::*, + types::StartDebuggingRequestKind, +}; +use forc_pkg::{ + manifest::GenericManifestFile, + source::IPFSNode, + {self, BuildProfile, Built, BuiltPackage, PackageManifestFile}, +}; +use forc_test::{ + execute::{DebugResult, TestExecutor}, + setup::TestSetup, + BuiltTests, +}; use serde::{Deserialize, Serialize}; -use std::io::{Read, Write}; use std::{ - io::{BufReader, BufWriter}, - path::PathBuf, + collections::HashMap, + io::{BufReader, BufWriter, Read, Write}, process, + sync::Arc, }; +use sway_core::BuildTarget; +use sway_types::LineCol; pub const THREAD_ID: i64 = 0; pub const REGISTERS_VARIABLE_REF: i64 = 1; @@ -52,230 +63,115 @@ impl Default for DapServer { } impl DapServer { + /// Creates a new DAP server with custom input and output streams. + /// + /// # Arguments + /// * `input` - Source of DAP protocol messages (usually stdin) + /// * `output` - Destination for DAP protocol messages (usually stdout) pub fn new(input: Box, output: Box) -> Self { let server = Server::new(BufReader::new(input), BufWriter::new(output)); DapServer { server, - state: Default::default(), - breakpoint_id_gen: Default::default(), + state: ServerState::default(), + breakpoint_id_gen: IdGenerator::default(), } } - pub fn start(&mut self) -> DynResult<()> { + /// Runs the debug server event loop, handling client requests until completion or error. + pub fn start(&mut self) -> error::Result<()> { loop { - match self.server.poll_request()? { - Some(req) => { - let rsp = self.handle_request(req)?; - self.server.respond(rsp)?; - - if !self.state.initialized_event_sent { - let _ = self.server.send_event(Event::Initialized); - self.state.initialized_event_sent = true; - } - if self.state.configuration_done && !self.state.started_debugging { - if let Some(StartDebuggingRequestKind::Launch) = self.state.mode { - self.state.started_debugging = true; - match self.handle_launch() { - Ok(true) => {} - Ok(false) => { - // The tests finished executing - self.exit(0); - } - Err(e) => { - self.error(format!("Launch error: {:?}", e)); - self.exit(1); - } - } - } + let req = match self.server.poll_request()? { + Some(req) => req, + None => return Err(Error::AdapterError(AdapterError::MissingCommand)), + }; + + // Handle the request and send response + let response = self.handle_request(req)?; + self.server.respond(response)?; + + // Handle one-time initialization + if !self.state.initialized_event_sent { + let _ = self.server.send_event(Event::Initialized); + self.state.initialized_event_sent = true; + } + + // Handle launch after configuration is complete + if self.should_launch() { + self.state.started_debugging = true; + match self.launch() { + Ok(true) => continue, + Ok(false) => self.exit(0), // The tests finished executing + Err(e) => { + self.error(format!("Launch error: {e:?}")); + self.exit(1); } } - None => return Err(Box::new(AdapterError::MissingCommand)), - }; + } } } - fn handle_request(&mut self, req: Request) -> DynResult { - let command = req.command.clone(); - let (result, exit_code) = self.handle_command(command); + /// Processes a debug adapter request and generates appropriate response. + fn handle_request(&mut self, req: Request) -> error::Result { + let (result, exit_code) = self.handle_command(&req.command).into_tuple(); let response = match result { Ok(rsp) => Ok(req.success(rsp)), Err(e) => { - self.error(format!("{:?}", e)); - Ok(req.error(&format!("{:?}", e))) + self.error(format!("{e:?}")); + Ok(req.error(&format!("{e:?}"))) } }; if let Some(exit_code) = exit_code { - self.exit(exit_code) + self.exit(exit_code); } response } /// Handles a command and returns the result and exit code, if any. - pub fn handle_command( - &mut self, - command: Command, - ) -> (Result, Option) { + pub fn handle_command(&mut self, command: &Command) -> HandlerResult { match command { - Command::Attach(_) => { - self.state.mode = Some(StartDebuggingRequestKind::Attach); - self.error("This feature is not currently supported.".into()); - (Ok(ResponseBody::Attach), Some(0)) - } + Command::Attach(_) => self.handle_attach(), Command::BreakpointLocations(ref args) => { - match self.handle_breakpoint_locations(args) { - Ok(breakpoints) => ( - Ok(ResponseBody::BreakpointLocations( - responses::BreakpointLocationsResponse { breakpoints }, - )), - None, - ), - Err(e) => (Err(e), None), - } - } - Command::ConfigurationDone => { - self.state.configuration_done = true; - (Ok(ResponseBody::ConfigurationDone), None) - } - Command::Continue(_) => match self.handle_continue() { - Ok(true) => ( - Ok(ResponseBody::Continue(responses::ContinueResponse { - all_threads_continued: Some(true), - })), - None, - ), - Ok(false) => ( - Ok(ResponseBody::Continue(responses::ContinueResponse { - all_threads_continued: Some(true), - })), - Some(0), - ), - Err(e) => (Err(e), Some(1)), - }, - Command::Disconnect(_) => (Ok(ResponseBody::Disconnect), Some(0)), - Command::Evaluate(args) => { - let result = match args.context { - Some(types::EvaluateArgumentsContext::Variables) => args.expression.clone(), - _ => "Evaluate expressions not supported in this context".into(), - }; - ( - Ok(ResponseBody::Evaluate(responses::EvaluateResponse { - result, - ..Default::default() - })), - None, - ) - } - Command::Initialize(_) => ( - Ok(ResponseBody::Initialize(types::Capabilities { - supports_breakpoint_locations_request: Some(true), - supports_configuration_done_request: Some(true), - ..Default::default() - })), - None, - ), - Command::Launch(ref args) => { - self.state.mode = Some(StartDebuggingRequestKind::Launch); - if let Some(additional_data) = &args.additional_data { - if let Ok(data) = - serde_json::from_value::(additional_data.clone()) - { - self.state.program_path = PathBuf::from(data.program); - return (Ok(ResponseBody::Launch), None); - } - } - (Err(AdapterError::MissingConfiguration), Some(1)) - } - Command::Next(_) => { - match self.handle_next() { - Ok(true) => (Ok(ResponseBody::Next), None), - Ok(false) => { - // The tests finished executing - (Ok(ResponseBody::Next), Some(0)) - } - Err(e) => (Err(e), Some(1)), - } - } - Command::Pause(_) => { - // TODO: interpreter pause function - if let Some(executor) = self.state.executor() { - executor.interpreter.set_single_stepping(true); - } - (Ok(ResponseBody::Pause), None) + self.handle_breakpoint_locations_command(args) } - Command::Restart(_) => { - self.state.reset(); - (Ok(ResponseBody::Restart), None) - } - Command::Scopes(_) => ( - Ok(ResponseBody::Scopes(responses::ScopesResponse { - scopes: vec![ - Scope { - name: "Current VM Instruction".into(), - presentation_hint: Some(types::ScopePresentationhint::Registers), - variables_reference: INSTRUCTIONS_VARIABLE_REF, - ..Default::default() - }, - Scope { - name: "Registers".into(), - presentation_hint: Some(types::ScopePresentationhint::Registers), - variables_reference: REGISTERS_VARIABLE_REF, - ..Default::default() - }, - ], - })), - None, - ), - Command::SetBreakpoints(ref args) => match self.handle_set_breakpoints(args) { - Ok(breakpoints) => ( - Ok(ResponseBody::SetBreakpoints( - responses::SetBreakpointsResponse { breakpoints }, - )), - None, - ), - Err(e) => (Err(e), None), - }, - Command::StackTrace(_) => match self.handle_stack_trace() { - Ok(stack_frames) => ( - Ok(ResponseBody::StackTrace(responses::StackTraceResponse { - stack_frames, - total_frames: None, - })), - None, - ), - Err(e) => (Err(e), None), - }, + Command::ConfigurationDone => self.handle_configuration_done(), + Command::Continue(_) => self.handle_continue(), + Command::Disconnect(_) => HandlerResult::ok_with_exit(ResponseBody::Disconnect, 0), + Command::Evaluate(args) => self.handle_evaluate(args), + Command::Initialize(_) => self.handle_initialize(), + Command::Launch(ref args) => self.handle_launch(args), + Command::Next(_) => self.handle_next(), + Command::Pause(_) => self.handle_pause(), + Command::Restart(_) => self.handle_restart(), + Command::Scopes(_) => self.handle_scopes(), + Command::SetBreakpoints(ref args) => self.handle_set_breakpoints_command(args), + Command::StackTrace(_) => self.handle_stack_trace_command(), Command::StepIn(_) => { self.error("This feature is not currently supported.".into()); - (Ok(ResponseBody::StepIn), None) + HandlerResult::ok(ResponseBody::StepIn) } Command::StepOut(_) => { self.error("This feature is not currently supported.".into()); - (Ok(ResponseBody::StepOut), None) + HandlerResult::ok(ResponseBody::StepOut) } - Command::Terminate(_) => (Ok(ResponseBody::Terminate), Some(0)), - Command::TerminateThreads(_) => (Ok(ResponseBody::TerminateThreads), Some(0)), - Command::Threads => ( - Ok(ResponseBody::Threads(responses::ThreadsResponse { - threads: vec![types::Thread { - id: THREAD_ID, - name: "main".into(), - }], - })), - None, - ), - Command::Variables(ref args) => match self.handle_variables(args) { - Ok(variables) => ( - Ok(ResponseBody::Variables(responses::VariablesResponse { - variables, - })), - None, - ), - Err(e) => (Err(e), None), - }, - _ => (Err(AdapterError::UnhandledCommand { command }), None), + Command::Terminate(_) => HandlerResult::ok_with_exit(ResponseBody::Terminate, 0), + Command::TerminateThreads(_) => { + HandlerResult::ok_with_exit(ResponseBody::TerminateThreads, 0) + } + Command::Threads => self.handle_threads(), + Command::Variables(ref args) => self.handle_variables_command(args), + _ => HandlerResult::err(AdapterError::UnhandledCommand { + command: command.clone(), + }), } } + /// Checks whether debug session is ready to begin launching tests. + fn should_launch(&self) -> bool { + self.state.configuration_done + && !self.state.started_debugging + && matches!(self.state.mode, Some(StartDebuggingRequestKind::Launch)) + } + /// Logs a message to the client's debugger console output. fn log(&mut self, output: String) { let _ = self.server.send_event(Event::Output(OutputEventBody { @@ -293,53 +189,212 @@ impl DapServer { })); } + /// Logs test execution results in a cargo-test-like format, showing duration and gas usage for each test. fn log_test_results(&mut self) { if !self.state.executors.is_empty() { return; } - - let results = self - .state - .test_results + let test_results = &self.state.test_results; + let test_lines = test_results .iter() - .map(|result| { - let outcome = match result.passed() { - true => "ok", - false => "failed", - }; - + .map(|r| { + let outcome = if r.passed() { "ok" } else { "failed" }; format!( "test {} ... {} ({}ms, {} gas)", - result.name, + r.name, outcome, - result.duration.as_millis(), - result.gas_used + r.duration.as_millis(), + r.gas_used ) }) .collect::>() .join("\n"); - let final_outcome = match self.state.test_results.iter().any(|r| !r.passed()) { - true => "FAILED", - false => "OK", + + let passed = test_results.iter().filter(|r| r.passed()).count(); + let final_outcome = if passed == test_results.len() { + "OK" + } else { + "FAILED" }; - let passed = self - .state - .test_results - .iter() - .filter(|r| r.passed()) - .count(); - let failed = self - .state - .test_results - .iter() - .filter(|r| !r.passed()) - .count(); + self.log(format!( - "{}\nResult: {}. {} passed. {} failed.\n", - results, final_outcome, passed, failed + "{test_lines}\nResult: {final_outcome}. {passed} passed. {} failed.\n", + test_results.len() - passed )); } + /// Handles a `launch` request. Returns true if the server should continue running. + pub fn launch(&mut self) -> Result { + // Build tests for the given path. + let (pkg_to_debug, test_setup) = self.build_tests()?; + let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| { + if let Some(test_entry) = entry.kind.test() { + return Some((entry, test_entry)); + } + None + }); + + // Construct a TestExecutor for each test and store it + let executors: Vec = entries + .filter_map(|(entry, test_entry)| { + let offset = u32::try_from(entry.finalized.imm) + .expect("test instruction offset out of range"); + let name = entry.finalized.fn_name.clone(); + if test_entry.file_path.as_path() != self.state.program_path.as_path() { + return None; + } + + TestExecutor::build( + &pkg_to_debug.bytecode.bytes, + offset, + test_setup.clone(), + test_entry, + name.clone(), + ) + .ok() + }) + .collect(); + self.state.init_executors(executors); + + // Start debugging + self.start_debugging_tests(false) + } + + /// Builds the tests at the given [PathBuf] and stores the source maps. + pub(crate) fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> { + if let Some(pkg) = &self.state.built_package { + if let Some(setup) = &self.state.test_setup { + return Ok((pkg.clone(), setup.clone())); + } + } + + // 1. Build the packages + let manifest_file = forc_pkg::manifest::ManifestFile::from_dir(&self.state.program_path) + .map_err(|err| AdapterError::BuildFailed { + reason: format!("read manifest file: {err:?}"), + })?; + let pkg_manifest: PackageManifestFile = + manifest_file + .clone() + .try_into() + .map_err(|err: anyhow::Error| AdapterError::BuildFailed { + reason: format!("package manifest: {err:?}"), + })?; + let member_manifests = + manifest_file + .member_manifests() + .map_err(|err| AdapterError::BuildFailed { + reason: format!("member manifests: {err:?}"), + })?; + let lock_path = manifest_file + .lock_path() + .map_err(|err| AdapterError::BuildFailed { + reason: format!("lock path: {err:?}"), + })?; + let build_plan = forc_pkg::BuildPlan::from_lock_and_manifests( + &lock_path, + &member_manifests, + false, + false, + &IPFSNode::default(), + ) + .map_err(|err| AdapterError::BuildFailed { + reason: format!("build plan: {err:?}"), + })?; + + let project_name = pkg_manifest.project_name(); + + let outputs = std::iter::once(build_plan.find_member_index(project_name).ok_or( + AdapterError::BuildFailed { + reason: format!("find built project: {project_name}"), + }, + )?) + .collect(); + + let built_packages = forc_pkg::build( + &build_plan, + BuildTarget::default(), + &BuildProfile { + optimization_level: sway_core::OptLevel::Opt0, + include_tests: true, + ..Default::default() + }, + &outputs, + &[], + &[], + ) + .map_err(|err| AdapterError::BuildFailed { + reason: format!("build packages: {err:?}"), + })?; + + // 2. Store the source maps + let mut pkg_to_debug: Option<&BuiltPackage> = None; + for (_, built_pkg) in &built_packages { + if built_pkg.descriptor.manifest_file == pkg_manifest { + pkg_to_debug = Some(built_pkg); + } + let source_map = &built_pkg.source_map; + + let paths = &source_map.paths; + source_map.map.iter().for_each(|(instruction, sm_span)| { + if let Some(path_buf) = paths.get(sm_span.path.0) { + let LineCol { line, .. } = sm_span.range.start; + let (line, instruction) = (line as i64, *instruction as Instruction); + + self.state + .source_map + .entry(path_buf.clone()) + .and_modify(|new_map| { + new_map + .entry(line) + .and_modify(|val| { + // Store the instructions in ascending order + match val.binary_search(&instruction) { + Ok(_) => {} // Ignore duplicates + Err(pos) => val.insert(pos, instruction), + } + }) + .or_insert(vec![instruction]); + }) + .or_insert(HashMap::from([(line, vec![instruction])])); + } else { + self.error(format!( + "Path missing from source map: {:?}", + sm_span.path.0 + )); + } + }); + } + + // 3. Build the tests + let built_package = pkg_to_debug.ok_or(AdapterError::BuildFailed { + reason: format!("find package: {project_name}"), + })?; + + let built = Built::Package(Arc::from(built_package.clone())); + + let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| { + AdapterError::BuildFailed { + reason: format!("build tests: {err:?}"), + } + })?; + + let pkg_tests = match built_tests { + BuiltTests::Package(pkg_tests) => pkg_tests, + BuiltTests::Workspace(_) => { + return Err(AdapterError::BuildFailed { + reason: "package tests: workspace tests not supported".into(), + }) + } + }; + let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed { + reason: format!("test setup: {err:?}"), + })?; + self.state.built_package = Some(built_package.clone()); + self.state.test_setup = Some(test_setup.clone()); + Ok((built_package.clone(), test_setup)) + } + /// Sends the 'exited' event to the client and kills the server process. fn exit(&mut self, exit_code: i64) { let _ = self @@ -422,3 +477,49 @@ impl DapServer { Ok(false) } } + +/// Represents the result of a DAP handler operation, combining the response/error and an optional exit code +#[derive(Debug)] +pub struct HandlerResult { + response: Result, + exit_code: Option, +} + +impl HandlerResult { + /// Creates a new successful result with no exit code + pub fn ok(response: ResponseBody) -> Self { + Self { + response: Ok(response), + exit_code: None, + } + } + + /// Creates a new successful result with an exit code + pub fn ok_with_exit(response: ResponseBody, code: ExitCode) -> Self { + Self { + response: Ok(response), + exit_code: Some(code), + } + } + + /// Creates a new error result with an exit code + pub fn err_with_exit(error: AdapterError, code: ExitCode) -> Self { + Self { + response: Err(error), + exit_code: Some(code), + } + } + + /// Creates a new error result with no exit code + pub fn err(error: AdapterError) -> Self { + Self { + response: Err(error), + exit_code: None, + } + } + + /// Deconstructs the result into its original tuple form + pub fn into_tuple(self) -> (Result, Option) { + (self.response, self.exit_code) + } +} diff --git a/forc-plugins/forc-debug/src/server/state.rs b/forc-plugins/forc-debug/src/server/state.rs index 948869ead23..a99d5898791 100644 --- a/forc-plugins/forc-debug/src/server/state.rs +++ b/forc-plugins/forc-debug/src/server/state.rs @@ -1,12 +1,10 @@ -use super::AdapterError; -use crate::types::Breakpoints; -use crate::types::Instruction; -use crate::types::SourceMap; +use crate::{ + error::AdapterError, + types::{Breakpoints, Instruction, SourceMap}, +}; use dap::types::StartDebuggingRequestKind; use forc_pkg::BuiltPackage; -use forc_test::execute::TestExecutor; -use forc_test::setup::TestSetup; -use forc_test::TestResult; +use forc_test::{execute::TestExecutor, setup::TestSetup, TestResult}; use std::path::PathBuf; #[derive(Default, Debug, Clone)] @@ -65,7 +63,7 @@ impl ServerState { self.source_map .iter() .find_map(|(source_path, source_map)| { - for (&line, instructions) in source_map.iter() { + for (&line, instructions) in source_map { // Divide by 4 to get the opcode offset rather than the program counter offset. let instruction_offset = pc / 4; if instructions @@ -75,7 +73,6 @@ impl ServerState { return Some((source_path, line)); } } - None }) .ok_or(AdapterError::MissingSourceMap { pc }) diff --git a/forc-plugins/forc-debug/src/server/util.rs b/forc-plugins/forc-debug/src/server/util.rs index ed24363c17b..513b071e192 100644 --- a/forc-plugins/forc-debug/src/server/util.rs +++ b/forc-plugins/forc-debug/src/server/util.rs @@ -27,6 +27,9 @@ impl IdGenerator { } } +/// Converts a filesystem path into a DAP Source object, which is used by the debug adapter +/// to identify source locations. Only sets the path field, leaving other Source fields at +/// their default values. pub(crate) fn path_into_source(path: &Path) -> Source { Source { path: Some(path.to_string_lossy().into_owned()), diff --git a/forc-plugins/forc-debug/src/types.rs b/forc-plugins/forc-debug/src/types.rs index a7f9828ddc5..4eb9b194eea 100644 --- a/forc-plugins/forc-debug/src/types.rs +++ b/forc-plugins/forc-debug/src/types.rs @@ -1,9 +1,8 @@ use dap::types::Breakpoint; -use std::collections::HashMap; -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; -pub type DynResult = std::result::Result>; pub type Line = i64; +pub type ExitCode = i64; pub type Instruction = u64; pub type FileSourceMap = HashMap>; pub type SourceMap = HashMap; diff --git a/forc-plugins/forc-debug/tests/server_integration.rs b/forc-plugins/forc-debug/tests/server_integration.rs index 5f33e658b3f..5bd291b63a4 100644 --- a/forc-plugins/forc-debug/tests/server_integration.rs +++ b/forc-plugins/forc-debug/tests/server_integration.rs @@ -7,8 +7,12 @@ use dap::{ use forc_debug::server::{ AdditionalData, DapServer, INSTRUCTIONS_VARIABLE_REF, REGISTERS_VARIABLE_REF, }; -use std::sync::Mutex; -use std::{env, io::Write, path::PathBuf, sync::Arc}; +use std::{ + env, + io::Write, + path::PathBuf, + sync::{Arc, Mutex}, +}; pub fn sway_workspace_dir() -> PathBuf { env::current_dir().unwrap().parent().unwrap().to_path_buf() @@ -59,12 +63,16 @@ fn test_server_attach_mode() { let mut server = DapServer::new(input, output); // Initialize request - let (result, exit_code) = server.handle_command(Command::Initialize(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Initialize(Default::default())) + .into_tuple(); assert!(matches!(result, Ok(ResponseBody::Initialize(_)))); assert!(exit_code.is_none()); // Attach request - let (result, exit_code) = server.handle_command(Command::Attach(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Attach(Default::default())) + .into_tuple(); assert!(matches!(result, Ok(ResponseBody::Attach))); assert_eq!(exit_code, Some(0)); assert_not_supported_event(output_capture.take_event()); @@ -81,7 +89,9 @@ fn test_server_launch_mode() { let source_str = program_path.to_string_lossy().to_string(); // Initialize request - let (result, exit_code) = server.handle_command(Command::Initialize(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Initialize(Default::default())) + .into_tuple(); assert!(matches!(result, Ok(ResponseBody::Initialize(_)))); assert!(exit_code.is_none()); @@ -90,16 +100,18 @@ fn test_server_launch_mode() { program: source_str.clone(), }) .unwrap(); - let (result, exit_code) = server.handle_command(Command::Launch(LaunchRequestArguments { - additional_data: Some(additional_data), - ..Default::default() - })); + let (result, exit_code) = server + .handle_command(&Command::Launch(LaunchRequestArguments { + additional_data: Some(additional_data), + ..Default::default() + })) + .into_tuple(); assert!(matches!(result, Ok(ResponseBody::Launch))); assert!(exit_code.is_none()); // Set Breakpoints - let (result, exit_code) = - server.handle_command(Command::SetBreakpoints(SetBreakpointsArguments { + let (result, exit_code) = server + .handle_command(&Command::SetBreakpoints(SetBreakpointsArguments { source: Source { path: Some(source_str.clone()), ..Default::default() @@ -119,7 +131,8 @@ fn test_server_launch_mode() { }, ]), ..Default::default() - })); + })) + .into_tuple(); match result.expect("set breakpoints result") { ResponseBody::SetBreakpoints(res) => { assert!(res.breakpoints.len() == 3); @@ -129,17 +142,19 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Configuration Done request - let (result, exit_code) = server.handle_command(Command::ConfigurationDone); + let (result, exit_code) = server + .handle_command(&Command::ConfigurationDone) + .into_tuple(); assert!(matches!(result, Ok(ResponseBody::ConfigurationDone))); assert!(exit_code.is_none()); // Launch, should hit first breakpoint - let keep_running = server.handle_launch().expect("launched without error"); + let keep_running = server.launch().expect("launched without error"); assert!(keep_running); assert_stopped_breakpoint_event(output_capture.take_event(), 0); // Threads request - let (result, exit_code) = server.handle_command(Command::Threads); + let (result, exit_code) = server.handle_command(&Command::Threads).into_tuple(); match result.expect("threads result") { ResponseBody::Threads(res) => { assert_eq!(res.threads.len(), 1); @@ -149,7 +164,9 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Stack Trace request - let (result, exit_code) = server.handle_command(Command::StackTrace(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::StackTrace(Default::default())) + .into_tuple(); match result.expect("stack trace result") { ResponseBody::StackTrace(res) => { assert_eq!(res.stack_frames.len(), 1); @@ -159,7 +176,9 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Scopes request - let (result, exit_code) = server.handle_command(Command::Scopes(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Scopes(Default::default())) + .into_tuple(); match result.expect("scopes result") { ResponseBody::Scopes(res) => { assert_eq!(res.scopes.len(), 2); @@ -169,10 +188,12 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Variables request - registers - let (result, exit_code) = server.handle_command(Command::Variables(VariablesArguments { - variables_reference: REGISTERS_VARIABLE_REF, - ..Default::default() - })); + let (result, exit_code) = server + .handle_command(&Command::Variables(VariablesArguments { + variables_reference: REGISTERS_VARIABLE_REF, + ..Default::default() + })) + .into_tuple(); match result.expect("registers variables result") { ResponseBody::Variables(res) => { assert_eq!(res.variables.len(), 64); @@ -182,10 +203,12 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Variables request - VM instructions - let (result, exit_code) = server.handle_command(Command::Variables(VariablesArguments { - variables_reference: INSTRUCTIONS_VARIABLE_REF, - ..Default::default() - })); + let (result, exit_code) = server + .handle_command(&Command::Variables(VariablesArguments { + variables_reference: INSTRUCTIONS_VARIABLE_REF, + ..Default::default() + })) + .into_tuple(); match result.expect("instructions variables result") { ResponseBody::Variables(res) => { let expected = vec![ @@ -201,37 +224,49 @@ fn test_server_launch_mode() { assert!(exit_code.is_none()); // Next request - let (result, exit_code) = server.handle_command(Command::Next(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Next(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert!(exit_code.is_none()); assert_stopped_next_event(output_capture.take_event()); // Step In request - let (result, exit_code) = server.handle_command(Command::StepIn(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::StepIn(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert!(exit_code.is_none()); assert_not_supported_event(output_capture.take_event()); // Step Out request - let (result, exit_code) = server.handle_command(Command::StepOut(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::StepOut(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert!(exit_code.is_none()); assert_not_supported_event(output_capture.take_event()); // Continue request, should hit 2nd breakpoint - let (result, exit_code) = server.handle_command(Command::Continue(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Continue(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert!(exit_code.is_none()); assert_stopped_breakpoint_event(output_capture.take_event(), 1); // Continue request, should hit 3rd breakpoint - let (result, exit_code) = server.handle_command(Command::Continue(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Continue(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert!(exit_code.is_none()); assert_stopped_breakpoint_event(output_capture.take_event(), 2); // Continue request, should exit cleanly - let (result, exit_code) = server.handle_command(Command::Continue(Default::default())); + let (result, exit_code) = server + .handle_command(&Command::Continue(Default::default())) + .into_tuple(); assert!(result.is_ok()); assert_eq!(exit_code, Some(0)); diff --git a/forc-plugins/forc-doc/README.md b/forc-plugins/forc-doc/README.md index 90531e248cd..c82602aa225 100644 --- a/forc-plugins/forc-doc/README.md +++ b/forc-plugins/forc-doc/README.md @@ -78,7 +78,7 @@ $ cargo install --path forc-plugins/forc-doc Great! Let's check everything is working as intended. Try running `forc doc` on one of the test directories: ```sh -$ forc doc --manifest-path src/tests/data/impl_traits --open +$ forc doc --path src/tests/data/impl_traits --open ``` If it succeeded, you should be seeing the test docs in your browser. @@ -274,10 +274,10 @@ Now we can call this function anytime we need to generate a searchbar for our we Once you've made some changes, run the `forc doc` binary, passing it a path containing a `Forc.toml`: ```sh -cargo run -- --manifest-path path/to/manifest --open +cargo run -- --path path/to/manifest --open ``` -> **Tip:** VS Code user? Try the Live Server plugin to make viewing changes even easier. It will reload a webpage on updates, so you only need to rebuild the docs (`cargo run -- --manifest-path path/to/manifest`). Just right click the index file of docs produced by `forc doc` which can be found in the `out/doc` directory, and choose the option "open with Live Server". Voila! +> **Tip:** VS Code user? Try the Live Server plugin to make viewing changes even easier. It will reload a webpage on updates, so you only need to rebuild the docs (`cargo run -- --path path/to/manifest`). Just right click the index file of docs produced by `forc doc` which can be found in the `out/doc` directory, and choose the option "open with Live Server". Voila! [forc-reference]: https://fuellabs.github.io/sway/master/book/forc/index.html "forc reference" [manifest-reference]: https://fuellabs.github.io/sway/master/book/forc/manifest_reference.html "manifest reference" diff --git a/forc-plugins/forc-doc/src/cli.rs b/forc-plugins/forc-doc/src/cli.rs index 309bfb880d6..dff10298122 100644 --- a/forc-plugins/forc-doc/src/cli.rs +++ b/forc-plugins/forc-doc/src/cli.rs @@ -6,7 +6,7 @@ forc_util::cli_examples! { crate::cli::Command { [ Build the docs for a project in the current path => "forc doc"] [ Build the docs for a project in the current path and open it in the browser => "forc doc --open" ] - [ Build the docs for a project located in another path => "forc doc --manifest-path {path}" ] + [ Build the docs for a project located in another path => "forc doc --path {path}" ] [ Build the docs for the current project exporting private types => "forc doc --document-private-items" ] [ Build the docs offline without downloading any dependency from the network => "forc doc --offline" ] } @@ -20,10 +20,11 @@ forc_util::cli_examples! { version )] pub struct Command { - /// Path to the Forc.toml file. By default, forc-doc searches for the Forc.toml - /// file in the current directory or any parent directory. - #[clap(long)] - pub manifest_path: Option, + /// Path to the project. + /// + /// If not specified, current working directory will be used. + #[clap(short, long, alias = "manifest-path")] + pub path: Option, /// Include non-public items in the documentation. #[clap(long)] pub document_private_items: bool, diff --git a/forc-plugins/forc-doc/src/lib.rs b/forc-plugins/forc-doc/src/lib.rs index 4043cfde1f2..ffaf5a5dbe8 100644 --- a/forc-plugins/forc-doc/src/lib.rs +++ b/forc-plugins/forc-doc/src/lib.rs @@ -57,7 +57,7 @@ pub fn compile_html( get_doc_dir: &dyn Fn(&Command) -> String, ) -> Result<(PathBuf, Box)> { // get manifest directory - let dir = if let Some(ref path) = build_instructions.manifest_path { + let dir = if let Some(ref path) = build_instructions.path { PathBuf::from(path) } else { std::env::current_dir()? diff --git a/forc-plugins/forc-doc/src/tests/expects/impl_trait/mod.rs b/forc-plugins/forc-doc/src/tests/expects/impl_trait/mod.rs index 602b66f49d7..555954ce244 100644 --- a/forc-plugins/forc-doc/src/tests/expects/impl_trait/mod.rs +++ b/forc-plugins/forc-doc/src/tests/expects/impl_trait/mod.rs @@ -21,7 +21,7 @@ fn test_impl_traits_default() { let doc_dir_name: &str = "impl_traits_default"; let project_name = "impl_traits"; let command = Command { - manifest_path: Some(format!("{}/{}", DATA_DIR, project_name)), + path: Some(format!("{}/{}", DATA_DIR, project_name)), doc_path: Some(doc_dir_name.into()), ..Default::default() }; @@ -171,7 +171,7 @@ fn test_impl_traits_no_deps() { let doc_dir_name: &str = "impl_traits_no_deps"; let project_name: &str = "impl_traits_clone"; let command = Command { - manifest_path: Some(format!("{}/{}", DATA_DIR, project_name)), + path: Some(format!("{}/{}", DATA_DIR, project_name)), doc_path: Some(doc_dir_name.into()), no_deps: true, ..Default::default() diff --git a/forc-plugins/forc-doc/tests/lib.rs b/forc-plugins/forc-doc/tests/lib.rs index 9624b656a4f..996e095645b 100644 --- a/forc-plugins/forc-doc/tests/lib.rs +++ b/forc-plugins/forc-doc/tests/lib.rs @@ -5,10 +5,10 @@ use std::path::Path; fn builds_lib_std_docs() { let path = Path::new("./../../sway-lib-std"); let build_instructions = Command { - manifest_path: Some(path.to_str().unwrap().to_string()), + path: Some(path.to_str().unwrap().to_string()), ..Default::default() }; - println!("Building docs for {:?}", build_instructions.manifest_path); + println!("Building docs for {:?}", build_instructions.path); let res = compile_html(&build_instructions, &get_doc_dir); assert!(res.is_ok()); } diff --git a/forc-plugins/forc-fmt/src/main.rs b/forc-plugins/forc-fmt/src/main.rs index 6a818ff245a..331c3e51373 100644 --- a/forc-plugins/forc-fmt/src/main.rs +++ b/forc-plugins/forc-fmt/src/main.rs @@ -15,7 +15,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use sway_core::{BuildConfig, BuildTarget}; use sway_utils::{constants, find_parent_manifest_dir, get_sway_files, is_sway_file}; use swayfmt::Formatter; use taplo::formatter as taplo_fmt; @@ -46,7 +45,9 @@ pub struct App { /// - Exits with `1` and prints a diff if formatting is required. #[clap(short, long)] pub check: bool, - /// Path to the project, if not specified, current working directory will be used. + /// Path to the project. + /// + /// If not specified, current working directory will be used. #[clap(short, long)] pub path: Option, #[clap(short, long)] @@ -77,13 +78,8 @@ fn run() -> Result<()> { if let Some(f) = app.file.as_ref() { let file_path = &PathBuf::from(f); - // If we're formatting a single file, find the nearest manifest if within a project. - // Otherwise, we simply provide 'None' to format_file(). - let manifest_file = find_parent_manifest_dir(file_path) - .map(|path| path.join(constants::MANIFEST_FILE_NAME)); - if is_sway_file(file_path) { - format_file(&app, file_path.to_path_buf(), manifest_file, &mut formatter)?; + format_file(&app, file_path.to_path_buf(), &mut formatter)?; return Ok(()); } @@ -142,12 +138,7 @@ fn get_sway_dirs(workspace_dir: PathBuf) -> Vec { /// - Ok(true) if executed successfully and formatted, /// - Ok(false) if executed successfully and not formatted, /// - Err if it fails to execute at all. -fn format_file( - app: &App, - file: PathBuf, - manifest_file: Option, - formatter: &mut Formatter, -) -> Result { +fn format_file(app: &App, file: PathBuf, formatter: &mut Formatter) -> Result { let file = file.canonicalize()?; if is_file_dirty(&file) { bail!( @@ -160,14 +151,7 @@ fn format_file( if let Ok(file_content) = fs::read_to_string(&file) { let mut edited = false; let file_content: Arc = Arc::from(file_content); - let build_config = manifest_file.map(|f| { - BuildConfig::root_from_file_name_and_manifest_path( - file.clone(), - f, - BuildTarget::default(), - ) - }); - match Formatter::format(formatter, file_content.clone(), build_config.as_ref()) { + match Formatter::format(formatter, file_content.clone()) { Ok(formatted_content) => { if app.check { if *file_content != formatted_content { @@ -213,12 +197,7 @@ fn format_workspace_at_dir(app: &App, workspace: &WorkspaceManifestFile, dir: &P for entry in read_dir.filter_map(|res| res.ok()) { let path = entry.path(); if is_sway_file(&path) { - format_file( - app, - path, - Some(workspace.dir().to_path_buf()), - &mut formatter, - )?; + format_file(app, path, &mut formatter)?; } } } @@ -295,7 +274,7 @@ fn format_pkg_at_dir(app: &App, dir: &Path, formatter: &mut Formatter) -> Result let mut contains_edits = false; for file in files { - contains_edits |= format_file(app, file, Some(manifest_file.clone()), formatter)?; + contains_edits |= format_file(app, file, formatter)?; } // format manifest using taplo formatter contains_edits |= format_manifest(app, manifest_file)?; diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 3436261af94..a0423caba12 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -5,11 +5,14 @@ use crate::TEST_METADATA_SEED; use forc_pkg::PkgTestEntry; use fuel_tx::{self as tx, output::contract::Contract, Chargeable, Finalizable}; use fuel_vm::error::InterpreterError; +use fuel_vm::fuel_asm; +use fuel_vm::prelude::Instruction; +use fuel_vm::prelude::RegId; use fuel_vm::{ self as vm, checked_transaction::builder::TransactionBuilderExt, interpreter::{Interpreter, NotSupportedEcal}, - prelude::{Instruction, SecretKey}, + prelude::SecretKey, storage::MemoryStorage, }; use rand::{Rng, SeedableRng}; @@ -27,6 +30,8 @@ pub struct TestExecutor { pub tx: vm::checked_transaction::Ready, pub test_entry: PkgTestEntry, pub name: String, + pub jump_instruction_index: usize, + pub relative_jump_in_bytes: u32, } /// The result of executing a test with breakpoints enabled. @@ -41,15 +46,16 @@ pub enum DebugResult { impl TestExecutor { pub fn build( bytecode: &[u8], - test_offset: u32, + test_instruction_index: u32, test_setup: TestSetup, test_entry: &PkgTestEntry, name: String, ) -> anyhow::Result { let storage = test_setup.storage().clone(); - // Patch the bytecode to jump to the relevant test. - let bytecode = patch_test_bytecode(bytecode, test_offset).into_owned(); + // Find the instruction which we will jump into the + // specified test + let jump_instruction_index = find_jump_instruction_index(bytecode); // Create a transaction to execute the test function. let script_input_data = vec![]; @@ -68,7 +74,7 @@ impl TestExecutor { let block_height = (u32::MAX >> 1).into(); let gas_price = 0; - let mut tx_builder = tx::TransactionBuilder::script(bytecode, script_input_data); + let mut tx_builder = tx::TransactionBuilder::script(bytecode.to_vec(), script_input_data); let params = maxed_consensus_params(); @@ -126,23 +132,72 @@ impl TestExecutor { tx, test_entry: test_entry.clone(), name, + jump_instruction_index, + relative_jump_in_bytes: (test_instruction_index - jump_instruction_index as u32) + * Instruction::SIZE as u32, }) } + // single-step until the jump-to-test instruction, then + // jump into the first instruction of the test + fn single_step_until_test(&mut self) -> ProgramState { + let jump_pc = (self.jump_instruction_index * Instruction::SIZE) as u64; + + let old_single_stepping = self.interpreter.single_stepping(); + self.interpreter.set_single_stepping(true); + let mut state = { + let transition = self.interpreter.transact(self.tx.clone()); + Ok(*transition.unwrap().state()) + }; + + loop { + match state { + // if the VM fails, we interpret as a revert + Err(_) => { + break ProgramState::Revert(0); + } + Ok( + state @ ProgramState::Return(_) + | state @ ProgramState::ReturnData(_) + | state @ ProgramState::Revert(_), + ) => break state, + Ok( + s @ ProgramState::RunProgram(eval) | s @ ProgramState::VerifyPredicate(eval), + ) => { + // time to jump into the specified test + if let Some(b) = eval.breakpoint() { + if b.pc() == jump_pc { + self.interpreter.registers_mut()[RegId::PC] += + self.relative_jump_in_bytes as u64; + self.interpreter.set_single_stepping(old_single_stepping); + break s; + } + } + + state = self.interpreter.resume(); + } + } + } + } + /// Execute the test with breakpoints enabled. pub fn start_debugging(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - let transition = self + + let _ = self.single_step_until_test(); + let state = self .interpreter - .transact(self.tx.clone()) - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - let state = *transition.state(); + .resume() + .map_err(|err: InterpreterError<_>| { + anyhow::anyhow!("VM failed to resume. {:?}", err) + })?; if let ProgramState::RunProgram(DebugEval::Breakpoint(breakpoint)) = state { // A breakpoint was hit, so we tell the client to stop. return Ok(DebugResult::Breakpoint(breakpoint.pc())); } + let duration = start.elapsed(); - let (gas_used, logs) = Self::get_gas_and_receipts(transition.receipts().to_vec())?; + let (gas_used, logs) = Self::get_gas_and_receipts(self.interpreter.receipts().to_vec())?; let span = self.test_entry.span.clone(); let file_path = self.test_entry.file_path.clone(); let condition = self.test_entry.pass_condition.clone(); @@ -192,14 +247,27 @@ impl TestExecutor { pub fn execute(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - let transition = self - .interpreter - .transact(self.tx.clone()) - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - let state = *transition.state(); + + let mut state = Ok(self.single_step_until_test()); + + // Run test until its end + loop { + match state { + Err(_) => { + state = Ok(ProgramState::Revert(0)); + break; + } + Ok( + ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_), + ) => break, + Ok(ProgramState::RunProgram(_) | ProgramState::VerifyPredicate(_)) => { + state = self.interpreter.resume(); + } + } + } let duration = start.elapsed(); - let (gas_used, logs) = Self::get_gas_and_receipts(transition.receipts().to_vec())?; + let (gas_used, logs) = Self::get_gas_and_receipts(self.interpreter.receipts().to_vec())?; let span = self.test_entry.span.clone(); let file_path = self.test_entry.file_path.clone(); let condition = self.test_entry.pass_condition.clone(); @@ -209,7 +277,7 @@ impl TestExecutor { file_path, duration, span, - state, + state: state.unwrap(), condition, logs, gas_used, @@ -237,39 +305,25 @@ impl TestExecutor { } } -/// Given some bytecode and an instruction offset for some test's desired entry point, patch the -/// bytecode with a `JI` (jump) instruction to jump to the desired test. -/// -/// We want to splice in the `JI` only after the initial data section setup is complete, and only -/// if the entry point doesn't begin exactly after the data section setup. -/// -/// The following is how the beginning of the bytecode is laid out: -/// -/// ```ignore -/// [0] ji i4 ; Jumps to the data section setup. -/// [1] noop -/// [2] DATA_SECTION_OFFSET[0..32] -/// [3] DATA_SECTION_OFFSET[32..64] -/// [4] lw $ds $is 1 ; The data section setup, i.e. where the first ji lands. -/// [5] add $$ds $$ds $is -/// [6] ; This is where we want to jump from to our test code! -/// ``` -fn patch_test_bytecode(bytecode: &[u8], test_offset: u32) -> std::borrow::Cow<[u8]> { - // TODO: Standardize this or add metadata to bytecode. - const PROGRAM_START_INST_OFFSET: u32 = 6; - const PROGRAM_START_BYTE_OFFSET: usize = PROGRAM_START_INST_OFFSET as usize * Instruction::SIZE; - - // If our desired entry point is the program start, no need to jump. - if test_offset == PROGRAM_START_INST_OFFSET { - return std::borrow::Cow::Borrowed(bytecode); - } +fn find_jump_instruction_index(bytecode: &[u8]) -> usize { + // Search first `move $$locbase $sp` + // This will be `__entry` for script/predicate/contract using encoding v1; + // `main` for script/predicate using encoding v0; + // or the first function for libraries + // MOVE R59 $sp ;; [26, 236, 80, 0] + let a = vm::fuel_asm::op::move_(59, fuel_asm::RegId::SP).to_bytes(); - // Create the jump instruction and splice it into the bytecode. - let ji = vm::fuel_asm::op::ji(test_offset); - let ji_bytes = ji.to_bytes(); - let start = PROGRAM_START_BYTE_OFFSET; - let end = start + ji_bytes.len(); - let mut patched = bytecode.to_vec(); - patched.splice(start..end, ji_bytes); - std::borrow::Cow::Owned(patched) + // for contracts using encoding v0 + // search the first `lw $r0 $fp i73` + // which is the start of the fn selector + // LW $writable $fp 0x49 ;; [93, 64, 96, 73] + let b = vm::fuel_asm::op::lw(fuel_asm::RegId::WRITABLE, fuel_asm::RegId::FP, 73).to_bytes(); + + bytecode + .chunks(Instruction::SIZE) + .position(|instruction| { + let instruction: [u8; 4] = instruction.try_into().unwrap(); + instruction == a || instruction == b + }) + .unwrap() } diff --git a/forc-util/Cargo.toml b/forc-util/Cargo.toml index b2b5cdded43..a7bd7364a7f 100644 --- a/forc-util/Cargo.toml +++ b/forc-util/Cargo.toml @@ -16,6 +16,7 @@ clap = { workspace = true, features = ["cargo", "derive", "env"] } dirs.workspace = true fd-lock.workspace = true forc-tracing.workspace = true +fuel-asm.workspace = true fuel-tx = { workspace = true, optional = true } hex.workspace = true paste.workspace = true @@ -23,6 +24,7 @@ regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true serial_test.workspace = true +sha2.workspace = true sway-core.workspace = true sway-error.workspace = true sway-types.workspace = true diff --git a/forc-util/src/bytecode.rs b/forc-util/src/bytecode.rs new file mode 100644 index 00000000000..7f73f39969e --- /dev/null +++ b/forc-util/src/bytecode.rs @@ -0,0 +1,174 @@ +use anyhow::anyhow; +use sha2::{Digest, Sha256}; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::Path; + +// The index of the beginning of the half-word (4 bytes) that contains the configurables section offset. +const CONFIGURABLES_OFFSET_INSTR_LO: usize = 4; +// The index of the end of the half-word (4 bytes) that contains the configurables section offset. +const CONFIGURABLES_OFFSET_INSTR_HI: usize = 5; +// The count of the beginning half-words that contain the configurables section offset. +const CONFIGURABLES_OFFSET_PREAMBLE: usize = CONFIGURABLES_OFFSET_INSTR_HI + 1; + +/// A tuple of an instruction and its corresponding bytes. Useful when needing to access the raw bytes +/// of an instruction that is parsed as [fuel_asm::InvalidOpcode], such as metadata in the preamble. +pub type InstructionWithBytes = ( + Result, + Vec, +); + +/// An iterator over each [fuel_asm::Instruction] or [fuel_asm::InvalidOpcode] with its corresponding bytes. +pub struct InstructionWithBytesIterator { + buf_reader: BufReader, +} + +impl InstructionWithBytesIterator { + /// Return a new iterator for each instruction parsed from raw bytes. + pub fn new(buf_reader: BufReader) -> Self { + InstructionWithBytesIterator { buf_reader } + } +} + +impl Iterator for InstructionWithBytesIterator { + type Item = InstructionWithBytes; + + fn next(&mut self) -> Option { + let mut buffer = [0; fuel_asm::Instruction::SIZE]; + // Read the next instruction into the buffer + match self.buf_reader.read_exact(&mut buffer) { + Ok(_) => fuel_asm::from_bytes(buffer) + .next() + .map(|inst| (inst, buffer.to_vec())), + Err(_) => None, + } + } +} + +/// Parses a bytecode file into an iterator of instructions and their corresponding bytes. +pub fn parse_bytecode_to_instructions

(path: P) -> anyhow::Result +where + P: AsRef + Clone, +{ + let f = File::open(path.clone()) + .map_err(|_| anyhow!("{}: file not found", path.as_ref().to_string_lossy()))?; + let buf_reader = BufReader::new(f); + + Ok(InstructionWithBytesIterator::new(buf_reader)) +} + +/// Gets the bytecode ID from a bytecode file. The bytecode ID is the hash of the bytecode after removing the +/// condigurables section, if any. +pub fn get_bytecode_id

(path: P) -> anyhow::Result +where + P: AsRef + Clone, +{ + let mut instructions = parse_bytecode_to_instructions(path.clone())?; + + // Collect the first six instructions into a temporary vector + let mut first_six_instructions = Vec::with_capacity(CONFIGURABLES_OFFSET_PREAMBLE); + for _ in 0..CONFIGURABLES_OFFSET_PREAMBLE { + if let Some(instruction) = instructions.next() { + first_six_instructions.push(instruction); + } else { + return Err(anyhow!("Incomplete bytecode")); + } + } + + let (lo_instr, low_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_LO]; + let (hi_instr, hi_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_HI]; + + if let Err(fuel_asm::InvalidOpcode) = lo_instr { + if let Err(fuel_asm::InvalidOpcode) = hi_instr { + // Now assemble the configurables offset. + let configurables_offset = usize::from_be_bytes([ + low_raw[0], low_raw[1], low_raw[2], low_raw[3], hi_raw[0], hi_raw[1], hi_raw[2], + hi_raw[3], + ]); + + // Hash the first six instructions + let mut hasher = Sha256::new(); + for (_, raw) in first_six_instructions { + hasher.update(raw); + } + + // Continue hashing the remaining instructions up to the configurables section offset. + instructions + .take( + configurables_offset / fuel_asm::Instruction::SIZE + - CONFIGURABLES_OFFSET_PREAMBLE, + ) // Minus 6 because we already hashed the first six + .for_each(|(_, raw)| { + hasher.update(raw); + }); + + let hash_result = hasher.finalize(); + let bytecode_id = format!("{:x}", hash_result); + return Ok(bytecode_id); + } + } + + Err(anyhow!("Configurables section offset not found")) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_bytecode_id_happy() { + // These binary files were generated from `examples/configurable_constants` and `examples/counter` + // using `forc build` and `forc build --release` respectively. + let bytecode_id: String = + get_bytecode_id("tests/fixtures/bytecode/debug-counter.bin").expect("bytecode id"); + assert_eq!( + bytecode_id, + "e65aa988cae1041b64dc2d85e496eed0e8a1d8105133bd313c17645a1859d53b".to_string() + ); + + let bytecode_id = + get_bytecode_id("tests/fixtures/bytecode/release-counter.bin").expect("bytecode id"); + assert_eq!( + bytecode_id, + "42ae8352cbc892d7c7621f1d6fb42b072a08ba5968508d49f54991668d4ea141".to_string() + ); + + let bytecode_id = + get_bytecode_id("tests/fixtures/bytecode/debug-configurable_constants.bin") + .expect("bytecode id"); + assert_eq!( + bytecode_id, + "babc3d9dcac8d48dee1e5aeb3340ff098d3c1ab8b0a28341d9291d8ff757199e".to_string() + ); + + let bytecode_id = + get_bytecode_id("tests/fixtures/bytecode/release-configurable_constants.bin") + .expect("bytecode id"); + assert_eq!( + bytecode_id, + "2adfb515b66763fd29391bdba012921d045a0be83d89be5492bcaacc429695e9".to_string() + ); + } + + #[test] + fn test_get_bytecode_id_missing_configurable_offset() { + // This bytecode file was generated from `examples/configurable_constants` using an older version of the + // compiler that did not include the configurables section offset in the preamble. + let result = get_bytecode_id( + "tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin", + ); + assert_eq!( + result.unwrap_err().to_string().as_str(), + "Configurables section offset not found" + ); + } + + #[test] + fn test_get_bytecode_id_bad_path() { + let result = get_bytecode_id("tests/fixtures/bytecode/blahblahblahblah.bin"); + assert_eq!( + result.unwrap_err().to_string().as_str(), + "tests/fixtures/bytecode/blahblahblahblah.bin: file not found" + ); + } +} diff --git a/forc-util/src/lib.rs b/forc-util/src/lib.rs index 003d50898ad..03775dcf8f1 100644 --- a/forc-util/src/lib.rs +++ b/forc-util/src/lib.rs @@ -23,6 +23,7 @@ use sway_error::{ use sway_types::{LineCol, LineColRange, SourceEngine, Span}; use sway_utils::constants; +pub mod bytecode; pub mod fs_locking; pub mod restricted; diff --git a/forc-util/tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin b/forc-util/tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin new file mode 100644 index 00000000000..cedfa0b99ae Binary files /dev/null and b/forc-util/tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin differ diff --git a/forc-util/tests/fixtures/bytecode/debug-configurable_constants.bin b/forc-util/tests/fixtures/bytecode/debug-configurable_constants.bin new file mode 100644 index 00000000000..4490555350d Binary files /dev/null and b/forc-util/tests/fixtures/bytecode/debug-configurable_constants.bin differ diff --git a/forc-util/tests/fixtures/bytecode/debug-counter.bin b/forc-util/tests/fixtures/bytecode/debug-counter.bin new file mode 100644 index 00000000000..b19a04b03b6 Binary files /dev/null and b/forc-util/tests/fixtures/bytecode/debug-counter.bin differ diff --git a/forc-util/tests/fixtures/bytecode/release-configurable_constants.bin b/forc-util/tests/fixtures/bytecode/release-configurable_constants.bin new file mode 100644 index 00000000000..2538c021e48 Binary files /dev/null and b/forc-util/tests/fixtures/bytecode/release-configurable_constants.bin differ diff --git a/forc-util/tests/fixtures/bytecode/release-counter.bin b/forc-util/tests/fixtures/bytecode/release-counter.bin new file mode 100644 index 00000000000..9f504b1c687 Binary files /dev/null and b/forc-util/tests/fixtures/bytecode/release-counter.bin differ diff --git a/forc/src/cli/commands/parse_bytecode.rs b/forc/src/cli/commands/parse_bytecode.rs index f4a30c82d6d..0ffa6f0ba4a 100644 --- a/forc/src/cli/commands/parse_bytecode.rs +++ b/forc/src/cli/commands/parse_bytecode.rs @@ -1,8 +1,6 @@ -use anyhow::anyhow; use clap::Parser; +use forc_util::bytecode::parse_bytecode_to_instructions; use forc_util::ForcResult; -use std::fs::{self, File}; -use std::io::Read; use term_table::row::Row; use term_table::table_cell::{Alignment, TableCell}; use tracing::info; @@ -21,15 +19,7 @@ pub(crate) struct Command { } pub(crate) fn exec(command: Command) -> ForcResult<()> { - let mut f = File::open(&command.file_path) - .map_err(|_| anyhow!("{}: file not found", command.file_path))?; - let metadata = fs::metadata(&command.file_path) - .map_err(|_| anyhow!("{}: file not found", command.file_path))?; - let mut buffer = vec![0; metadata.len() as usize]; - f.read_exact(&mut buffer).expect("buffer overflow"); - - let instructions = fuel_asm::from_bytes(buffer.iter().cloned()) - .zip(buffer.chunks(fuel_asm::Instruction::SIZE)); + let instructions = parse_bytecode_to_instructions(&command.file_path)?; let mut table = term_table::Table::new(); table.separate_rows = false; @@ -59,6 +49,14 @@ pub(crate) fn exec(command: Command) -> ForcResult<()> { parsed_raw ) } + Err(fuel_asm::InvalidOpcode) if word_ix == 4 || word_ix == 5 => { + let parsed_raw = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]); + format!( + "configurables offset {} ({})", + if word_ix == 4 { "lo" } else { "hi" }, + parsed_raw + ) + } Ok(_) | Err(fuel_asm::InvalidOpcode) => "".into(), }; table.add_row(Row::new(vec![ diff --git a/forc/tests/cli_integration.rs b/forc/tests/cli_integration.rs index 1f1248a2205..422e9f18f9c 100644 --- a/forc/tests/cli_integration.rs +++ b/forc/tests/cli_integration.rs @@ -49,10 +49,10 @@ fn test_forc_test_raw_logs() -> Result<(), rexpect::error::Error> { // Assert that the output is correct process.exp_string(" test test_log_4")?; process.exp_string("Raw logs:")?; - process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12664,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; + process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12672,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; process.exp_string(" test test_log_2")?; process.exp_string("Raw logs:")?; - process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12664,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; + process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12672,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; process.process.exit()?; Ok(()) @@ -74,11 +74,11 @@ fn test_forc_test_both_logs() -> Result<(), rexpect::error::Error> { process.exp_string(" test test_log_4")?; process.exp_string("Decoded log value: 4, log rb: 1515152261580153489")?; process.exp_string("Raw logs:")?; - process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12664,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; + process.exp_string(r#"[{"LogData":{"data":"0000000000000004","digest":"8005f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12672,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; process.exp_string(" test test_log_2")?; process.exp_string("Decoded log value: 2, log rb: 1515152261580153489")?; process.exp_string("Raw logs:")?; - process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12664,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; + process.exp_string(r#"[{"LogData":{"data":"0000000000000002","digest":"cd04a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f70","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":12672,"ptr":67107840,"ra":0,"rb":1515152261580153489}}]"#)?; process.process.exit()?; Ok(()) } diff --git a/sway-ast/src/expr/mod.rs b/sway-ast/src/expr/mod.rs index f27798c2d7f..52999a63dcf 100644 --- a/sway-ast/src/expr/mod.rs +++ b/sway-ast/src/expr/mod.rs @@ -306,6 +306,18 @@ impl ReassignmentOpVariant { ReassignmentOpVariant::ShrEquals => "rsh", } } + + pub fn as_str(&self) -> &'static str { + match self { + ReassignmentOpVariant::Equals => EqToken::AS_STR, + ReassignmentOpVariant::AddEquals => AddEqToken::AS_STR, + ReassignmentOpVariant::SubEquals => SubEqToken::AS_STR, + ReassignmentOpVariant::MulEquals => StarEqToken::AS_STR, + ReassignmentOpVariant::DivEquals => DivEqToken::AS_STR, + ReassignmentOpVariant::ShlEquals => ShlEqToken::AS_STR, + ReassignmentOpVariant::ShrEquals => ShrEqToken::AS_STR, + } + } } #[derive(Clone, Debug, Serialize)] diff --git a/sway-ast/src/expr/op_code.rs b/sway-ast/src/expr/op_code.rs index acfc54411eb..83c6d319743 100644 --- a/sway-ast/src/expr/op_code.rs +++ b/sway-ast/src/expr/op_code.rs @@ -123,6 +123,14 @@ macro_rules! define_op_codes ( } } + pub fn op_code_as_str(&self) -> &'static str { + match self { + $(Instruction::$op_name { .. } => { + $s + },)* + } + } + #[allow(clippy::vec_init_then_push)] pub fn register_arg_idents(&self) -> Vec { match self { diff --git a/sway-ast/src/intrinsics.rs b/sway-ast/src/intrinsics.rs index 30dea17436d..d14c4e1b85a 100644 --- a/sway-ast/src/intrinsics.rs +++ b/sway-ast/src/intrinsics.rs @@ -1,6 +1,7 @@ +use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Eq, PartialEq, Debug, Clone, Hash)] +#[derive(Eq, PartialEq, Debug, Clone, Hash, Serialize, Deserialize)] pub enum Intrinsic { IsReferenceType, SizeOfType, diff --git a/sway-ast/src/keywords.rs b/sway-ast/src/keywords.rs index 0e3ea802da0..236b07cb8aa 100644 --- a/sway-ast/src/keywords.rs +++ b/sway-ast/src/keywords.rs @@ -98,10 +98,13 @@ pub trait Token: Spanned + Sized { /// Punctuations that will not follow the token. const NOT_FOLLOWED_BY: &'static [PunctKind]; + + /// What the string representation of the token is when lexing. + const AS_STR: &'static str; } macro_rules! define_token ( - ($ty_name:ident, $description:literal, [$($punct_kinds:ident),*], [$($not_followed_by:ident),*]) => { + ($ty_name:ident, $description:literal, $as_str:literal, [$($punct_kinds:ident),*], [$($not_followed_by:ident),*]) => { #[derive(Clone, Debug, Serialize)] pub struct $ty_name { span: Span, @@ -132,6 +135,7 @@ macro_rules! define_token ( const PUNCT_KINDS: &'static [PunctKind] = &[$(PunctKind::$punct_kinds,)*]; const NOT_FOLLOWED_BY: &'static [PunctKind] = &[$(PunctKind::$not_followed_by,)*]; + const AS_STR: &'static str = $as_str; } impl From<$ty_name> for Ident { @@ -142,93 +146,121 @@ macro_rules! define_token ( }; ); -define_token!(SemicolonToken, "a semicolon", [Semicolon], []); +define_token!(SemicolonToken, "a semicolon", ";", [Semicolon], []); define_token!( ForwardSlashToken, "a forward slash", + "/", [ForwardSlash], [Equals] ); define_token!( DoubleColonToken, "a double colon (::)", + "::", [Colon, Colon], [Colon] ); -define_token!(StarToken, "an asterisk (*)", [Star], [Equals]); -define_token!(DoubleStarToken, "`**`", [Star, Star], []); -define_token!(CommaToken, "a comma", [Comma], []); -define_token!(ColonToken, "a colon", [Colon], [Colon]); +define_token!(StarToken, "an asterisk (*)", "*", [Star], [Equals]); +define_token!(DoubleStarToken, "`**`", "**", [Star, Star], []); +define_token!(CommaToken, "a comma", ",", [Comma], []); +define_token!(ColonToken, "a colon", ":", [Colon], [Colon]); define_token!( RightArrowToken, "`->`", + "->", [Sub, GreaterThan], [GreaterThan, Equals] ); -define_token!(LessThanToken, "`<`", [LessThan], [LessThan, Equals]); +define_token!(LessThanToken, "`<`", "<", [LessThan], [LessThan, Equals]); define_token!( GreaterThanToken, "`>`", + ">", [GreaterThan], [GreaterThan, Equals] ); -define_token!(OpenAngleBracketToken, "`<`", [LessThan], []); -define_token!(CloseAngleBracketToken, "`>`", [GreaterThan], []); -define_token!(EqToken, "`=`", [Equals], [GreaterThan, Equals]); -define_token!(AddEqToken, "`+=`", [Add, Equals], []); -define_token!(SubEqToken, "`-=`", [Sub, Equals], []); -define_token!(StarEqToken, "`*=`", [Star, Equals], []); -define_token!(DivEqToken, "`/=`", [ForwardSlash, Equals], []); -define_token!(ShlEqToken, "`<<=`", [LessThan, LessThan, Equals], []); -define_token!(ShrEqToken, "`>>=`", [GreaterThan, GreaterThan, Equals], []); +define_token!(OpenAngleBracketToken, "`<`", "<", [LessThan], []); +define_token!(CloseAngleBracketToken, "`>`", ">", [GreaterThan], []); +define_token!(EqToken, "`=`", "=", [Equals], [GreaterThan, Equals]); +define_token!(AddEqToken, "`+=`", "+=", [Add, Equals], []); +define_token!(SubEqToken, "`-=`", "-=", [Sub, Equals], []); +define_token!(StarEqToken, "`*=`", "*=", [Star, Equals], []); +define_token!(DivEqToken, "`/=`", "/=", [ForwardSlash, Equals], []); +define_token!(ShlEqToken, "`<<=`", "<<=", [LessThan, LessThan, Equals], []); +define_token!( + ShrEqToken, + "`>>=`", + ">>=", + [GreaterThan, GreaterThan, Equals], + [] +); define_token!( FatRightArrowToken, "`=>`", + "=>", [Equals, GreaterThan], [GreaterThan, Equals] ); -define_token!(DotToken, "`.`", [Dot], []); -define_token!(DoubleDotToken, "`..`", [Dot, Dot], [Dot]); -define_token!(BangToken, "`!`", [Bang], [Equals]); -define_token!(PercentToken, "`%`", [Percent], []); -define_token!(AddToken, "`+`", [Add], [Equals]); -define_token!(SubToken, "`-`", [Sub], [Equals]); +define_token!(DotToken, "`.`", ".", [Dot], []); +define_token!(DoubleDotToken, "`..`", "..", [Dot, Dot], [Dot]); +define_token!(BangToken, "`!`", "!", [Bang], [Equals]); +define_token!(PercentToken, "`%`", "%", [Percent], []); +define_token!(AddToken, "`+`", "+", [Add], [Equals]); +define_token!(SubToken, "`-`", "-", [Sub], [Equals]); define_token!( ShrToken, "`>>`", + ">>", [GreaterThan, GreaterThan], [GreaterThan, Equals] ); -define_token!(ShlToken, "`<<`", [LessThan, LessThan], [LessThan, Equals]); -define_token!(AmpersandToken, "`&`", [Ampersand], [Ampersand]); -define_token!(CaretToken, "`^`", [Caret], []); -define_token!(PipeToken, "`|`", [Pipe], [Pipe]); +define_token!( + ShlToken, + "`<<`", + "<<", + [LessThan, LessThan], + [LessThan, Equals] +); +define_token!(AmpersandToken, "`&`", "&", [Ampersand], [Ampersand]); +define_token!(CaretToken, "`^`", "^", [Caret], []); +define_token!(PipeToken, "`|`", "|", [Pipe], [Pipe]); define_token!( DoubleEqToken, "`==`", + "==", [Equals, Equals], [Equals, GreaterThan] ); -define_token!(BangEqToken, "`!=`", [Bang, Equals], [Equals, GreaterThan]); +define_token!( + BangEqToken, + "`!=`", + "!=", + [Bang, Equals], + [Equals, GreaterThan] +); define_token!( GreaterThanEqToken, "`>=`", + ">=", [GreaterThan, Equals], [Equals, GreaterThan] ); define_token!( LessThanEqToken, "`<=`", + "<=", [LessThan, Equals], [Equals, GreaterThan] ); define_token!( DoubleAmpersandToken, "`&&`", + "&&", [Ampersand, Ampersand], [Ampersand] ); -define_token!(DoublePipeToken, "`||`", [Pipe, Pipe], [Pipe]); -define_token!(UnderscoreToken, "`_`", [Underscore], [Underscore]); -define_token!(HashToken, "`#`", [Sharp], []); -define_token!(HashBangToken, "`#!`", [Sharp, Bang], []); +define_token!(DoublePipeToken, "`||`", "||", [Pipe, Pipe], [Pipe]); +define_token!(UnderscoreToken, "`_`", "_", [Underscore], [Underscore]); +define_token!(HashToken, "`#`", "#", [Sharp], []); +define_token!(HashBangToken, "`#!`", "#!", [Sharp, Bang], []); diff --git a/sway-ast/src/literal.rs b/sway-ast/src/literal.rs index 1cede566e18..0b175235506 100644 --- a/sway-ast/src/literal.rs +++ b/sway-ast/src/literal.rs @@ -1,25 +1,25 @@ use crate::priv_prelude::*; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub struct LitString { pub span: Span, pub parsed: String, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub struct LitChar { pub span: Span, pub parsed: char, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub struct LitInt { pub span: Span, pub parsed: BigUint, pub ty_opt: Option<(LitIntType, Span)>, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub enum LitIntType { U8, U16, @@ -32,13 +32,13 @@ pub enum LitIntType { I64, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub struct LitBool { pub span: Span, pub kind: LitBoolType, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub enum LitBoolType { True, False, @@ -53,7 +53,7 @@ impl From for bool { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Serialize, Deserialize)] pub enum Literal { String(LitString), Char(LitChar), diff --git a/sway-ast/src/path.rs b/sway-ast/src/path.rs index b472426c8d9..c7be1225e87 100644 --- a/sway-ast/src/path.rs +++ b/sway-ast/src/path.rs @@ -110,5 +110,5 @@ impl Spanned for PathTypeSegment { #[derive(Clone, Debug, Serialize)] pub struct QualifiedPathRoot { pub ty: Box, - pub as_trait: Option<(AsToken, Box)>, + pub as_trait: (AsToken, Box), } diff --git a/sway-ast/src/priv_prelude.rs b/sway-ast/src/priv_prelude.rs index b799d69f37d..9da93d80994 100644 --- a/sway-ast/src/priv_prelude.rs +++ b/sway-ast/src/priv_prelude.rs @@ -35,7 +35,7 @@ pub use { }, extension_trait::extension_trait, num_bigint::BigUint, - serde::Serialize, + serde::{Deserialize, Serialize}, sway_types::{ ast::{Delimiter, PunctKind}, Ident, Span, Spanned, diff --git a/sway-ast/src/punctuated.rs b/sway-ast/src/punctuated.rs index 9aa88c64495..af6cf423f89 100644 --- a/sway-ast/src/punctuated.rs +++ b/sway-ast/src/punctuated.rs @@ -20,6 +20,19 @@ impl Punctuated { final_value_opt: Some(Box::new(value)), } } + + /// Returns true if the [Punctuated] ends with the punctuation token. + /// E.g., `fn fun(x: u64, y: u64,)`. + pub fn has_trailing_punctuation(&self) -> bool { + !self.value_separator_pairs.is_empty() && self.final_value_opt.is_none() + } + + /// Returns true if the [Punctuated] has neither value separator pairs, + /// nor the final value. + /// E.g., `fn fun()`. + pub fn is_empty(&self) -> bool { + self.value_separator_pairs.is_empty() && self.final_value_opt.is_none() + } } impl IntoIterator for Punctuated { diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 5e2e5d54536..71ca263d962 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -23,7 +23,7 @@ graph-cycles.workspace = true hashbrown.workspace = true hex = { workspace = true, optional = true } im.workspace = true -indexmap.workspace = true +indexmap = { workspace = true, features = ["serde"] } itertools.workspace = true lazy_static.workspace = true object = { workspace = true, features = ["write"] } diff --git a/sway-core/src/asm_generation/finalized_asm.rs b/sway-core/src/asm_generation/finalized_asm.rs index 1bb66535693..936127d7554 100644 --- a/sway-core/src/asm_generation/finalized_asm.rs +++ b/sway-core/src/asm_generation/finalized_asm.rs @@ -3,8 +3,8 @@ use super::{ fuel::{checks, data_section::DataSection}, ProgramABI, ProgramKind, }; -use crate::asm_generation::fuel::data_section::{DataId, Datum, Entry}; -use crate::asm_lang::allocated_ops::{AllocatedOp, AllocatedOpcode}; +use crate::asm_generation::fuel::data_section::{Datum, Entry, EntryName}; +use crate::asm_lang::allocated_ops::{AllocatedOp, AllocatedOpcode, FuelAsmData}; use crate::decl_engine::DeclRefFunction; use crate::source_map::SourceMap; use crate::BuildConfig; @@ -16,7 +16,6 @@ use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::span::Span; use sway_types::SourceEngine; -use either::Either; use std::{collections::BTreeMap, fmt}; /// Represents an ASM set which has had register allocation, jump elimination, and optimization @@ -130,6 +129,8 @@ fn to_bytecode_mut( { 8 } + AllocatedOpcode::AddrDataId(_, _data_id) => 8, + AllocatedOpcode::ConfigurablesOffsetPlaceholder => 8, AllocatedOpcode::DataSectionOffsetPlaceholder => 8, AllocatedOpcode::BLOB(count) => count.value as u64 * 4, AllocatedOpcode::CFEI(i) | AllocatedOpcode::CFSI(i) if i.value == 0 => 0, @@ -159,6 +160,29 @@ fn to_bytecode_mut( &ops_padded }; + let mut offset_from_instr_start = 0; + for op in ops.iter() { + match &op.opcode { + AllocatedOpcode::LoadDataId(_reg, data_label) + if !data_section + .has_copy_type(data_label) + .expect("data label references non existent data -- internal error") => + { + // For non-copy type loads, pre-insert pointers into the data_section so that + // from this point on, the data_section remains immutable. This is necessary + // so that when we take addresses of configurables, that address doesn't change + // later on if a non-configurable is added to the data-section. + let offset_bytes = data_section.data_id_to_offset(data_label) as u64; + // The -4 is because $pc is added in the *next* instruction. + let pointer_offset_from_current_instr = + offset_to_data_section_in_bytes - offset_from_instr_start + offset_bytes - 4; + data_section.append_pointer(pointer_offset_from_current_instr); + } + _ => (), + } + offset_from_instr_start += op_size_in_bytes(data_section, op); + } + let mut bytecode = Vec::with_capacity(offset_to_data_section_in_bytes as usize); if build_config.print_bytecode { @@ -184,7 +208,7 @@ fn to_bytecode_mut( offset_from_instr_start += op_size_in_bytes(data_section, op); match fuel_op { - Either::Right(data) => { + FuelAsmData::DatasectionOffset(data) => { if build_config.print_bytecode { print!("{}{:#010x} ", " ".repeat(indentation), bytecode.len()); println!( @@ -200,7 +224,23 @@ fn to_bytecode_mut( bytecode.extend(data.iter().cloned()); half_word_ix += 2; } - Either::Left(instructions) => { + FuelAsmData::ConfigurablesOffset(data) => { + if build_config.print_bytecode { + print!("{}{:#010x} ", " ".repeat(indentation), bytecode.len()); + println!( + " ;; {:?}", + data + ); + } + + // Static assert to ensure that we're only dealing with ConfigurablesOffsetPlaceholder, + // a 1-word (8 bytes) data within the code. No other uses are known. + let _: [u8; 8] = data; + + bytecode.extend(data.iter().cloned()); + half_word_ix += 2; + } + FuelAsmData::Instructions(instructions) => { for instruction in instructions { // Print original source span only once if build_config.print_bytecode_spans { @@ -313,9 +353,9 @@ fn to_bytecode_mut( }; } - for (i, entry) in data_section.value_pairs.iter().enumerate() { - let entry_offset = data_section.data_id_to_offset(&DataId(i as u32)); - print_entry(indentation, offset + entry_offset, entry); + for (i, entry) in data_section.iter_all_entries().enumerate() { + let entry_offset = data_section.absolute_idx_to_offset(i); + print_entry(indentation, offset + entry_offset, &entry); } println!(";; --- END OF TARGET BYTECODE ---\n"); @@ -324,16 +364,19 @@ fn to_bytecode_mut( assert_eq!(half_word_ix * 4, offset_to_data_section_in_bytes as usize); assert_eq!(bytecode.len(), offset_to_data_section_in_bytes as usize); + let num_nonconfigurables = data_section.non_configurables.len(); let named_data_section_entries_offsets = data_section - .value_pairs + .configurables .iter() .enumerate() - .filter(|entry| entry.1.name.is_some()) .map(|(id, entry)| { + let EntryName::Configurable(name) = &entry.name else { + panic!("Non-configurable in configurables part of datasection"); + }; ( - entry.name.as_ref().unwrap().clone(), + name.clone(), offset_to_data_section_in_bytes - + data_section.raw_data_id_to_offset(id as u32) as u64, + + data_section.absolute_idx_to_offset(id + num_nonconfigurables) as u64, ) }) .collect::>(); diff --git a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs index 0865687f5c8..e939ef633a2 100644 --- a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs @@ -1,7 +1,10 @@ -use crate::asm_lang::{ - allocated_ops::{AllocatedOpcode, AllocatedRegister}, - AllocatedAbstractOp, ConstantRegister, ControlFlowOp, Label, RealizedOp, VirtualImmediate12, - VirtualImmediate18, VirtualImmediate24, +use crate::{ + asm_generation::fuel::data_section::EntryName, + asm_lang::{ + allocated_ops::{AllocatedOpcode, AllocatedRegister}, + AllocatedAbstractOp, ConstantRegister, ControlFlowOp, Label, RealizedOp, + VirtualImmediate12, VirtualImmediate18, VirtualImmediate24, + }, }; use super::{ @@ -348,6 +351,13 @@ impl AllocatedAbstractInstructionSet { comment: String::new(), }); } + ControlFlowOp::ConfigurablesOffsetPlaceholder => { + realized_ops.push(RealizedOp { + opcode: AllocatedOpcode::ConfigurablesOffsetPlaceholder, + owning_span: None, + comment: String::new(), + }); + } ControlFlowOp::LoadLabel(r1, ref lab) => { // LoadLabel ops are inserted by `rewrite_far_jumps`. // So the next instruction must be a relative jump. @@ -363,8 +373,11 @@ impl AllocatedAbstractInstructionSet { // We compute the relative offset w.r.t the actual jump. // Sub 1 because the relative jumps add a 1. let offset = rel_offset(curr_offset + 1, lab) - 1; - let data_id = - data_section.insert_data_value(Entry::new_word(offset, None, None)); + let data_id = data_section.insert_data_value(Entry::new_word( + offset, + EntryName::NonConfigurable, + None, + )); realized_ops.push(RealizedOp { opcode: AllocatedOpcode::LoadDataId(r1, data_id), owning_span, @@ -444,6 +457,8 @@ impl AllocatedAbstractInstructionSet { } } + Either::Left(AllocatedOpcode::AddrDataId(_, ref _data_id)) => 2, + // cfei 0 and cfsi 0 are omitted from asm emission, don't count them for offsets Either::Left(AllocatedOpcode::CFEI(ref op)) | Either::Left(AllocatedOpcode::CFSI(ref op)) @@ -473,6 +488,8 @@ impl AllocatedAbstractInstructionSet { 2 } + Either::Right(ConfigurablesOffsetPlaceholder) => 2, + Either::Right(PushAll(_)) | Either::Right(PopAll(_)) => unreachable!( "fix me, pushall and popall don't really belong in control flow ops \ since they're not about control flow" diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index 76f3300c705..86cb5a1133a 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -1,16 +1,30 @@ +use rustc_hash::FxHashMap; use sway_ir::{size_bytes_round_up_to_word_alignment, Constant, ConstantValue, Context, Padding}; use std::{fmt, iter::repeat}; +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub enum EntryName { + NonConfigurable, + Configurable(String), +} + +impl fmt::Display for EntryName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EntryName::NonConfigurable => write!(f, "NonConfigurable"), + EntryName::Configurable(name) => write!(f, "", name), + } + } +} + // An entry in the data section. It's important for the size to be correct, especially for unions // where the size could be larger than the represented value. #[derive(Clone, Debug, serde::Serialize)] pub struct Entry { pub value: Datum, pub padding: Padding, - // It is assumed, for now, that only configuration-time constants have a name. Otherwise, this - // is `None`. - pub name: Option, + pub name: EntryName, } #[derive(Clone, Debug, serde::Serialize)] @@ -23,7 +37,7 @@ pub enum Datum { } impl Entry { - pub(crate) fn new_byte(value: u8, name: Option, padding: Option) -> Entry { + pub(crate) fn new_byte(value: u8, name: EntryName, padding: Option) -> Entry { Entry { value: Datum::Byte(value), padding: padding.unwrap_or(Padding::default_for_u8(value)), @@ -31,7 +45,7 @@ impl Entry { } } - pub(crate) fn new_word(value: u64, name: Option, padding: Option) -> Entry { + pub(crate) fn new_word(value: u64, name: EntryName, padding: Option) -> Entry { Entry { value: Datum::Word(value), padding: padding.unwrap_or(Padding::default_for_u64(value)), @@ -41,7 +55,7 @@ impl Entry { pub(crate) fn new_byte_array( bytes: Vec, - name: Option, + name: EntryName, padding: Option, ) -> Entry { Entry { @@ -51,11 +65,7 @@ impl Entry { } } - pub(crate) fn new_slice( - bytes: Vec, - name: Option, - padding: Option, - ) -> Entry { + pub(crate) fn new_slice(bytes: Vec, name: EntryName, padding: Option) -> Entry { Entry { padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)), value: Datum::Slice(bytes), @@ -65,7 +75,7 @@ impl Entry { pub(crate) fn new_collection( elements: Vec, - name: Option, + name: EntryName, padding: Option, ) -> Entry { Entry { @@ -80,7 +90,7 @@ impl Entry { pub(crate) fn from_constant( context: &Context, constant: &Constant, - name: Option, + name: EntryName, padding: Option, ) -> Entry { // We need a special handling in case of enums. @@ -89,8 +99,9 @@ impl Entry { .enum_tag_and_value_with_paddings(context) .expect("Constant is an enum."); - let tag_entry = Entry::from_constant(context, tag.0, None, tag.1); - let value_entry = Entry::from_constant(context, value.0, None, value.1); + let tag_entry = Entry::from_constant(context, tag.0, EntryName::NonConfigurable, tag.1); + let value_entry = + Entry::from_constant(context, value.0, EntryName::NonConfigurable, value.1); return Entry::new_collection(vec![tag_entry, value_entry], name, padding); } @@ -118,7 +129,9 @@ impl Entry { .array_elements_with_padding(context) .expect("Constant is an array.") .into_iter() - .map(|(elem, padding)| Entry::from_constant(context, elem, None, padding)) + .map(|(elem, padding)| { + Entry::from_constant(context, elem, EntryName::NonConfigurable, padding) + }) .collect(), name, padding, @@ -128,7 +141,9 @@ impl Entry { .struct_fields_with_padding(context) .expect("Constant is a struct.") .into_iter() - .map(|(elem, padding)| Entry::from_constant(context, elem, None, padding)) + .map(|(elem, padding)| { + Entry::from_constant(context, elem, EntryName::NonConfigurable, padding) + }) .collect(), name, padding, @@ -198,45 +213,92 @@ impl Entry { } } +#[derive(Clone, Debug)] +pub enum DataIdEntryKind { + NonConfigurable, + Configurable, +} + +impl fmt::Display for DataIdEntryKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DataIdEntryKind::NonConfigurable => write!(f, "NonConfigurable"), + DataIdEntryKind::Configurable => write!(f, "Configurable"), + } + } +} + /// An address which refers to a value in the data section of the asm. #[derive(Clone, Debug)] -pub(crate) struct DataId(pub(crate) u32); +pub(crate) struct DataId { + pub(crate) idx: u32, + pub(crate) kind: DataIdEntryKind, +} impl fmt::Display for DataId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "data_{}", self.0) + write!(f, "data_{}_{}", self.kind, self.idx) } } +/// The data to be put in the data section of the asm #[derive(Default, Clone, Debug)] pub struct DataSection { - /// the data to be put in the data section of the asm - pub value_pairs: Vec, + pub non_configurables: Vec, + pub configurables: Vec, + pub(crate) pointer_id: FxHashMap, } impl DataSection { + /// Get the number of entries + pub fn num_entries(&self) -> usize { + self.non_configurables.len() + self.configurables.len() + } + + /// Iterate over all entries, non-configurables followed by configurables + pub fn iter_all_entries(&self) -> impl Iterator + '_ { + self.non_configurables + .iter() + .chain(self.configurables.iter()) + .cloned() + } + + /// Get the absolute index of an id + fn absolute_idx(&self, id: &DataId) -> usize { + match id.kind { + DataIdEntryKind::NonConfigurable => id.idx as usize, + DataIdEntryKind::Configurable => id.idx as usize + self.non_configurables.len(), + } + } + + /// Get entry at id + fn get(&self, id: &DataId) -> Option<&Entry> { + match id.kind { + DataIdEntryKind::NonConfigurable => self.non_configurables.get(id.idx as usize), + DataIdEntryKind::Configurable => self.configurables.get(id.idx as usize), + } + } + /// Given a [DataId], calculate the offset _from the beginning of the data section_ to the data /// in bytes. pub(crate) fn data_id_to_offset(&self, id: &DataId) -> usize { - self.raw_data_id_to_offset(id.0) + let idx = self.absolute_idx(id); + self.absolute_idx_to_offset(idx) } - /// Given a [DataId], calculate the offset _from the beginning of the data section_ to the data + /// Given an absolute index, calculate the offset _from the beginning of the data section_ to the data /// in bytes. - pub(crate) fn raw_data_id_to_offset(&self, id: u32) -> usize { - self.value_pairs - .iter() - .take(id as usize) - .fold(0, |offset, entry| { - //entries must be word aligned - size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len()) - }) + pub(crate) fn absolute_idx_to_offset(&self, idx: usize) -> usize { + self.iter_all_entries().take(idx).fold(0, |offset, entry| { + //entries must be word aligned + size_bytes_round_up_to_word_alignment!(offset + entry.to_bytes().len()) + }) } pub(crate) fn serialize_to_bytes(&self) -> Vec { // not the exact right capacity but serves as a lower bound - let mut buf = Vec::with_capacity(self.value_pairs.len()); - for entry in &self.value_pairs { + let mut buf = Vec::with_capacity(self.num_entries()); + for entry in self.iter_all_entries() { buf.append(&mut entry.to_bytes()); //entries must be word aligned @@ -248,16 +310,12 @@ impl DataSection { /// Returns whether a specific [DataId] value has a copy type (fits in a register). pub(crate) fn has_copy_type(&self, id: &DataId) -> Option { - self.value_pairs - .get(id.0 as usize) - .map(|entry| entry.has_copy_type()) + self.get(id).map(|entry| entry.has_copy_type()) } /// Returns whether a specific [DataId] value is a byte entry. pub(crate) fn is_byte(&self, id: &DataId) -> Option { - self.value_pairs - .get(id.0 as usize) - .map(|entry| entry.is_byte()) + self.get(id).map(|entry| entry.is_byte()) } /// When generating code, sometimes a hard-coded data pointer is needed to reference @@ -268,31 +326,57 @@ impl DataSection { /// relative to the current (load) instruction. pub(crate) fn append_pointer(&mut self, pointer_value: u64) -> DataId { // The 'pointer' is just a literal 64 bit address. - self.insert_data_value(Entry::new_word(pointer_value, None, None)) + let data_id = self.insert_data_value(Entry::new_word( + pointer_value, + EntryName::NonConfigurable, + None, + )); + self.pointer_id.insert(pointer_value, data_id.clone()); + data_id + } + + /// Get the [DataId] for a pointer, if it exists. + /// The pointer must've been inserted with append_pointer. + pub(crate) fn data_id_of_pointer(&self, pointer_value: u64) -> Option { + self.pointer_id.get(&pointer_value).cloned() } /// Given any data in the form of a [Literal] (using this type mainly because it includes type - /// information and debug spans), insert it into the data section and return its offset as a + /// information and debug spans), insert it into the data section and return its handle as /// [DataId]. pub(crate) fn insert_data_value(&mut self, new_entry: Entry) -> DataId { // if there is an identical data value, use the same id - match self - .value_pairs - .iter() - .position(|entry| entry.equiv(&new_entry)) - { - Some(num) => DataId(num as u32), + + let (value_pairs, kind) = match new_entry.name { + EntryName::NonConfigurable => ( + &mut self.non_configurables, + DataIdEntryKind::NonConfigurable, + ), + EntryName::Configurable(_) => (&mut self.configurables, DataIdEntryKind::Configurable), + }; + match value_pairs.iter().position(|entry| entry.equiv(&new_entry)) { + Some(num) => DataId { + idx: num as u32, + kind, + }, None => { - self.value_pairs.push(new_entry); + value_pairs.push(new_entry); // the index of the data section where the value is stored - DataId((self.value_pairs.len() - 1) as u32) + DataId { + idx: (value_pairs.len() - 1) as u32, + kind, + } } } } // If the stored data is Datum::Word, return the inner value. pub(crate) fn get_data_word(&self, data_id: &DataId) -> Option { - self.value_pairs.get(data_id.0 as usize).and_then(|entry| { + let value_pairs = match data_id.kind { + DataIdEntryKind::NonConfigurable => &self.non_configurables, + DataIdEntryKind::Configurable => &self.configurables, + }; + value_pairs.get(data_id.idx as usize).and_then(|entry| { if let Datum::Word(w) = entry.value { Some(w) } else { @@ -322,11 +406,12 @@ impl fmt::Display for DataSection { use std::fmt::Write; let mut data_buf = String::new(); - for (ix, entry) in self.value_pairs.iter().enumerate() { + for (ix, entry) in self.iter_all_entries().enumerate() { writeln!( data_buf, - "{} {}", - DataId(ix as u32), + "data_{}_{} {}", + entry.name, + ix, display_entry(&entry.value) )?; } diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index 0e56e87d33f..5c98c09411e 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -1,4 +1,5 @@ use super::{ + data_section::EntryName, globals_section::GlobalsSection, programs::{AbstractEntry, AbstractProgram}, }; @@ -98,7 +99,12 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { fn compile_configurable(&mut self, config: &ConfigContent) { match config { ConfigContent::V0 { name, constant, .. } => { - let entry = Entry::from_constant(self.context, constant, Some(name.clone()), None); + let entry = Entry::from_constant( + self.context, + constant, + EntryName::Configurable(name.clone()), + None, + ); let dataid = self.data_section.insert_data_value(entry); self.configurable_v0_data_id.insert(name.clone(), dataid); } @@ -117,7 +123,7 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { let (decode_fn_label, _) = self.func_label_map.get(&decode_fn.get()).unwrap(); let dataid = self.data_section.insert_data_value(Entry::new_byte_array( encoded_bytes.clone(), - Some(name.clone()), + EntryName::Configurable(name.clone()), None, )); @@ -2057,6 +2063,11 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { _otherwise => { // Get the constant into the namespace. + let config_name = if let Some(name) = config_name { + EntryName::Configurable(name) + } else { + EntryName::NonConfigurable + }; let entry = Entry::from_constant(self.context, constant, config_name, None); let data_id = self.data_section.insert_data_value(entry); @@ -2177,9 +2188,11 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { } } else { let comment = comment.into(); - let data_id = self - .data_section - .insert_data_value(Entry::new_word(imm, None, None)); + let data_id = self.data_section.insert_data_value(Entry::new_word( + imm, + EntryName::NonConfigurable, + None, + )); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::LoadDataId(reg.clone(), data_id)), owning_span: span.clone(), diff --git a/sway-core/src/asm_generation/fuel/functions.rs b/sway-core/src/asm_generation/fuel/functions.rs index 42577c543d6..f217a4e8396 100644 --- a/sway-core/src/asm_generation/fuel/functions.rs +++ b/sway-core/src/asm_generation/fuel/functions.rs @@ -26,7 +26,7 @@ use sway_error::{ }; use sway_types::{Ident, Span}; -use super::compiler_constants::NUM_ARG_REGISTERS; +use super::{compiler_constants::NUM_ARG_REGISTERS, data_section::EntryName}; /// A summary of the adopted calling convention: /// @@ -830,9 +830,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ); } _ => { - let data_id = self.data_section.insert_data_value( - Entry::from_constant(self.context, constant, None, None), - ); + let data_id = + self.data_section.insert_data_value(Entry::from_constant( + self.context, + constant, + EntryName::NonConfigurable, + None, + )); self.ptr_map.insert(*ptr, Storage::Data(data_id)); } } @@ -858,9 +862,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { }); } _ => { - let data_id = self.data_section.insert_data_value( - Entry::from_constant(self.context, constant, None, None), - ); + let data_id = + self.data_section.insert_data_value(Entry::from_constant( + self.context, + constant, + EntryName::NonConfigurable, + None, + )); init_mut_vars.push(InitMutVars { stack_base_words, diff --git a/sway-core/src/asm_generation/fuel/optimizations.rs b/sway-core/src/asm_generation/fuel/optimizations.rs index 4800ba6906f..7fa0da2356c 100644 --- a/sway-core/src/asm_generation/fuel/optimizations.rs +++ b/sway-core/src/asm_generation/fuel/optimizations.rs @@ -27,7 +27,7 @@ impl AbstractInstructionSet { } // What does a register contain? - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] enum RegContents { Constant(u64), BaseOffset(VRegDef, u64), @@ -57,145 +57,184 @@ impl AbstractInstructionSet { latest_version.get(reg).cloned().unwrap_or(0) } - for op in &mut self.ops { + fn process_add( + reg_contents: &mut FxHashMap, + latest_version: &mut FxHashMap, + dest: &VirtualRegister, + opd1: &VirtualRegister, + c2: u64, + ) { + match reg_contents.get(opd1) { + Some(RegContents::Constant(c1)) if c1.checked_add(c2).is_some() => { + reg_contents.insert(dest.clone(), RegContents::Constant(c1 + c2)); + record_new_def(latest_version, dest); + } + Some(RegContents::BaseOffset(base_reg, offset)) + if get_def_version(latest_version, &base_reg.reg) == base_reg.ver + && offset.checked_add(c2).is_some() => + { + reg_contents.insert( + dest.clone(), + RegContents::BaseOffset(base_reg.clone(), offset + c2), + ); + record_new_def(latest_version, dest); + } + _ => { + let base = VRegDef { + reg: opd1.clone(), + ver: get_def_version(latest_version, opd1), + }; + reg_contents.insert(dest.clone(), RegContents::BaseOffset(base, c2)); + record_new_def(latest_version, dest); + } + } + } + + self.ops.retain_mut(|op| { + let mut retain = true; + let mut clear_state = false; + // Uncomment to debug what this optimization is doing // let op_before = op.clone(); - fn process_add( - reg_contents: &mut FxHashMap, - latest_version: &mut FxHashMap, - dest: &VirtualRegister, - opd1: &VirtualRegister, - c2: u64, - ) { - match reg_contents.get(opd1) { - Some(RegContents::Constant(c1)) if c1.checked_add(c2).is_some() => { - reg_contents.insert(dest.clone(), RegContents::Constant(c1 + c2)); - record_new_def(latest_version, dest); - } - Some(RegContents::BaseOffset(base_reg, offset)) - if get_def_version(latest_version, &base_reg.reg) == base_reg.ver - && offset.checked_add(c2).is_some() => - { - reg_contents.insert( - dest.clone(), - RegContents::BaseOffset(base_reg.clone(), offset + c2), - ); - record_new_def(latest_version, dest); - } - _ => { - let base = VRegDef { - reg: opd1.clone(), - ver: get_def_version(latest_version, opd1), - }; - reg_contents.insert(dest.clone(), RegContents::BaseOffset(base, c2)); - record_new_def(latest_version, dest); - } - } - } match &mut op.opcode { - either::Either::Left(op) => match op { - VirtualOp::ADD(dest, opd1, opd2) => { - // We don't look for the first operand being a constant and the second - // one a base register. Such patterns must be canonicalised prior. - let Some(&RegContents::Constant(c2)) = reg_contents.get(opd2) else { - reg_contents.remove(dest); - record_new_def(&mut latest_version, dest); - continue; - }; - process_add(&mut reg_contents, &mut latest_version, dest, opd1, c2); - } - VirtualOp::ADDI(dest, opd1, opd2) => { - let c2 = opd2.value as u64; - process_add(&mut reg_contents, &mut latest_version, dest, opd1, c2); - } - VirtualOp::MUL(dest, opd1, opd2) => { - match (reg_contents.get(opd1), reg_contents.get(opd2)) { - (Some(RegContents::Constant(c1)), Some(RegContents::Constant(c2))) => { - reg_contents.insert(dest.clone(), RegContents::Constant(c1 * c2)); + either::Either::Left(op) => { + match op { + VirtualOp::ADD(dest, opd1, opd2) => { + // We don't look for the first operand being a constant and the second + // one a base register. Such patterns must be canonicalised prior. + if let Some(&RegContents::Constant(c2)) = reg_contents.get(opd2) { + process_add(&mut reg_contents, &mut latest_version, dest, opd1, c2); + } else { + reg_contents.remove(dest); record_new_def(&mut latest_version, dest); + }; + } + VirtualOp::ADDI(dest, opd1, opd2) => { + let c2 = opd2.value as u64; + process_add(&mut reg_contents, &mut latest_version, dest, opd1, c2); + } + VirtualOp::MUL(dest, opd1, opd2) => { + match (reg_contents.get(opd1), reg_contents.get(opd2)) { + ( + Some(RegContents::Constant(c1)), + Some(RegContents::Constant(c2)), + ) => { + reg_contents + .insert(dest.clone(), RegContents::Constant(c1 * c2)); + record_new_def(&mut latest_version, dest); + } + _ => { + reg_contents.remove(dest); + record_new_def(&mut latest_version, dest); + } } - _ => { + } + VirtualOp::LoadDataId(dest, data_id) => { + if let Some(c) = data_section.get_data_word(data_id) { + reg_contents.insert(dest.clone(), RegContents::Constant(c)); + } else { reg_contents.remove(dest); - record_new_def(&mut latest_version, dest); } + record_new_def(&mut latest_version, dest); } - } - VirtualOp::LoadDataId(dest, data_id) => { - if let Some(c) = data_section.get_data_word(data_id) { - reg_contents.insert(dest.clone(), RegContents::Constant(c)); - } else { - reg_contents.remove(dest); + VirtualOp::MOVI(dest, imm) => { + reg_contents + .insert(dest.clone(), RegContents::Constant(imm.value as u64)); + record_new_def(&mut latest_version, dest); } - record_new_def(&mut latest_version, dest); - } - VirtualOp::MOVI(dest, imm) => { - reg_contents.insert(dest.clone(), RegContents::Constant(imm.value as u64)); - record_new_def(&mut latest_version, dest); - } - VirtualOp::LW(dest, addr_reg, imm) => match reg_contents.get(addr_reg) { - Some(RegContents::BaseOffset(base_reg, offset)) - if get_def_version(&latest_version, &base_reg.reg) == base_reg.ver - && ((offset / 8) + imm.value as u64) - < compiler_constants::TWELVE_BITS => - { - // bail if LW cannot read where this memory is - if offset % 8 == 0 { + VirtualOp::LW(dest, addr_reg, imm) => match reg_contents.get(addr_reg) { + Some(RegContents::BaseOffset(base_reg, offset)) + if get_def_version(&latest_version, &base_reg.reg) + == base_reg.ver + && ((offset / 8) + imm.value as u64) + < compiler_constants::TWELVE_BITS => + { + // bail if LW cannot read where this memory is + if offset % 8 == 0 { + let new_imm = VirtualImmediate12::new_unchecked( + (offset / 8) + imm.value as u64, + "Immediate offset too big for LW", + ); + let new_lw = + VirtualOp::LW(dest.clone(), base_reg.reg.clone(), new_imm); + // The register defined is no more useful for us. Forget anything from its past. + reg_contents.remove(dest); + record_new_def(&mut latest_version, dest); + // Replace the LW with a new one in-place. + *op = new_lw; + } + } + _ => { + reg_contents.remove(dest); + record_new_def(&mut latest_version, dest); + } + }, + VirtualOp::SW(addr_reg, src, imm) => match reg_contents.get(addr_reg) { + Some(RegContents::BaseOffset(base_reg, offset)) + if get_def_version(&latest_version, &base_reg.reg) + == base_reg.ver + && ((offset / 8) + imm.value as u64) + < compiler_constants::TWELVE_BITS => + { let new_imm = VirtualImmediate12::new_unchecked( (offset / 8) + imm.value as u64, - "Immediate offset too big for LW", + "Immediate offset too big for SW", + ); + let new_sw = + VirtualOp::SW(base_reg.reg.clone(), src.clone(), new_imm); + // Replace the SW with a new one in-place. + *op = new_sw; + } + _ => (), + }, + VirtualOp::MOVE(dest, src) => { + let ver = get_def_version(&latest_version, src); + if let Some(RegContents::BaseOffset(src, 0)) = reg_contents.get(src) { + if dest == &src.reg && src.ver == ver { + retain = false; + } + } else { + reg_contents.insert( + dest.clone(), + RegContents::BaseOffset( + VRegDef { + reg: src.clone(), + ver, + }, + 0, + ), ); - let new_lw = - VirtualOp::LW(dest.clone(), base_reg.reg.clone(), new_imm); - // The register defined is no more useful for us. Forget anything from its past. - reg_contents.remove(dest); - record_new_def(&mut latest_version, dest); - // Replace the LW with a new one in-place. - *op = new_lw; } } _ => { - reg_contents.remove(dest); - record_new_def(&mut latest_version, dest); - } - }, - VirtualOp::SW(addr_reg, src, imm) => match reg_contents.get(addr_reg) { - Some(RegContents::BaseOffset(base_reg, offset)) - if get_def_version(&latest_version, &base_reg.reg) == base_reg.ver - && ((offset / 8) + imm.value as u64) - < compiler_constants::TWELVE_BITS => - { - let new_imm = VirtualImmediate12::new_unchecked( - (offset / 8) + imm.value as u64, - "Immediate offset too big for SW", - ); - let new_sw = VirtualOp::SW(base_reg.reg.clone(), src.clone(), new_imm); - // Replace the SW with a new one in-place. - *op = new_sw; - } - _ => (), - }, - _ => { - // For every Op that we don't know about, - // forget everything we know about its def registers. - for def_reg in op.def_registers() { - reg_contents.remove(def_reg); - record_new_def(&mut latest_version, def_reg); + // For every Op that we don't know about, + // forget everything we know about its def registers. + for def_reg in op.def_registers() { + reg_contents.remove(def_reg); + record_new_def(&mut latest_version, def_reg); + } } } - }, + } + either::Either::Right(ControlFlowOp::SaveRetAddr(..)) => {} either::Either::Right(_) => { - // Reset state. - latest_version.clear(); - reg_contents.clear(); + clear_state = true; } } // Uncomment to debug what this optimization is doing - // let before = op_before.opcode.to_string(); - // let after = op.opcode.to_string(); - + //let before = op_before.opcode.to_string(); + //let after = op.opcode.to_string(); // println!("{}", before); + + if clear_state { + latest_version.clear(); + reg_contents.clear(); + // println!(" state cleared"); + } + + // Uncomment to debug what this optimization is doing // if before != after { // println!(" optimized to"); // println!(" {}", after); @@ -204,7 +243,20 @@ impl AbstractInstructionSet { // println!(" - {:?} -> {:?}", k, v); // } // } - } + // if !retain { + // println!(" removed"); + // for (k, v) in reg_contents.iter() { + // println!(" - {:?} -> {:?}", k, v); + // } + // } + // if forget_def_registers { + // for def_reg in op.def_registers() { + // println!(" forget {}", def_reg.to_string()); + // } + // } + + retain + }); self } diff --git a/sway-core/src/asm_generation/fuel/programs/abstract.rs b/sway-core/src/asm_generation/fuel/programs/abstract.rs index b5a8a12e302..3256da242bb 100644 --- a/sway-core/src/asm_generation/fuel/programs/abstract.rs +++ b/sway-core/src/asm_generation/fuel/programs/abstract.rs @@ -5,7 +5,7 @@ use crate::{ abstract_instruction_set::AbstractInstructionSet, allocated_abstract_instruction_set::AllocatedAbstractInstructionSet, compiler_constants, - data_section::{DataSection, Entry}, + data_section::{DataSection, Entry, EntryName}, globals_section::GlobalsSection, register_sequencer::RegisterSequencer, }, @@ -75,7 +75,7 @@ impl AbstractProgram { pub(crate) fn is_empty(&self) -> bool { self.non_entries.is_empty() && self.entries.is_empty() - && self.data_section.value_pairs.is_empty() + && self.data_section.iter_all_entries().next().is_none() } /// Adds prologue, globals allocation, before entries, contract method switch, and allocates virtual register. @@ -164,14 +164,28 @@ impl AbstractProgram { /// Right now, it looks like this: /// /// WORD OP - /// [1] MOV $scratch $pc - /// [-] JMPF $zero i2 - /// [2] DATA_START (0-32) (in bytes, offset from $is) - /// [-] DATA_START (32-64) - /// [3] LW $ds $scratch 1 - /// [-] ADD $ds $ds $scratch - /// [4] .program_start: + /// 1 MOV $scratch $pc + /// - JMPF $zero i10 + /// 2 DATA_START (0-32) (in bytes, offset from $is) + /// - DATA_START (32-64) + /// 3 CONFIGURABLES_OFFSET (0-32) + /// - CONFIGURABLES_OFFSET (32-64) + /// 4 LW $ds $scratch 1 + /// - ADD $ds $ds $scratch + /// 5 .program_start: fn build_prologue(&mut self) -> AllocatedAbstractInstructionSet { + const _: () = assert!( + crate::PRELUDE_CONFIGURABLES_OFFSET_IN_BYTES == 16, + "Inconsistency in the assumption of prelude organisation" + ); + const _: () = assert!( + crate::PRELUDE_CONFIGURABLES_SIZE_IN_BYTES == 8, + "Inconsistency in the assumption of prelude organisation" + ); + const _: () = assert!( + crate::PRELUDE_SIZE_IN_BYTES == 32, + "Inconsistency in the assumption of prelude organisation" + ); let label = self.reg_seqr.get_label(); AllocatedAbstractInstructionSet { ops: [ @@ -195,12 +209,18 @@ impl AbstractProgram { comment: "data section offset".into(), owning_span: None, }, + // word 3 -- full word u64 placeholder + AllocatedAbstractOp { + opcode: Either::Right(ControlFlowOp::ConfigurablesOffsetPlaceholder), + comment: "configurables offset".into(), + owning_span: None, + }, AllocatedAbstractOp { opcode: Either::Right(ControlFlowOp::Label(label)), - comment: "end of metadata".into(), + comment: "end of configurables offset".into(), owning_span: None, }, - // word 3 -- load the data offset into $ds + // word 4 -- load the data offset into $ds AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::LW( AllocatedRegister::Constant(ConstantRegister::DataSectionStart), @@ -210,7 +230,7 @@ impl AbstractProgram { comment: "".into(), owning_span: None, }, - // word 3.5 -- add $ds $ds $is + // word 4.5 -- add $ds $ds $is AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::ADD( AllocatedRegister::Constant(ConstantRegister::DataSectionStart), @@ -281,7 +301,7 @@ impl AbstractProgram { // Put the selector in the data section. let data_label = self.data_section.insert_data_value(Entry::new_word( u32::from_be_bytes(selector) as u64, - None, + EntryName::NonConfigurable, None, )); diff --git a/sway-core/src/asm_lang/allocated_ops.rs b/sway-core/src/asm_lang/allocated_ops.rs index b78ba37b2db..b817afd3c32 100644 --- a/sway-core/src/asm_lang/allocated_ops.rs +++ b/sway-core/src/asm_lang/allocated_ops.rs @@ -17,8 +17,10 @@ use crate::{ }, fuel_prelude::fuel_asm::{self, op}, }; -use either::Either; -use fuel_vm::fuel_asm::{op::ADDI, Imm12}; +use fuel_vm::fuel_asm::{ + op::{ADD, MOVI}, + Imm18, +}; use std::fmt::{self, Write}; use sway_types::span::Span; @@ -273,6 +275,7 @@ pub(crate) enum AllocatedOpcode { /* Non-VM Instructions */ BLOB(VirtualImmediate24), + ConfigurablesOffsetPlaceholder, DataSectionOffsetPlaceholder, LoadDataId(AllocatedRegister, DataId), AddrDataId(AllocatedRegister, DataId), @@ -397,6 +400,7 @@ impl AllocatedOpcode { /* Non-VM Instructions */ BLOB(_imm) => vec![], + ConfigurablesOffsetPlaceholder => vec![], DataSectionOffsetPlaceholder => vec![], LoadDataId(r1, _i) => vec![r1], AddrDataId(r1, _i) => vec![r1], @@ -525,6 +529,10 @@ impl fmt::Display for AllocatedOpcode { /* Non-VM Instructions */ BLOB(a) => write!(fmtr, "blob {a}"), + ConfigurablesOffsetPlaceholder => write!( + fmtr, + "CONFIGURABLES_OFFSET[0..32]\nCONFIGURABLES_OFFSET[32..64]" + ), DataSectionOffsetPlaceholder => { write!( fmtr, @@ -562,17 +570,21 @@ impl fmt::Display for AllocatedOp { } } -type DoubleWideData = [u8; 8]; +pub(crate) enum FuelAsmData { + ConfigurablesOffset([u8; 8]), + DatasectionOffset([u8; 8]), + Instructions(Vec), +} impl AllocatedOp { pub(crate) fn to_fuel_asm( &self, offset_to_data_section: u64, offset_from_instr_start: u64, - data_section: &mut DataSection, - ) -> Either, DoubleWideData> { + data_section: &DataSection, + ) -> FuelAsmData { use AllocatedOpcode::*; - Either::Left(vec![match &self.opcode { + FuelAsmData::Instructions(vec![match &self.opcode { /* Arithmetic/Logic (ALU) Instructions */ ADD(a, b, c) => op::ADD::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id()).into(), ADDI(a, b, c) => op::ADDI::new(a.to_reg_id(), b.to_reg_id(), c.value.into()).into(), @@ -641,9 +653,9 @@ impl AllocatedOp { /* Memory Instructions */ ALOC(a) => op::ALOC::new(a.to_reg_id()).into(), - CFEI(a) if a.value == 0 => return Either::Left(vec![]), + CFEI(a) if a.value == 0 => return FuelAsmData::Instructions(vec![]), CFEI(a) => op::CFEI::new(a.value.into()).into(), - CFSI(a) if a.value == 0 => return Either::Left(vec![]), + CFSI(a) if a.value == 0 => return FuelAsmData::Instructions(vec![]), CFSI(a) => op::CFSI::new(a.value.into()).into(), CFE(a) => op::CFE::new(a.to_reg_id()).into(), CFS(a) => op::CFS::new(a.to_reg_id()).into(), @@ -727,17 +739,20 @@ impl AllocatedOp { /* Non-VM Instructions */ BLOB(a) => { - return Either::Left( + return FuelAsmData::Instructions( std::iter::repeat(op::NOOP::new().into()) .take(a.value as usize) .collect(), ) } + ConfigurablesOffsetPlaceholder => { + return FuelAsmData::ConfigurablesOffset([0, 0, 0, 0, 0, 0, 0, 0]) + } DataSectionOffsetPlaceholder => { - return Either::Right(offset_to_data_section.to_be_bytes()) + return FuelAsmData::DatasectionOffset(offset_to_data_section.to_be_bytes()) } LoadDataId(a, b) => { - return Either::Left(realize_load( + return FuelAsmData::Instructions(realize_load( a, b, data_section, @@ -745,7 +760,7 @@ impl AllocatedOp { offset_from_instr_start, )) } - AddrDataId(a, b) => return Either::Left(addr_of(a, b, data_section)), + AddrDataId(a, b) => return FuelAsmData::Instructions(addr_of(a, b, data_section)), Undefined => unreachable!("Sway cannot generate undefined ASM opcodes"), }]) } @@ -755,14 +770,20 @@ impl AllocatedOp { fn addr_of( dest: &AllocatedRegister, data_id: &DataId, - data_section: &mut DataSection, + data_section: &DataSection, ) -> Vec { let offset_bytes = data_section.data_id_to_offset(data_id) as u64; - vec![fuel_asm::Instruction::ADDI(ADDI::new( - dest.to_reg_id(), - fuel_asm::RegId::new(DATA_SECTION_REGISTER), - Imm12::new(offset_bytes as u16), - ))] + vec![ + fuel_asm::Instruction::MOVI(MOVI::new( + dest.to_reg_id(), + Imm18::new(offset_bytes.try_into().unwrap()), + )), + fuel_asm::Instruction::ADD(ADD::new( + dest.to_reg_id(), + dest.to_reg_id(), + fuel_asm::RegId::new(DATA_SECTION_REGISTER), + )), + ] } /// Converts a virtual load word instruction which uses data labels into one which uses @@ -772,7 +793,7 @@ fn addr_of( fn realize_load( dest: &AllocatedRegister, data_id: &DataId, - data_section: &mut DataSection, + data_section: &DataSection, offset_to_data_section: u64, offset_from_instr_start: u64, ) -> Vec { @@ -815,7 +836,9 @@ fn realize_load( offset_to_data_section - offset_from_instr_start + offset_bytes - 4; // insert the pointer as bytes as a new data section entry at the end of the data - let data_id_for_pointer = data_section.append_pointer(pointer_offset_from_current_instr); + let data_id_for_pointer = data_section + .data_id_of_pointer(pointer_offset_from_current_instr) + .expect("Pointer offset must be in data_section"); // now load the pointer we just created into the `dest`ination let mut buf = Vec::with_capacity(2); diff --git a/sway-core/src/asm_lang/mod.rs b/sway-core/src/asm_lang/mod.rs index 5e1bcaded52..71e09cc2290 100644 --- a/sway-core/src/asm_lang/mod.rs +++ b/sway-core/src/asm_lang/mod.rs @@ -1248,6 +1248,7 @@ impl fmt::Display for VirtualOp { /* Non-VM Instructions */ BLOB(a) => write!(fmtr, "blob {a}"), DataSectionOffsetPlaceholder => write!(fmtr, "data section offset placeholder"), + ConfigurablesOffsetPlaceholder => write!(fmtr, "configurables offset placeholder"), LoadDataId(a, b) => write!(fmtr, "load {a} {b}"), AddrDataId(a, b) => write!(fmtr, "addr {a} {b}"), Undefined => write!(fmtr, "undefined op"), @@ -1277,6 +1278,8 @@ pub(crate) enum ControlFlowOp { Call(Label), // Save a return label address in a register. SaveRetAddr(Reg, Label), + // Placeholder for the offset into the configurables section. + ConfigurablesOffsetPlaceholder, // placeholder for the DataSection offset DataSectionOffsetPlaceholder, // Placeholder for loading an address from the data section. @@ -1304,6 +1307,8 @@ impl fmt::Display for ControlFlowOp { SaveRetAddr(r1, lab) => format!("mova {r1} {lab}"), DataSectionOffsetPlaceholder => "DATA SECTION OFFSET[0..32]\nDATA SECTION OFFSET[32..64]".into(), + ConfigurablesOffsetPlaceholder => + "CONFIGURABLES_OFFSET[0..32]\nCONFIGURABLES_OFFSET[32..64]".into(), LoadLabel(r1, lab) => format!("lwlab {r1} {lab}"), PushAll(lab) => format!("pusha {lab}"), PopAll(lab) => format!("popa {lab}"), @@ -1321,6 +1326,7 @@ impl ControlFlowOp { | Jump(_) | Call(_) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | PushAll(_) | PopAll(_) => vec![], @@ -1339,6 +1345,7 @@ impl ControlFlowOp { | Call(_) | SaveRetAddr(..) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | LoadLabel(..) | PushAll(_) | PopAll(_) => vec![], @@ -1360,6 +1367,7 @@ impl ControlFlowOp { | JumpIfNotZero(..) | Call(_) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | PushAll(_) | PopAll(_) => vec![], }) @@ -1381,6 +1389,7 @@ impl ControlFlowOp { | Jump(_) | Call(_) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | PushAll(_) | PopAll(_) => self.clone(), @@ -1410,6 +1419,7 @@ impl ControlFlowOp { | Call(_) | SaveRetAddr(..) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | LoadLabel(..) | PushAll(_) | PopAll(_) => (), @@ -1466,6 +1476,7 @@ impl ControlFlowOp { Jump(label) => Jump(*label), Call(label) => Call(*label), DataSectionOffsetPlaceholder => DataSectionOffsetPlaceholder, + ConfigurablesOffsetPlaceholder => ConfigurablesOffsetPlaceholder, PushAll(label) => PushAll(*label), PopAll(label) => PopAll(*label), diff --git a/sway-core/src/asm_lang/virtual_ops.rs b/sway-core/src/asm_lang/virtual_ops.rs index ffe3509b7cd..3d041713a4f 100644 --- a/sway-core/src/asm_lang/virtual_ops.rs +++ b/sway-core/src/asm_lang/virtual_ops.rs @@ -226,6 +226,7 @@ pub(crate) enum VirtualOp { /* Non-VM Instructions */ BLOB(VirtualImmediate24), + ConfigurablesOffsetPlaceholder, DataSectionOffsetPlaceholder, // LoadDataId takes a virtual register and a DataId, which points to a labeled piece // of data in the data section. Note that the ASM op corresponding to a LW is @@ -347,6 +348,7 @@ impl VirtualOp { /* Non-VM Instructions */ BLOB(_imm) => vec![], DataSectionOffsetPlaceholder => vec![], + ConfigurablesOffsetPlaceholder => vec![], LoadDataId(r1, _i) => vec![r1], AddrDataId(r1, _) => vec![r1], @@ -465,6 +467,7 @@ impl VirtualOp { // Virtual OPs | BLOB(_) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | Undefined => true } } @@ -571,6 +574,7 @@ impl VirtualOp { | GTF(_, _, _) | BLOB(_) | DataSectionOffsetPlaceholder + | ConfigurablesOffsetPlaceholder | LoadDataId(_, _) | AddrDataId(_, _) | Undefined => vec![], @@ -692,6 +696,7 @@ impl VirtualOp { /* Non-VM Instructions */ BLOB(_imm) => vec![], DataSectionOffsetPlaceholder => vec![], + ConfigurablesOffsetPlaceholder => vec![], LoadDataId(_r1, _i) => vec![], AddrDataId(_r1, _i) => vec![], @@ -815,6 +820,7 @@ impl VirtualOp { LoadDataId(r1, _i) => vec![r1], AddrDataId(r1, _i) => vec![r1], DataSectionOffsetPlaceholder => vec![], + ConfigurablesOffsetPlaceholder => vec![], Undefined => vec![], }) .into_iter() @@ -1263,6 +1269,7 @@ impl VirtualOp { /* Non-VM Instructions */ BLOB(i) => Self::BLOB(i.clone()), DataSectionOffsetPlaceholder => Self::DataSectionOffsetPlaceholder, + ConfigurablesOffsetPlaceholder => Self::ConfigurablesOffsetPlaceholder, LoadDataId(r1, i) => Self::LoadDataId(update_reg(reg_to_reg_map, r1), i.clone()), AddrDataId(r1, i) => Self::AddrDataId(update_reg(reg_to_reg_map, r1), i.clone()), Undefined => Self::Undefined, @@ -1743,6 +1750,7 @@ impl VirtualOp { /* Non-VM Instructions */ BLOB(imm) => AllocatedOpcode::BLOB(imm.clone()), DataSectionOffsetPlaceholder => AllocatedOpcode::DataSectionOffsetPlaceholder, + ConfigurablesOffsetPlaceholder => AllocatedOpcode::ConfigurablesOffsetPlaceholder, LoadDataId(reg1, label) => { AllocatedOpcode::LoadDataId(map_reg(&mapping, reg1), label.clone()) } diff --git a/sway-core/src/decl_engine/id.rs b/sway-core/src/decl_engine/id.rs index 8878b9aaa79..597b716f1c1 100644 --- a/sway-core/src/decl_engine/id.rs +++ b/sway-core/src/decl_engine/id.rs @@ -1,20 +1,20 @@ -use std::collections::hash_map::DefaultHasher; -use std::hash::Hasher; -use std::marker::PhantomData; -use std::{fmt, hash::Hash}; - -use sway_types::{Named, Spanned}; - -use crate::language::ty::{TyDeclParsedType, TyTraitType}; use crate::{ decl_engine::*, engine_threading::*, language::ty::{ - TyEnumDecl, TyFunctionDecl, TyImplSelfOrTrait, TyStructDecl, TyTraitDecl, TyTraitFn, - TyTypeAliasDecl, + TyDeclParsedType, TyEnumDecl, TyFunctionDecl, TyImplSelfOrTrait, TyStructDecl, TyTraitDecl, + TyTraitFn, TyTraitType, TyTypeAliasDecl, }, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::{ + collections::hash_map::DefaultHasher, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, +}; +use sway_types::{Named, Spanned}; pub type DeclIdIndexType = usize; @@ -27,7 +27,7 @@ impl fmt::Debug for DeclId { } } -#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct DeclUniqueId(pub(crate) u64); impl DeclId { @@ -258,3 +258,22 @@ where } } } + +impl Serialize for DeclId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for DeclId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let id = DeclIdIndexType::deserialize(deserializer)?; + Ok(DeclId::new(id)) + } +} diff --git a/sway-core/src/decl_engine/interface_decl_id.rs b/sway-core/src/decl_engine/interface_decl_id.rs index 2306622a94a..cba36cfd429 100644 --- a/sway-core/src/decl_engine/interface_decl_id.rs +++ b/sway-core/src/decl_engine/interface_decl_id.rs @@ -1,3 +1,4 @@ +use super::{parsed_engine::ParsedDeclEngineGet, parsed_id::ParsedDeclId}; use crate::{ decl_engine::*, engine_threading::{EqWithEngines, PartialEqWithEngines, PartialEqWithEnginesContext}, @@ -6,8 +7,7 @@ use crate::{ ty, }, }; - -use super::{parsed_engine::ParsedDeclEngineGet, parsed_id::ParsedDeclId}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum ParsedInterfaceDeclId { @@ -43,7 +43,7 @@ impl From> for ParsedInterfaceDeclId { } } -#[derive(Debug, Eq, PartialEq, Hash, Clone)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)] pub enum InterfaceDeclId { Abi(DeclId), Trait(DeclId), diff --git a/sway-core/src/decl_engine/parsed_id.rs b/sway-core/src/decl_engine/parsed_id.rs index 3a97459aa75..1a513a6d5be 100644 --- a/sway-core/src/decl_engine/parsed_id.rs +++ b/sway-core/src/decl_engine/parsed_id.rs @@ -1,16 +1,20 @@ -use std::hash::{DefaultHasher, Hasher}; -use std::marker::PhantomData; -use std::{fmt, hash::Hash}; - -use sway_types::{Named, Spanned}; - -use crate::engine_threading::{ - EqWithEngines, HashWithEngines, PartialEqWithEngines, PartialEqWithEnginesContext, +use super::{ + parsed_engine::{ParsedDeclEngine, ParsedDeclEngineGet, ParsedDeclEngineIndex}, + DeclUniqueId, }; -use crate::Engines; - -use super::parsed_engine::{ParsedDeclEngine, ParsedDeclEngineGet, ParsedDeclEngineIndex}; -use super::DeclUniqueId; +use crate::{ + engine_threading::{ + EqWithEngines, HashWithEngines, PartialEqWithEngines, PartialEqWithEnginesContext, + }, + Engines, +}; +use serde::{Deserialize, Serialize}; +use std::{ + hash::{DefaultHasher, Hasher}, + marker::PhantomData, + {fmt, hash::Hash}, +}; +use sway_types::{Named, Spanned}; pub type ParsedDeclIdIndexType = usize; @@ -91,6 +95,25 @@ impl Ord for ParsedDeclId { } } +impl Serialize for ParsedDeclId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'de, T> Deserialize<'de> for ParsedDeclId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let id = usize::deserialize(deserializer)?; + Ok(ParsedDeclId::new(id)) + } +} + impl ParsedDeclId { pub(crate) fn new(id: usize) -> Self { ParsedDeclId(id, PhantomData) diff --git a/sway-core/src/decl_engine/ref.rs b/sway-core/src/decl_engine/ref.rs index 663fd563a60..d3b2d15feae 100644 --- a/sway-core/src/decl_engine/ref.rs +++ b/sway-core/src/decl_engine/ref.rs @@ -20,11 +20,6 @@ //! `fn my_function() { .. }`, and to use [DeclRef] for cases like function //! application `my_function()`. -use std::hash::{Hash, Hasher}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ decl_engine::*, engine_threading::*, @@ -35,6 +30,10 @@ use crate::{ semantic_analysis::TypeCheckContext, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Named, Span, Spanned}; pub type DeclRefFunction = DeclRef>; pub type DeclRefTrait = DeclRef>; @@ -53,7 +52,7 @@ pub type DeclRefMixedInterface = DeclRef; /// Represents the use of / syntactic reference to a declaration. A /// smart-wrapper around a [DeclId], containing additional information about a /// declaration. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DeclRef { /// The name of the declaration. // NOTE: In the case of storage, the name is "storage". @@ -103,7 +102,7 @@ where let decl_engine = ctx.engines.de(); if ctx .type_subst_map - .source_ids_contains_concrete_type(ctx.engines) + .is_some_and(|tsm| tsm.source_ids_contains_concrete_type(ctx.engines)) || !decl_engine.get(&self.id).is_concrete(ctx.engines) { let mut decl = (*decl_engine.get(&self.id)).clone(); diff --git a/sway-core/src/language/asm.rs b/sway-core/src/language/asm.rs index c8fcbb429a5..409e343eef6 100644 --- a/sway-core/src/language/asm.rs +++ b/sway-core/src/language/asm.rs @@ -1,8 +1,8 @@ +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; - use sway_types::{BaseIdent, Ident, Span}; -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize)] pub struct AsmOp { pub(crate) op_name: Ident, pub(crate) op_args: Vec, @@ -43,7 +43,7 @@ impl PartialEq for AsmOp { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AsmRegister { pub(crate) name: String, } diff --git a/sway-core/src/language/call_path.rs b/sway-core/src/language/call_path.rs index 8036d3ecab8..4ef90706af1 100644 --- a/sway-core/src/language/call_path.rs +++ b/sway-core/src/language/call_path.rs @@ -1,27 +1,25 @@ -use std::{ - cmp::Ordering, - fmt, - hash::{Hash, Hasher}, - sync::Arc, -}; - use crate::{ engine_threading::{ DebugWithEngines, DisplayWithEngines, EqWithEngines, HashWithEngines, OrdWithEngines, OrdWithEnginesContext, PartialEqWithEngines, PartialEqWithEnginesContext, }, + parsed::QualifiedPathType, Engines, Ident, Namespace, }; - +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, + sync::Arc, +}; use sway_error::{ error::CompileError, handler::{ErrorEmitted, Handler}, }; use sway_types::{span::Span, Spanned}; -use super::parsed::QualifiedPathType; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct CallPathTree { pub qualified_call_path: QualifiedCallPath, pub children: Vec, @@ -75,7 +73,7 @@ impl OrdWithEngines for CallPathTree { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct QualifiedCallPath { pub call_path: CallPath, @@ -179,7 +177,7 @@ impl DebugWithEngines for QualifiedCallPath { /// In the expression `a::b::c()`, `a` and `b` are the prefixes and `c` is the suffix. /// `c` can be any type `T`, but in practice `c` is either an `Ident` or a `TypeInfo`. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)] pub struct CallPath { pub prefixes: Vec, pub suffix: T, diff --git a/sway-core/src/language/lazy_op.rs b/sway-core/src/language/lazy_op.rs index 73e3daae256..abcb6149d26 100644 --- a/sway-core/src/language/lazy_op.rs +++ b/sway-core/src/language/lazy_op.rs @@ -1,4 +1,6 @@ -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum LazyOp { And, Or, diff --git a/sway-core/src/language/literal.rs b/sway-core/src/language/literal.rs index afedaad12d0..df0bc8735fb 100644 --- a/sway-core/src/language/literal.rs +++ b/sway-core/src/language/literal.rs @@ -1,15 +1,14 @@ use crate::{type_system::*, Engines}; - -use sway_error::error::CompileError; -use sway_types::{integer_bits::IntegerBits, span, u256::U256}; - +use serde::{Deserialize, Serialize}; use std::{ fmt, hash::{Hash, Hasher}, num::{IntErrorKind, ParseIntError}, }; +use sway_error::error::CompileError; +use sway_types::{integer_bits::IntegerBits, span, u256::U256}; -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize)] pub enum Literal { U8(u8), U16(u16), diff --git a/sway-core/src/language/parsed/declaration/trait.rs b/sway-core/src/language/parsed/declaration/trait.rs index 12e76e554c6..89bff7b4e94 100644 --- a/sway-core/src/language/parsed/declaration/trait.rs +++ b/sway-core/src/language/parsed/declaration/trait.rs @@ -1,7 +1,4 @@ -use std::hash::{Hash, Hasher}; - use super::{ConstantDeclaration, FunctionDeclaration, FunctionParameter}; - use crate::{ decl_engine::{parsed_id::ParsedDeclId, DeclRefTrait}, engine_threading::*, @@ -9,16 +6,18 @@ use crate::{ transform, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; use sway_error::handler::ErrorEmitted; use sway_types::{ident::Ident, span::Span, Named, Spanned}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum TraitItem { TraitFn(ParsedDeclId), Constant(ParsedDeclId), Type(ParsedDeclId), // to handle parser recovery: Error represents an incomplete trait item - Error(Box<[Span]>, ErrorEmitted), + Error(Box<[Span]>, #[serde(skip)] ErrorEmitted), } impl EqWithEngines for TraitItem {} @@ -75,7 +74,7 @@ impl Spanned for TraitDeclaration { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Supertrait { pub name: CallPath, pub decl_ref: Option, diff --git a/sway-core/src/language/parsed/expression/mod.rs b/sway-core/src/language/parsed/expression/mod.rs index 90d41fbf19b..d6f6f2df54b 100644 --- a/sway-core/src/language/parsed/expression/mod.rs +++ b/sway-core/src/language/parsed/expression/mod.rs @@ -1,5 +1,3 @@ -use std::{cmp::Ordering, fmt, hash::Hasher}; - use crate::{ decl_engine::parsed_id::ParsedDeclId, engine_threading::{ @@ -10,6 +8,8 @@ use crate::{ type_system::TypeBinding, Engines, TypeArgument, TypeId, }; +use serde::{Deserialize, Serialize}; +use std::{cmp::Ordering, fmt, hash::Hasher}; use sway_error::handler::ErrorEmitted; use sway_types::{ident::Ident, Span, Spanned}; @@ -198,7 +198,7 @@ impl Spanned for AmbiguousSuffix { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct QualifiedPathType { pub ty: TypeArgument, pub as_trait: TypeId, @@ -370,6 +370,7 @@ impl PartialEqWithEngines for IntrinsicFunctionExpression { pub struct WhileLoopExpression { pub condition: Box, pub body: CodeBlock, + pub is_desugared_for_loop: bool, } impl EqWithEngines for WhileLoopExpression {} diff --git a/sway-core/src/language/parsed/use_statement.rs b/sway-core/src/language/parsed/use_statement.rs index ceda688d75f..106fa21e04a 100644 --- a/sway-core/src/language/parsed/use_statement.rs +++ b/sway-core/src/language/parsed/use_statement.rs @@ -1,7 +1,8 @@ use crate::{language::Visibility, parsed::Span}; +use serde::{Deserialize, Serialize}; use sway_types::ident::Ident; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ImportType { Star, SelfImport(Span), diff --git a/sway-core/src/language/purity.rs b/sway-core/src/language/purity.rs index 4b498b2fcdd..46961a8d0df 100644 --- a/sway-core/src/language/purity.rs +++ b/sway-core/src/language/purity.rs @@ -1,7 +1,9 @@ +use serde::{Deserialize, Serialize}; + /// The purity of a function is related to its access of contract storage. If a function accesses /// or could potentially access contract storage, it is [Purity::Impure]. If a function does not utilize any /// any accesses (reads _or_ writes) of storage, then it is [Purity::Pure]. -#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub enum Purity { #[default] Pure, diff --git a/sway-core/src/language/ty/ast_node.rs b/sway-core/src/language/ty/ast_node.rs index 59d9e942004..34dcdf95572 100644 --- a/sway-core/src/language/ty/ast_node.rs +++ b/sway-core/src/language/ty/ast_node.rs @@ -1,11 +1,3 @@ -use std::{ - fmt::{self, Debug}, - hash::{Hash, Hasher}, -}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Span}; - use crate::{ decl_engine::*, engine_threading::*, @@ -18,12 +10,19 @@ use crate::{ type_system::*, types::*, }; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Debug}, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Span}; pub trait GetDeclIdent { fn get_decl_ident(&self, engines: &Engines) -> Option; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyAstNode { pub content: TyAstNodeContent, pub span: Span, @@ -353,13 +352,13 @@ impl TyAstNode { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyAstNodeContent { Declaration(TyDecl), Expression(TyExpression), // a no-op node used for something that just issues a side effect, like an import statement. SideEffect(TySideEffect), - Error(Box<[Span]>, ErrorEmitted), + Error(Box<[Span]>, #[serde(skip)] ErrorEmitted), } impl EqWithEngines for TyAstNodeContent {} diff --git a/sway-core/src/language/ty/code_block.rs b/sway-core/src/language/ty/code_block.rs index 6ae9b0d0dea..77a841ff4bc 100644 --- a/sway-core/src/language/ty/code_block.rs +++ b/sway-core/src/language/ty/code_block.rs @@ -1,14 +1,13 @@ -use std::hash::Hasher; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::Span; - use crate::{ decl_engine::*, engine_threading::*, language::ty::*, semantic_analysis::TypeCheckContext, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::Hasher; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::Span; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyCodeBlock { pub contents: Vec, pub(crate) whole_block_span: Span, diff --git a/sway-core/src/language/ty/declaration/abi.rs b/sway-core/src/language/ty/declaration/abi.rs index 7dae1ef3271..4d3371394ed 100644 --- a/sway-core/src/language/ty/declaration/abi.rs +++ b/sway-core/src/language/ty/declaration/abi.rs @@ -1,18 +1,17 @@ +use super::{TyDeclParsedType, TyTraitInterfaceItem, TyTraitItem}; use crate::{ engine_threading::*, language::parsed::{self, AbiDeclaration}, transform, type_system::*, }; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; - use sway_types::{Ident, Named, Span, Spanned}; -use super::{TyDeclParsedType, TyTraitInterfaceItem, TyTraitItem}; - /// A [TyAbiDecl] contains the type-checked version of the parse tree's /// `AbiDeclaration`. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyAbiDecl { /// The name of the abi trait (also known as a "contract trait") pub name: Ident, diff --git a/sway-core/src/language/ty/declaration/configurable.rs b/sway-core/src/language/ty/declaration/configurable.rs index da7349fc91b..3de69ed1425 100644 --- a/sway-core/src/language/ty/declaration/configurable.rs +++ b/sway-core/src/language/ty/declaration/configurable.rs @@ -1,11 +1,3 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, -}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ decl_engine::{DeclId, DeclMapping, DeclRef, ReplaceDecls}, engine_threading::*, @@ -15,8 +7,15 @@ use crate::{ transform, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Named, Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyConfigurableDecl { pub call_path: CallPath, pub value: Option, diff --git a/sway-core/src/language/ty/declaration/constant.rs b/sway-core/src/language/ty/declaration/constant.rs index c8acda6b5cf..35b675e6ace 100644 --- a/sway-core/src/language/ty/declaration/constant.rs +++ b/sway-core/src/language/ty/declaration/constant.rs @@ -1,11 +1,3 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, -}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ decl_engine::{DeclMapping, ReplaceDecls}, engine_threading::*, @@ -15,8 +7,15 @@ use crate::{ transform, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Named, Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyConstantDecl { pub call_path: CallPath, pub value: Option, diff --git a/sway-core/src/language/ty/declaration/declaration.rs b/sway-core/src/language/ty/declaration/declaration.rs index 3c5b6481d09..5a9ba831149 100644 --- a/sway-core/src/language/ty/declaration/declaration.rs +++ b/sway-core/src/language/ty/declaration/declaration.rs @@ -1,3 +1,11 @@ +use crate::{ + decl_engine::*, + engine_threading::*, + language::{parsed::Declaration, ty::*, Visibility}, + type_system::*, + types::*, +}; +use serde::{Deserialize, Serialize}; use std::{ fmt, hash::{Hash, Hasher}, @@ -9,15 +17,7 @@ use sway_error::{ }; use sway_types::{Ident, Named, Span, Spanned}; -use crate::{ - decl_engine::*, - engine_threading::*, - language::{parsed::Declaration, ty::*, Visibility}, - type_system::*, - types::*, -}; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyDecl { VariableDecl(Box), ConstantDecl(ConstantDecl), @@ -33,7 +33,7 @@ pub enum TyDecl { // If type parameters are defined for a function, they are put in the namespace just for // the body of that function. GenericTypeForFunctionScope(GenericTypeForFunctionScope), - ErrorRecovery(Span, ErrorEmitted), + ErrorRecovery(Span, #[serde(skip)] ErrorEmitted), StorageDecl(StorageDecl), TypeAliasDecl(TypeAliasDecl), } @@ -46,70 +46,70 @@ pub trait TyDeclParsedType { type ParsedType; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ConstantDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ConfigurableDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TraitTypeDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FunctionDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TraitDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct StructDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct EnumDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct EnumVariantDecl { pub enum_ref: DeclRefEnum, pub variant_name: Ident, pub variant_decl_span: Span, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ImplSelfOrTrait { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct AbiDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct GenericTypeForFunctionScope { pub name: Ident, pub type_id: TypeId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct StorageDecl { pub decl_id: DeclId, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TypeAliasDecl { pub decl_id: DeclId, } diff --git a/sway-core/src/language/ty/declaration/enum.rs b/sway-core/src/language/ty/declaration/enum.rs index 2ce5e3eae46..126fb4549a5 100644 --- a/sway-core/src/language/ty/declaration/enum.rs +++ b/sway-core/src/language/ty/declaration/enum.rs @@ -1,26 +1,23 @@ +use crate::{ + engine_threading::*, + has_changes, + language::{parsed::EnumDeclaration, ty::TyDeclParsedType, CallPath, Visibility}, + transform, + type_system::*, +}; +use monomorphization::MonomorphizeHelper; +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, hash::{Hash, Hasher}, }; - -use monomorphization::MonomorphizeHelper; use sway_error::{ error::CompileError, handler::{ErrorEmitted, Handler}, }; use sway_types::{Ident, Named, Span, Spanned}; -use crate::{ - engine_threading::*, - has_changes, - language::{parsed::EnumDeclaration, CallPath, Visibility}, - transform, - type_system::*, -}; - -use super::TyDeclParsedType; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyEnumDecl { pub call_path: CallPath, pub type_parameters: Vec, @@ -133,7 +130,7 @@ impl Spanned for TyEnumVariant { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyEnumVariant { pub name: Ident, pub type_argument: TypeArgument, diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index 8bb26f1f598..6849ccaa7db 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -1,37 +1,31 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, -}; - -use monomorphization::MonomorphizeHelper; -use sha2::{Digest, Sha256}; -use sway_error::handler::{ErrorEmitted, Handler}; - use crate::{ + decl_engine::*, + engine_threading::*, has_changes, + language::{parsed, ty::*, Inline, Purity, Visibility}, language::{ parsed::{FunctionDeclaration, FunctionDeclarationKind}, CallPath, }, - transform::AttributeKind, -}; - -use crate::{ - decl_engine::*, - engine_threading::*, - language::{parsed, ty::*, Inline, Purity, Visibility}, semantic_analysis::TypeCheckContext, - transform, + transform::{self, AttributeKind}, type_system::*, types::*, }; - +use monomorphization::MonomorphizeHelper; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::{ + fmt, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::{ constants::{INLINE_ALWAYS_NAME, INLINE_NEVER_NAME}, Ident, Named, Span, Spanned, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyFunctionDeclKind { Default, Entry, @@ -39,7 +33,7 @@ pub enum TyFunctionDeclKind { Test, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyFunctionDecl { pub name: Ident, pub body: TyCodeBlock, @@ -489,7 +483,7 @@ impl TyFunctionDecl { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyFunctionParameter { pub name: Ident, pub is_reference: bool, diff --git a/sway-core/src/language/ty/declaration/impl_trait.rs b/sway-core/src/language/ty/declaration/impl_trait.rs index 0c2d25aa5fd..6125eeb9569 100644 --- a/sway-core/src/language/ty/declaration/impl_trait.rs +++ b/sway-core/src/language/ty/declaration/impl_trait.rs @@ -1,7 +1,4 @@ -use std::hash::{Hash, Hasher}; - -use sway_types::{Ident, Named, Span, Spanned}; - +use super::{TyDeclParsedType, TyTraitItem}; use crate::{ decl_engine::DeclRefMixedInterface, engine_threading::*, @@ -9,13 +6,14 @@ use crate::{ language::{parsed::ImplSelfOrTrait, CallPath}, type_system::*, }; - -use super::{TyDeclParsedType, TyTraitItem}; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_types::{Ident, Named, Span, Spanned}; pub type TyImplItem = TyTraitItem; // impl Trait for Type -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyImplSelfOrTrait { pub impl_type_parameters: Vec, pub trait_name: CallPath, diff --git a/sway-core/src/language/ty/declaration/storage.rs b/sway-core/src/language/ty/declaration/storage.rs index abc5a67b20c..1036159a826 100644 --- a/sway-core/src/language/ty/declaration/storage.rs +++ b/sway-core/src/language/ty/declaration/storage.rs @@ -1,11 +1,3 @@ -use std::hash::{Hash, Hasher}; - -use sway_error::{ - error::{CompileError, StructFieldUsageContext}, - handler::{ErrorEmitted, Handler}, -}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ engine_threading::*, ir_generation::storage::get_storage_key_string, @@ -15,8 +7,15 @@ use crate::{ type_system::*, Namespace, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_error::{ + error::{CompileError, StructFieldUsageContext}, + handler::{ErrorEmitted, Handler}, +}; +use sway_types::{Ident, Named, Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStorageDecl { pub fields: Vec, pub span: Span, @@ -243,7 +242,7 @@ impl Spanned for TyStorageField { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStorageField { pub name: Ident, pub namespace_names: Vec, diff --git a/sway-core/src/language/ty/declaration/struct.rs b/sway-core/src/language/ty/declaration/struct.rs index c4dc7e3e219..e64281b1ea9 100644 --- a/sway-core/src/language/ty/declaration/struct.rs +++ b/sway-core/src/language/ty/declaration/struct.rs @@ -1,24 +1,21 @@ -use std::{ - cmp::Ordering, - hash::{Hash, Hasher}, -}; - -use monomorphization::MonomorphizeHelper; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ engine_threading::*, error::module_can_be_changed, has_changes, - language::{parsed::StructDeclaration, CallPath, Visibility}, + language::{parsed::StructDeclaration, ty::TyDeclParsedType, CallPath, Visibility}, transform, type_system::*, Namespace, }; +use monomorphization::MonomorphizeHelper; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + hash::{Hash, Hasher}, +}; +use sway_types::{Ident, Named, Span, Spanned}; -use super::TyDeclParsedType; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStructDecl { pub call_path: CallPath, pub fields: Vec, @@ -182,7 +179,7 @@ impl From for (bool, bool) { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyStructField { pub visibility: Visibility, pub name: Ident, diff --git a/sway-core/src/language/ty/declaration/trait.rs b/sway-core/src/language/ty/declaration/trait.rs index f0c7ee24ff3..14970814fe3 100644 --- a/sway-core/src/language/ty/declaration/trait.rs +++ b/sway-core/src/language/ty/declaration/trait.rs @@ -1,12 +1,3 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, -}; - -use monomorphization::MonomorphizeHelper; -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ decl_engine::{ DeclEngineReplace, DeclRefConstant, DeclRefFunction, DeclRefTraitFn, DeclRefTraitType, @@ -16,6 +7,7 @@ use crate::{ has_changes, language::{ parsed::{self, TraitDeclaration}, + ty::{TyDecl, TyDeclParsedType}, CallPath, Visibility, }, semantic_analysis::{ @@ -25,10 +17,16 @@ use crate::{ transform, type_system::*, }; +use monomorphization::MonomorphizeHelper; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Named, Span, Spanned}; -use super::{TyDecl, TyDeclParsedType}; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyTraitDecl { pub name: Ident, pub type_parameters: Vec, @@ -46,7 +44,7 @@ impl TyDeclParsedType for TyTraitDecl { type ParsedType = TraitDeclaration; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyTraitInterfaceItem { TraitFn(DeclRefTraitFn), Constant(DeclRefConstant), @@ -82,7 +80,7 @@ impl DebugWithEngines for TyTraitInterfaceItem { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyTraitItem { Fn(DeclRefFunction), Constant(DeclRefConstant), diff --git a/sway-core/src/language/ty/declaration/trait_type.rs b/sway-core/src/language/ty/declaration/trait_type.rs index 24925afa457..9c9cee3e5eb 100644 --- a/sway-core/src/language/ty/declaration/trait_type.rs +++ b/sway-core/src/language/ty/declaration/trait_type.rs @@ -1,18 +1,15 @@ +use crate::{ + engine_threading::*, has_changes, language::parsed::TraitTypeDeclaration, + language::ty::TyDeclParsedType, transform, type_system::*, +}; +use serde::{Deserialize, Serialize}; use std::{ fmt, hash::{Hash, Hasher}, }; - use sway_types::{Ident, Named, Span, Spanned}; -use crate::{ - engine_threading::*, has_changes, language::parsed::TraitTypeDeclaration, transform, - type_system::*, -}; - -use super::TyDeclParsedType; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyTraitType { pub name: Ident, pub attributes: transform::AttributesMap, diff --git a/sway-core/src/language/ty/declaration/type_alias.rs b/sway-core/src/language/ty/declaration/type_alias.rs index cb33c0d27bd..9c41fae8f50 100644 --- a/sway-core/src/language/ty/declaration/type_alias.rs +++ b/sway-core/src/language/ty/declaration/type_alias.rs @@ -1,17 +1,14 @@ -use std::hash::{Hash, Hasher}; - -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ engine_threading::*, - language::{parsed::TypeAliasDeclaration, CallPath, Visibility}, + language::{parsed::TypeAliasDeclaration, ty::TyDeclParsedType, CallPath, Visibility}, transform, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_types::{Ident, Named, Span, Spanned}; -use super::TyDeclParsedType; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyTypeAliasDecl { pub name: Ident, pub call_path: CallPath, diff --git a/sway-core/src/language/ty/declaration/variable.rs b/sway-core/src/language/ty/declaration/variable.rs index d7eb80ad7c6..5337651b8e5 100644 --- a/sway-core/src/language/ty/declaration/variable.rs +++ b/sway-core/src/language/ty/declaration/variable.rs @@ -1,14 +1,13 @@ -use std::hash::{Hash, Hasher}; - -use sway_types::{Ident, Named, Spanned}; - use crate::{ engine_threading::*, language::{parsed::VariableDeclaration, ty::*}, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_types::{Ident, Named, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyVariableDecl { pub name: Ident, pub body: TyExpression, diff --git a/sway-core/src/language/ty/expression/asm.rs b/sway-core/src/language/ty/expression/asm.rs index 04944125258..6e4ad95af3c 100644 --- a/sway-core/src/language/ty/expression/asm.rs +++ b/sway-core/src/language/ty/expression/asm.rs @@ -1,10 +1,9 @@ +use crate::{engine_threading::*, language::ty::*, type_system::*}; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; - use sway_types::Ident; -use crate::{engine_threading::*, language::ty::*, type_system::*}; - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyAsmRegisterDeclaration { pub initializer: Option, pub(crate) name: Ident, diff --git a/sway-core/src/language/ty/expression/contract.rs b/sway-core/src/language/ty/expression/contract.rs index 2bdec40421e..926aba9923c 100644 --- a/sway-core/src/language/ty/expression/contract.rs +++ b/sway-core/src/language/ty/expression/contract.rs @@ -1,6 +1,7 @@ use crate::language::ty::*; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ContractCallParams { // This is none in encoding V1 pub(crate) func_selector: Option<[u8; 4]>, diff --git a/sway-core/src/language/ty/expression/expression.rs b/sway-core/src/language/ty/expression/expression.rs index a0c70559b1d..4f369d3c269 100644 --- a/sway-core/src/language/ty/expression/expression.rs +++ b/sway-core/src/language/ty/expression/expression.rs @@ -1,13 +1,3 @@ -use std::{fmt, hash::Hasher}; - -use sway_error::{ - error::CompileError, - handler::{ErrorEmitted, Handler}, - type_error::TypeError, - warning::{CompileWarning, Warning}, -}; -use sway_types::{Span, Spanned}; - use crate::{ decl_engine::*, engine_threading::*, @@ -21,8 +11,17 @@ use crate::{ type_system::*, types::*, }; +use serde::{Deserialize, Serialize}; +use std::{fmt, hash::Hasher}; +use sway_error::{ + error::CompileError, + handler::{ErrorEmitted, Handler}, + type_error::TypeError, + warning::{CompileWarning, Warning}, +}; +use sway_types::{Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyExpression { pub expression: TyExpressionVariant, pub return_type: TypeId, diff --git a/sway-core/src/language/ty/expression/expression_variant.rs b/sway-core/src/language/ty/expression/expression_variant.rs index 9908bb09f5b..ea221466273 100644 --- a/sway-core/src/language/ty/expression/expression_variant.rs +++ b/sway-core/src/language/ty/expression/expression_variant.rs @@ -1,13 +1,3 @@ -use std::{ - collections::VecDeque, - fmt::{self, Write}, - hash::{Hash, Hasher}, -}; - -use indexmap::IndexMap; -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Named, Span, Spanned}; - use crate::{ decl_engine::*, engine_threading::*, @@ -20,8 +10,17 @@ use crate::{ }, type_system::*, }; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use std::{ + collections::VecDeque, + fmt::{self, Write}, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Named, Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyExpressionVariant { Literal(Literal), FunctionApplication { diff --git a/sway-core/src/language/ty/expression/intrinsic_function.rs b/sway-core/src/language/ty/expression/intrinsic_function.rs index 4fb4e72c3bc..d7646ecf64c 100644 --- a/sway-core/src/language/ty/expression/intrinsic_function.rs +++ b/sway-core/src/language/ty/expression/intrinsic_function.rs @@ -1,18 +1,18 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, -}; - use crate::{ abi_generation::abi_str::AbiStrContext, engine_threading::*, has_changes, language::ty::*, type_system::*, types::*, }; use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + hash::{Hash, Hasher}, +}; use sway_ast::Intrinsic; use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::Span; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyIntrinsicFunctionKind { pub kind: Intrinsic, pub arguments: Vec, diff --git a/sway-core/src/language/ty/expression/reassignment.rs b/sway-core/src/language/ty/expression/reassignment.rs index 54a8e740c5c..698af692dda 100644 --- a/sway-core/src/language/ty/expression/reassignment.rs +++ b/sway-core/src/language/ty/expression/reassignment.rs @@ -1,11 +1,3 @@ -use std::{ - borrow::Cow, - hash::{Hash, Hasher}, -}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Ident, Span, Spanned}; - use crate::{ decl_engine::*, engine_threading::*, @@ -17,14 +9,21 @@ use crate::{ }, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, +}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Ident, Span, Spanned}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyReassignment { pub lhs: TyReassignmentTarget, pub rhs: TyExpression, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum TyReassignmentTarget { /// An [TyExpression] representing a single variable or a path /// to a part of an aggregate. @@ -258,7 +257,7 @@ impl UpdateConstantExpression for TyReassignment { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum ProjectionKind { StructField { name: Ident, diff --git a/sway-core/src/language/ty/expression/scrutinee.rs b/sway-core/src/language/ty/expression/scrutinee.rs index 68c51af26eb..78fde994732 100644 --- a/sway-core/src/language/ty/expression/scrutinee.rs +++ b/sway-core/src/language/ty/expression/scrutinee.rs @@ -1,19 +1,19 @@ -use sway_types::{Ident, Span}; - use crate::{ decl_engine::{DeclRefEnum, DeclRefStruct}, language::{ty::*, *}, type_system::*, }; +use serde::{Deserialize, Serialize}; +use sway_types::{Ident, Span}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyScrutinee { pub variant: TyScrutineeVariant, pub type_id: TypeId, pub span: Span, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum TyScrutineeVariant { Or(Vec), CatchAll, @@ -36,7 +36,7 @@ pub enum TyScrutineeVariant { Tuple(Vec), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TyStructScrutineeField { pub field: Ident, pub scrutinee: Option, diff --git a/sway-core/src/language/ty/expression/storage.rs b/sway-core/src/language/ty/expression/storage.rs index 127d64fd20b..adaa6f28730 100644 --- a/sway-core/src/language/ty/expression/storage.rs +++ b/sway-core/src/language/ty/expression/storage.rs @@ -1,13 +1,11 @@ +use super::TyExpression; +use crate::{engine_threading::*, type_system::TypeId}; +use serde::{Deserialize, Serialize}; use std::hash::{Hash, Hasher}; - use sway_types::{Ident, Span, Spanned}; -use crate::{engine_threading::*, type_system::TypeId}; - -use super::TyExpression; - /// Describes the full storage access including all the subfields -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStorageAccess { pub fields: Vec, pub storage_field_names: Vec, @@ -64,7 +62,7 @@ impl TyStorageAccess { } /// Describes a single subfield access in the sequence when accessing a subfield within storage. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStorageAccessDescriptor { pub name: Ident, pub type_id: TypeId, diff --git a/sway-core/src/language/ty/expression/struct_exp_field.rs b/sway-core/src/language/ty/expression/struct_exp_field.rs index 5f879640fb3..7262f38a901 100644 --- a/sway-core/src/language/ty/expression/struct_exp_field.rs +++ b/sway-core/src/language/ty/expression/struct_exp_field.rs @@ -1,8 +1,3 @@ -use std::hash::{Hash, Hasher}; - -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::Ident; - use crate::{ decl_engine::*, engine_threading::*, @@ -10,8 +5,12 @@ use crate::{ semantic_analysis::{TypeCheckContext, TypeCheckFinalization, TypeCheckFinalizationContext}, type_system::*, }; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::Ident; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TyStructExpressionField { pub name: Ident, pub value: TyExpression, diff --git a/sway-core/src/language/ty/side_effect/include_statement.rs b/sway-core/src/language/ty/side_effect/include_statement.rs index 5d35f02b722..666e5b63a58 100644 --- a/sway-core/src/language/ty/side_effect/include_statement.rs +++ b/sway-core/src/language/ty/side_effect/include_statement.rs @@ -1,8 +1,8 @@ use crate::language::Visibility; - +use serde::{Deserialize, Serialize}; use sway_types::{ident::Ident, Span, Spanned}; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TyIncludeStatement { pub span: Span, pub visibility: Visibility, diff --git a/sway-core/src/language/ty/side_effect/side_effect.rs b/sway-core/src/language/ty/side_effect/side_effect.rs index 41f2a5b945b..9e8e672c5fa 100644 --- a/sway-core/src/language/ty/side_effect/side_effect.rs +++ b/sway-core/src/language/ty/side_effect/side_effect.rs @@ -1,11 +1,12 @@ use super::{TyIncludeStatement, TyUseStatement}; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TySideEffect { pub side_effect: TySideEffectVariant, } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum TySideEffectVariant { IncludeStatement(TyIncludeStatement), UseStatement(TyUseStatement), diff --git a/sway-core/src/language/ty/side_effect/use_statement.rs b/sway-core/src/language/ty/side_effect/use_statement.rs index 5d9c0cf1ca1..476a58907ef 100644 --- a/sway-core/src/language/ty/side_effect/use_statement.rs +++ b/sway-core/src/language/ty/side_effect/use_statement.rs @@ -1,7 +1,8 @@ use crate::language::parsed; +use serde::{Deserialize, Serialize}; use sway_types::{ident::Ident, Span, Spanned}; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct TyUseStatement { pub call_path: Vec, pub span: Span, diff --git a/sway-core/src/language/ty/variable_mutability.rs b/sway-core/src/language/ty/variable_mutability.rs index ba2532028b6..b1831af1279 100644 --- a/sway-core/src/language/ty/variable_mutability.rs +++ b/sway-core/src/language/ty/variable_mutability.rs @@ -1,6 +1,7 @@ use crate::language::Visibility; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub enum VariableMutability { // mutable Mutable, diff --git a/sway-core/src/language/visibility.rs b/sway-core/src/language/visibility.rs index a3838e06aff..e6b19912e7e 100644 --- a/sway-core/src/language/visibility.rs +++ b/sway-core/src/language/visibility.rs @@ -1,4 +1,6 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Visibility { Private, Public, diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index d59970de1b0..402a81d1242 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -1000,6 +1000,28 @@ pub fn compile_to_bytecode( ) } +/// Size of the prelude's CONFIGURABLES_OFFSET section, in bytes. +pub const PRELUDE_CONFIGURABLES_SIZE_IN_BYTES: usize = 8; +/// Offset (in bytes) of the CONFIGURABLES_OFFSET section in the prelude. +pub const PRELUDE_CONFIGURABLES_OFFSET_IN_BYTES: usize = 16; +/// Total size of the prelude in bytes. Instructions start right after. +pub const PRELUDE_SIZE_IN_BYTES: usize = 32; + +/// Given bytecode, overwrite the existing offset to configurables offset in the prelude with the given one. +pub fn set_bytecode_configurables_offset( + compiled_bytecode: &mut CompiledBytecode, + md: &[u8; PRELUDE_CONFIGURABLES_SIZE_IN_BYTES], +) { + assert!( + compiled_bytecode.bytecode.len() + >= PRELUDE_CONFIGURABLES_OFFSET_IN_BYTES + PRELUDE_CONFIGURABLES_SIZE_IN_BYTES + ); + let code = &mut compiled_bytecode.bytecode; + for (index, byte) in md.iter().enumerate() { + code[index + PRELUDE_CONFIGURABLES_OFFSET_IN_BYTES] = *byte; + } +} + /// Given the assembly (opcodes), compile to [CompiledBytecode], containing the asm in bytecode form. pub fn asm_to_bytecode( handler: &Handler, diff --git a/sway-core/src/semantic_analysis/ast_node/code_block.rs b/sway-core/src/semantic_analysis/ast_node/code_block.rs index c82523f9395..02cf5e581e7 100644 --- a/sway-core/src/semantic_analysis/ast_node/code_block.rs +++ b/sway-core/src/semantic_analysis/ast_node/code_block.rs @@ -11,15 +11,20 @@ impl ty::TyCodeBlock { ctx: &mut SymbolCollectionContext, code_block: &CodeBlock, ) -> Result<(), ErrorEmitted> { - let _ = ctx.scoped(engines, code_block.whole_block_span.clone(), |scoped_ctx| { - let _ = code_block - .contents - .iter() - .map(|node| ty::TyAstNode::collect(handler, engines, scoped_ctx, node)) - .filter_map(|res| res.ok()) - .collect::>(); - Ok(()) - }); + let _ = ctx.scoped( + engines, + code_block.whole_block_span.clone(), + None, + |scoped_ctx| { + let _ = code_block + .contents + .iter() + .map(|node| ty::TyAstNode::collect(handler, engines, scoped_ctx, node)) + .filter_map(|res| res.ok()) + .collect::>(); + Ok(()) + }, + ); Ok(()) } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs b/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs index 0274195845b..30db0276496 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/abi.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use sway_error::error::CompileError; use sway_types::{Ident, Named, Span, Spanned}; @@ -39,14 +39,10 @@ impl ty::TyAbiDecl { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let abi_decl = engines.pe().get_abi(decl_id); - ctx.insert_parsed_symbol( - handler, - engines, - abi_decl.name.clone(), - Declaration::AbiDeclaration(*decl_id), - )?; + let decl = Declaration::AbiDeclaration(*decl_id); + ctx.insert_parsed_symbol(handler, engines, abi_decl.name.clone(), decl.clone())?; - let _ = ctx.scoped(engines, abi_decl.span.clone(), |scoped_ctx| { + let _ = ctx.scoped(engines, abi_decl.span.clone(), Some(decl), |scoped_ctx| { abi_decl.interface_surface.iter().for_each(|item| { let _ = TyTraitItem::collect(handler, engines, scoped_ctx, item); }); @@ -163,31 +159,19 @@ impl ty::TyAbiDecl { ctx.engines.de().insert(const_decl.clone(), Some(&decl_id)); new_interface_surface .push(ty::TyTraitInterfaceItem::Constant(decl_ref.clone())); - - let const_name = const_decl.call_path.suffix.clone(); - ctx.insert_symbol( - handler, - const_name.clone(), - ty::TyDecl::ConstantDecl(ty::ConstantDecl { - decl_id: *decl_ref.id(), - }), - )?; - - const_name + const_decl.call_path.suffix.clone() } TraitItem::Type(decl_id) => { let type_decl = engines.pe().get_trait_type(&decl_id).as_ref().clone(); handler.emit_err(CompileError::AssociatedTypeNotSupportedInAbi { span: type_decl.span.clone(), }); - let type_decl = ty::TyTraitType::type_check(handler, ctx.by_ref(), type_decl)?; let decl_ref = ctx.engines().de().insert(type_decl.clone(), Some(&decl_id)); new_interface_surface .push(ty::TyTraitInterfaceItem::Type(decl_ref.clone())); - type_decl.name } TraitItem::Error(_, _) => { @@ -276,6 +260,8 @@ impl ty::TyAbiDecl { (false, Span::dummy()) }; + let mut const_symbols = HashMap::::new(); + handler.scope(|handler| { for item in interface_surface.iter() { match item { @@ -339,9 +325,8 @@ impl ty::TyAbiDecl { let const_decl = decl_engine.get_constant(decl_ref); let const_name = const_decl.call_path.suffix.clone(); all_items.push(TyImplItem::Constant(decl_ref.clone())); - let _ = ctx.insert_symbol( - handler, - const_name.clone(), + const_symbols.insert( + const_name, ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id: *decl_ref.id(), }), @@ -397,10 +382,24 @@ impl ty::TyAbiDecl { } ty::TyTraitItem::Constant(decl_ref) => { let const_decl = decl_engine.get_constant(decl_ref); - all_items.push(TyImplItem::Constant(decl_engine.insert_arc( + let const_name = const_decl.name().clone(); + let const_has_value = const_decl.value.is_some(); + let decl_id = decl_engine.insert_arc( const_decl, decl_engine.get_parsed_decl_id(decl_ref.id()).as_ref(), - ))); + ); + all_items.push(TyImplItem::Constant(decl_id.clone())); + + // If this non-interface item has a value, then we want to overwrite the + // the previously inserted constant symbol from the interface surface. + if const_has_value { + const_symbols.insert( + const_name, + ty::TyDecl::ConstantDecl(ty::ConstantDecl { + decl_id: *decl_id.id(), + }), + ); + } } ty::TyTraitItem::Type(decl_ref) => { let type_decl = decl_engine.get_type(decl_ref); @@ -411,6 +410,12 @@ impl ty::TyAbiDecl { } } } + + // Insert the constants into the namespace. + for (name, decl) in const_symbols.into_iter() { + let _ = ctx.insert_symbol(handler, name, decl); + } + // Insert the methods of the ABI into the namespace. // Specifically do not check for conflicting definitions because // this is just a temporary namespace for type checking and diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs index 98def2c187f..9efcc2392d3 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/auto_impl.rs @@ -8,14 +8,14 @@ use crate::{ Purity, }, semantic_analysis::TypeCheckContext, - Engines, TypeId, TypeInfo, TypeParameter, + Engines, TypeArgument, TypeInfo, TypeParameter, }; use sway_error::{ error::CompileError, handler::{ErrorEmitted, Handler}, }; use sway_parse::Parse; -use sway_types::{integer_bits::IntegerBits, BaseIdent, Named, ProgramId, Span, Spanned}; +use sway_types::{BaseIdent, Named, ProgramId, Span, Spanned}; /// Contains all information needed to implement AbiEncode pub struct EncodingAutoImplContext<'a, 'b> @@ -38,7 +38,7 @@ where T: Parse, { // Uncomment this to see what is being generated - //println!("{}", input); + // println!("{}", input); let handler = <_>::default(); let source_id = @@ -195,7 +195,7 @@ where code.push_str(&format!( "{field_name}: buffer.decode::<{field_type_name}>(),", field_name = f.name.as_str(), - field_type_name = Self::generate_type(engines, f.type_argument.type_id)?, + field_type_name = Self::generate_type(engines, &f.type_argument)?, )); } @@ -217,7 +217,7 @@ where format!("{} => {}::{}, \n", x.tag, enum_name, name) }, _ => { - let variant_type_name = Self::generate_type(engines, x.type_argument.type_id)?; + let variant_type_name = Self::generate_type(engines, &x.type_argument)?; format!("{tag_value} => {enum_name}::{variant_name}(buffer.decode::<{variant_type}>()), \n", tag_value = x.tag, enum_name = enum_name, @@ -496,92 +496,15 @@ where } } - fn generate_type(engines: &Engines, type_id: TypeId) -> Option { - let name = match &*engines.te().get(type_id) { - TypeInfo::UnknownGeneric { name, .. } => name.to_string(), - TypeInfo::Placeholder(type_param) => type_param.name.to_string(), - TypeInfo::StringSlice => "str".into(), - TypeInfo::StringArray(x) => format!("str[{}]", x.val()), - TypeInfo::UnsignedInteger(x) => match x { - IntegerBits::Eight => "u8", - IntegerBits::Sixteen => "u16", - IntegerBits::ThirtyTwo => "u32", - IntegerBits::SixtyFour => "u64", - IntegerBits::V256 => "u256", - } - .into(), - TypeInfo::Boolean => "bool".into(), - TypeInfo::Custom { - qualified_call_path: call_path, - .. - } => call_path.call_path.suffix.to_string(), - TypeInfo::Tuple(fields) => { - if fields.is_empty() { - return Some("()".into()); - } - let field_strs = fields - .iter() - .map(|field| Self::generate_type(engines, field.type_id)) - .collect::>>()?; - format!("({},)", field_strs.join(", ")) - } - TypeInfo::B256 => "b256".into(), - TypeInfo::Enum(decl_id) => { - let decl = engines.de().get_enum(decl_id); - - let type_parameters = decl - .type_parameters - .iter() - .map(|x| Self::generate_type(engines, x.type_id)) - .collect::>>()? - .join(", "); - - let type_parameters = if !type_parameters.is_empty() { - format!("<{type_parameters}>") - } else { - type_parameters - }; - - format!("{}{type_parameters}", decl.call_path.suffix.as_str()) - } - TypeInfo::Struct(decl_id) => { - let decl = engines.de().get(decl_id); - - let type_parameters = decl - .type_parameters - .iter() - .map(|x| Self::generate_type(engines, x.type_id)) - .collect::>>()? - .join(", "); - - let type_parameters = if !type_parameters.is_empty() { - format!("<{type_parameters}>") - } else { - type_parameters - }; - - format!("{}{type_parameters}", decl.call_path.suffix.as_str()) - } - TypeInfo::Array(elem_ty, count) => { - format!( - "[{}; {}]", - Self::generate_type(engines, elem_ty.type_id)?, - count.val() - ) - } - TypeInfo::Slice(elem_ty) => { - format!( - "__slice[{}]", - Self::generate_type(engines, elem_ty.type_id)? - ) - } - TypeInfo::RawUntypedPtr => "raw_ptr".into(), - TypeInfo::RawUntypedSlice => "raw_slice".into(), - TypeInfo::Alias { name, .. } => name.to_string(), - _ => return None, - }; - - Some(name) + // The safest way would be to return a canonical fully qualified type path. + // We do not have a way to do this at the moment, so the best way is to use + // exactly what was typed by the user, to accommodate aliased imports. + fn generate_type(engines: &Engines, t: &TypeArgument) -> Option { + match &*engines.te().get(t.type_id) { + // when a function does not define a return type, the span points to the whole signature. + TypeInfo::Tuple(v) if v.is_empty() => Some("()".into()), + _ => Some(t.span().as_str().to_string()), + } } pub(crate) fn generate_contract_entry( @@ -613,7 +536,7 @@ where let Some(args_types) = decl .parameters .iter() - .map(|x| Self::generate_type(engines, x.type_argument.type_id)) + .map(|x| Self::generate_type(engines, &x.type_argument)) .collect::>>() else { let err = handler.emit_err(CompileError::UnknownType { @@ -638,7 +561,7 @@ where ) .collect::(); - let Some(return_type) = Self::generate_type(engines, decl.return_type.type_id) else { + let Some(return_type) = Self::generate_type(engines, &decl.return_type) else { let err = handler.emit_err(CompileError::UnknownType { span: Span::dummy(), }); @@ -674,8 +597,7 @@ where let fallback = if let Some(fallback_fn) = fallback_fn { let fallback_fn = engines.de().get(&fallback_fn); - let Some(return_type) = Self::generate_type(engines, fallback_fn.return_type.type_id) - else { + let Some(return_type) = Self::generate_type(engines, &fallback_fn.return_type) else { let err = handler.emit_err(CompileError::UnknownType { span: Span::dummy(), }); @@ -743,7 +665,7 @@ where let Some(args_types) = decl .parameters .iter() - .map(|x| Self::generate_type(engines, x.type_argument.type_id)) + .map(|x| Self::generate_type(engines, &x.type_argument)) .collect::>>() else { let err = handler.emit_err(CompileError::UnknownType { @@ -836,7 +758,7 @@ where let Some(args_types) = decl .parameters .iter() - .map(|x| Self::generate_type(engines, x.type_argument.type_id)) + .map(|x| Self::generate_type(engines, &x.type_argument)) .collect::>>() else { let err = handler.emit_err(CompileError::UnknownType { @@ -860,7 +782,7 @@ where ) .collect::(); - let Some(return_type) = Self::generate_type(engines, decl.return_type.type_id) else { + let Some(return_type) = Self::generate_type(engines, &decl.return_type) else { let err = handler.emit_err(CompileError::UnknownType { span: Span::dummy(), }); diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs index b063fd53028..6047c8776aa 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs @@ -187,26 +187,20 @@ impl TyDecl { // save decl_refs for the LSP for supertrait in trait_decl.supertraits.iter_mut() { - let _ = ctx - .namespace() - .resolve_call_path_typed( - handler, - engines, - &supertrait.name, - ctx.self_type(), - ) - .map(|supertrait_decl| { - if let ty::TyDecl::TraitDecl(ty::TraitDecl { - decl_id: supertrait_decl_id, - }) = supertrait_decl - { - supertrait.decl_ref = Some(DeclRef::new( - engines.de().get(&supertrait_decl_id).name.clone(), - supertrait_decl_id, - engines.de().get(&supertrait_decl_id).span.clone(), - )); - } - }); + let _ = + ctx.resolve_call_path(handler, &supertrait.name) + .map(|supertrait_decl| { + if let ty::TyDecl::TraitDecl(ty::TraitDecl { + decl_id: supertrait_decl_id, + }) = supertrait_decl + { + supertrait.decl_ref = Some(DeclRef::new( + engines.de().get(&supertrait_decl_id).name.clone(), + supertrait_decl_id, + engines.de().get(&supertrait_decl_id).span.clone(), + )); + } + }); } let decl: ty::TyDecl = decl_engine @@ -301,12 +295,7 @@ impl TyDecl { // Choose which items are going to be visible depending if this is an abi impl // or trait impl - let t = ctx.namespace().resolve_call_path_typed( - &Handler::default(), - engines, - &impl_trait.trait_name, - ctx.self_type(), - ); + let t = ctx.resolve_call_path(&Handler::default(), &impl_trait.trait_name); let empty_vec = vec![]; let impl_trait_items = if let Ok(ty::TyDecl::TraitDecl { .. }) = t { @@ -369,26 +358,20 @@ impl TyDecl { // save decl_refs for the LSP for supertrait in abi_decl.supertraits.iter_mut() { - let _ = ctx - .namespace() - .resolve_call_path_typed( - handler, - engines, - &supertrait.name, - ctx.self_type(), - ) - .map(|supertrait_decl| { - if let ty::TyDecl::TraitDecl(ty::TraitDecl { - decl_id: supertrait_decl_id, - }) = supertrait_decl - { - supertrait.decl_ref = Some(DeclRef::new( - engines.de().get(&supertrait_decl_id).name.clone(), - supertrait_decl_id, - engines.de().get(&supertrait_decl_id).span.clone(), - )); - } - }); + let _ = + ctx.resolve_call_path(handler, &supertrait.name) + .map(|supertrait_decl| { + if let ty::TyDecl::TraitDecl(ty::TraitDecl { + decl_id: supertrait_decl_id, + }) = supertrait_decl + { + supertrait.decl_ref = Some(DeclRef::new( + engines.de().get(&supertrait_decl_id).name.clone(), + supertrait_decl_id, + engines.de().get(&supertrait_decl_id).span.clone(), + )); + } + }); } let decl: ty::TyDecl = decl_engine.insert(abi_decl.clone(), Some(&decl_id)).into(); diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/enum.rs b/sway-core/src/semantic_analysis/ast_node/declaration/enum.rs index 5e4839c0172..5f564978960 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/enum.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/enum.rs @@ -16,15 +16,13 @@ impl ty::TyEnumDecl { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let enum_decl = engines.pe().get_enum(decl_id); - ctx.insert_parsed_symbol( - handler, - engines, - enum_decl.name.clone(), - Declaration::EnumDeclaration(*decl_id), - )?; + let decl = Declaration::EnumDeclaration(*decl_id); + ctx.insert_parsed_symbol(handler, engines, enum_decl.name.clone(), decl.clone())?; // create a namespace for the decl, used to create a scope for generics - let _ = ctx.scoped(engines, enum_decl.span.clone(), |mut _ctx| Ok(())); + let _ = ctx.scoped(engines, enum_decl.span.clone(), Some(decl), |mut _ctx| { + Ok(()) + }); Ok(()) } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 69603909545..2b1fcd5d5a2 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -28,15 +28,11 @@ impl ty::TyFunctionDecl { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let fn_decl = engines.pe().get_function(decl_id); - let _ = ctx.insert_parsed_symbol( - handler, - engines, - fn_decl.name.clone(), - Declaration::FunctionDeclaration(*decl_id), - ); + let decl = Declaration::FunctionDeclaration(*decl_id); + let _ = ctx.insert_parsed_symbol(handler, engines, fn_decl.name.clone(), decl.clone()); // create a namespace for the function - let _ = ctx.scoped(engines, fn_decl.span.clone(), |scoped_ctx| { + let _ = ctx.scoped(engines, fn_decl.span.clone(), Some(decl), |scoped_ctx| { TyCodeBlock::collect(handler, engines, scoped_ctx, &fn_decl.body) }); Ok(()) diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs index 72320202ba2..f881cbbef60 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/impl_trait.rs @@ -9,7 +9,7 @@ use sway_error::{ error::{CompileError, InterfaceName}, handler::{ErrorEmitted, Handler}, }; -use sway_types::{Ident, Span, Spanned}; +use sway_types::{Ident, Named, Span, Spanned}; use crate::{ decl_engine::{parsed_id::ParsedDeclId, *}, @@ -17,12 +17,12 @@ use crate::{ language::{ parsed::*, ty::{ - self, TyConstantDecl, TyDecl, TyFunctionDecl, TyImplItem, TyImplSelfOrTrait, - TyTraitInterfaceItem, TyTraitItem, TyTraitType, + self, ConstantDecl, TyConstantDecl, TyDecl, TyFunctionDecl, TyImplItem, + TyImplSelfOrTrait, TyTraitInterfaceItem, TyTraitItem, TyTraitType, }, *, }, - namespace::{IsExtendingExistingImpl, IsImplSelf, TryInsertingTraitImplOnFailure}, + namespace::{IsExtendingExistingImpl, IsImplSelf, TraitMap, TryInsertingTraitImplOnFailure}, semantic_analysis::{ symbol_collection_context::SymbolCollectionContext, AbiMode, ConstShadowingMode, TyNodeDepGraphNodeId, TypeCheckAnalysis, TypeCheckAnalysisContext, TypeCheckContext, @@ -39,27 +39,34 @@ impl TyImplSelfOrTrait { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let impl_trait = engines.pe().get_impl_self_or_trait(decl_id); + + let decl = Declaration::ImplSelfOrTrait(*decl_id); ctx.insert_parsed_symbol( handler, engines, impl_trait.trait_name.suffix.clone(), - Declaration::ImplSelfOrTrait(*decl_id), + decl.clone(), )?; - let _ = ctx.scoped(engines, impl_trait.block_span.clone(), |scoped_ctx| { - impl_trait.items.iter().for_each(|item| match item { - ImplItem::Fn(decl_id) => { - let _ = TyFunctionDecl::collect(handler, engines, scoped_ctx, decl_id); - } - ImplItem::Constant(decl_id) => { - let _ = TyConstantDecl::collect(handler, engines, scoped_ctx, decl_id); - } - ImplItem::Type(decl_id) => { - let _ = TyTraitType::collect(handler, engines, scoped_ctx, decl_id); - } - }); - Ok(()) - }); + let _ = ctx.scoped( + engines, + impl_trait.block_span.clone(), + Some(decl), + |scoped_ctx| { + impl_trait.items.iter().for_each(|item| match item { + ImplItem::Fn(decl_id) => { + let _ = TyFunctionDecl::collect(handler, engines, scoped_ctx, decl_id); + } + ImplItem::Constant(decl_id) => { + let _ = TyConstantDecl::collect(handler, engines, scoped_ctx, decl_id); + } + ImplItem::Type(decl_id) => { + let _ = TyTraitType::collect(handler, engines, scoped_ctx, decl_id); + } + }); + Ok(()) + }, + ); Ok(()) } @@ -165,11 +172,7 @@ impl TyImplSelfOrTrait { .with_type_annotation(type_engine.new_unknown()) .with_self_type(Some(implementing_for.type_id)); - let impl_trait = match ctx - .namespace() - .resolve_call_path_typed(handler, engines, &trait_name, ctx.self_type()) - .ok() - { + let impl_trait = match ctx.resolve_call_path(handler, &trait_name).ok() { Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => { let mut trait_decl = (*decl_engine.get_trait(&decl_id)).clone(); @@ -695,20 +698,19 @@ fn type_check_trait_implementation( ctx.namespace_mut() .module_mut(engines) .write(engines, |m| { - m.current_items_mut() - .implemented_traits - .check_if_trait_constraints_are_satisfied_for_type( - handler, - implementing_for, - &trait_supertraits - .iter() - .map(|x| x.into()) - .collect::>(), - block_span, - engines, - TryInsertingTraitImplOnFailure::Yes, - code_block_first_pass.into(), - ) + TraitMap::check_if_trait_constraints_are_satisfied_for_type( + handler, + m, + implementing_for, + &trait_supertraits + .iter() + .map(|x| x.into()) + .collect::>(), + block_span, + engines, + TryInsertingTraitImplOnFailure::Yes, + code_block_first_pass.into(), + ) })?; for (type_arg, type_param) in trait_type_arguments.iter().zip(trait_type_parameters) { @@ -893,6 +895,54 @@ fn type_check_trait_implementation( } } + for item in impl_items { + match item { + ImplItem::Fn(_impl_method_id) => {} + ImplItem::Constant(decl_id) => { + let const_decl = engines.pe().get_constant(decl_id).as_ref().clone(); + let mut const_decl = type_check_const_decl( + handler, + ctx.by_ref().with_type_subst(&trait_type_mapping), + &const_decl, + trait_name, + is_contract, + &impld_item_refs, + &constant_checklist, + ) + .unwrap_or_else(|_| ty::TyConstantDecl::error(ctx.engines(), const_decl.clone())); + + const_decl.subst(&SubstTypesContext::new( + engines, + &trait_type_mapping, + !ctx.code_block_first_pass(), + )); + + // Remove this constant from the checklist. + let name = const_decl.call_path.suffix.clone(); + constant_checklist.remove(&name); + + // Add this constant to the "impld decls". + let decl_ref = decl_engine.insert(const_decl, Some(decl_id)); + impld_item_refs.insert( + (name.clone(), implementing_for), + TyTraitItem::Constant(decl_ref.clone()), + ); + + let prev_const_shadowing_mode = ctx.const_shadowing_mode; + ctx.const_shadowing_mode = ConstShadowingMode::Allow; + let _ = ctx.insert_symbol( + handler, + name, + TyDecl::ConstantDecl(ConstantDecl { + decl_id: *decl_ref.id(), + }), + ); + ctx.const_shadowing_mode = prev_const_shadowing_mode; + } + ImplItem::Type(_) => {} + } + } + for item in impl_items { match item { ImplItem::Fn(impl_method_id) => { @@ -924,33 +974,7 @@ fn type_check_trait_implementation( let decl_ref = decl_engine.insert(impl_method, Some(impl_method_id)); impld_item_refs.insert((name, implementing_for), TyTraitItem::Fn(decl_ref)); } - ImplItem::Constant(decl_id) => { - let const_decl = engines.pe().get_constant(decl_id).as_ref().clone(); - let mut const_decl = type_check_const_decl( - handler, - ctx.by_ref().with_type_subst(&trait_type_mapping), - &const_decl, - trait_name, - is_contract, - &impld_item_refs, - &constant_checklist, - ) - .unwrap_or_else(|_| ty::TyConstantDecl::error(ctx.engines(), const_decl.clone())); - - const_decl.subst(&SubstTypesContext::new( - engines, - &trait_type_mapping, - !ctx.code_block_first_pass(), - )); - - // Remove this constant from the checklist. - let name = const_decl.call_path.suffix.clone(); - constant_checklist.remove(&name); - - // Add this constant to the "impld decls". - let decl_ref = decl_engine.insert(const_decl, Some(decl_id)); - impld_item_refs.insert((name, implementing_for), TyTraitItem::Constant(decl_ref)); - } + ImplItem::Constant(_decl_id) => {} ImplItem::Type(_) => {} } } @@ -1310,6 +1334,50 @@ fn type_check_impl_method( }) } +fn trait_const_item_value( + trait_decl: TyDecl, + engines: &Engines, + const_decl: &TyConstantDecl, +) -> Option { + fn get_const_decl_from_trait_items( + engines: &Engines, + items: &[TyTraitInterfaceItem], + name: &Ident, + ) -> Option> { + for item in items.iter() { + match item { + TyTraitInterfaceItem::Constant(decl) => { + let const_decl = engines.de().get_constant(decl.id()); + if const_decl.name() == name { + return Some(const_decl); + } + } + _ => continue, + } + } + None + } + + let trait_or_abi_const_decl = match trait_decl { + TyDecl::TraitDecl(decl) => get_const_decl_from_trait_items( + engines, + &engines.de().get_trait(&decl.decl_id).interface_surface, + const_decl.name(), + ), + TyDecl::AbiDecl(decl) => get_const_decl_from_trait_items( + engines, + &engines.de().get_abi(&decl.decl_id).interface_surface, + const_decl.name(), + ), + _ => unreachable!(), + }; + + match trait_or_abi_const_decl { + Some(trait_or_abi_const_decl) => trait_or_abi_const_decl.value.clone(), + None => None, + } +} + #[allow(clippy::too_many_arguments)] fn type_check_const_decl( handler: &Handler, @@ -1341,10 +1409,25 @@ fn type_check_const_decl( }; // type check the constant declaration - let const_decl = ty::TyConstantDecl::type_check(handler, ctx.by_ref(), const_decl.clone())?; + let mut const_decl = ty::TyConstantDecl::type_check(handler, ctx.by_ref(), const_decl.clone())?; let const_name = const_decl.call_path.suffix.clone(); + // Ensure that there is an expression if the constant in the base trait is not defined. + let trait_decl = ctx.resolve_call_path(handler, trait_name)?; + + let trait_const_item_value = trait_const_item_value(trait_decl, engines, &const_decl); + if trait_const_item_value.is_none() && const_decl.value.is_none() { + return Err(handler.emit_err(CompileError::ConstantRequiresExpression { + span: const_decl.span.clone(), + })); + } + + // Ensure the constant decl has a value, if none was set then it inherits the base trait/abi value. + if const_decl.value.is_none() { + const_decl.value = trait_const_item_value; + } + // Ensure that there aren't multiple definitions of this constant if impld_constant_ids.contains_key(&(const_name.clone(), self_type_id)) { return Err( @@ -1567,14 +1650,8 @@ fn handle_supertraits( } match ctx - .namespace() // Use the default Handler to avoid emitting the redundant SymbolNotFound error. - .resolve_call_path_typed( - &Handler::default(), - engines, - &supertrait.name, - ctx.self_type(), - ) + .resolve_call_path(&Handler::default(), &supertrait.name) .ok() { Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => { diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/struct.rs b/sway-core/src/semantic_analysis/ast_node/declaration/struct.rs index 4ad0a268284..18e7a6e394e 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/struct.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/struct.rs @@ -16,15 +16,16 @@ impl ty::TyStructDecl { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let struct_decl = engines.pe().get_struct(decl_id); - ctx.insert_parsed_symbol( - handler, - engines, - struct_decl.name.clone(), - Declaration::StructDeclaration(*decl_id), - )?; + let decl = Declaration::StructDeclaration(*decl_id); + ctx.insert_parsed_symbol(handler, engines, struct_decl.name.clone(), decl.clone())?; // create a namespace for the decl, used to create a scope for generics - let _ = ctx.scoped(engines, struct_decl.span.clone(), |_scoped_ctx| Ok(())); + let _ = ctx.scoped( + engines, + struct_decl.span.clone(), + Some(decl), + |_scoped_ctx| Ok(()), + ); Ok(()) } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/supertrait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/supertrait.rs index d74932eea9e..0a791301737 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/supertrait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/supertrait.rs @@ -53,14 +53,8 @@ pub(crate) fn insert_supertraits_into_namespace( } let decl = ctx - .namespace() // Use the default Handler to avoid emitting the redundant SymbolNotFound error. - .resolve_call_path_typed( - &Handler::default(), - engines, - &supertrait.name, - ctx.self_type(), - ) + .resolve_call_path(&Handler::default(), &supertrait.name) .ok(); match (decl.clone(), supertraits_of) { diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs index 2f4504c1b90..e25a568cb3d 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use parsed_id::ParsedDeclId; use sway_error::{ @@ -6,7 +6,7 @@ use sway_error::{ handler::{ErrorEmitted, Handler}, warning::{CompileWarning, Warning}, }; -use sway_types::{style::is_upper_camel_case, Ident, Spanned}; +use sway_types::{style::is_upper_camel_case, Ident, Named, Spanned}; use crate::{ decl_engine::*, @@ -53,15 +53,11 @@ impl TyTraitDecl { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let trait_decl = engines.pe().get_trait(decl_id); - ctx.insert_parsed_symbol( - handler, - engines, - trait_decl.name.clone(), - Declaration::TraitDeclaration(*decl_id), - )?; + let decl = Declaration::TraitDeclaration(*decl_id); + ctx.insert_parsed_symbol(handler, engines, trait_decl.name.clone(), decl.clone())?; // A temporary namespace for checking within the trait's scope. - let _ = ctx.scoped(engines, trait_decl.span.clone(), |scoped_ctx| { + let _ = ctx.scoped(engines, trait_decl.span.clone(), Some(decl), |scoped_ctx| { trait_decl.interface_surface.iter().for_each(|item| { let _ = TyTraitItem::collect(handler, engines, scoped_ctx, item); }); @@ -510,6 +506,8 @@ impl TyTraitDecl { .collect(), ); + let mut const_symbols = HashMap::::new(); + for item in interface_surface.iter() { match item { ty::TyTraitInterfaceItem::TraitFn(decl_ref) => { @@ -527,11 +525,10 @@ impl TyTraitDecl { } ty::TyTraitInterfaceItem::Constant(decl_ref) => { let const_decl = decl_engine.get_constant(decl_ref); - let const_name = const_decl.call_path.suffix.clone(); all_items.push(TyImplItem::Constant(decl_ref.clone())); - let _ = ctx.insert_symbol( - handler, - const_name.clone(), + let const_name = const_decl.call_path.suffix.clone(); + const_symbols.insert( + const_name, ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id: *decl_ref.id(), }), @@ -568,10 +565,24 @@ impl TyTraitDecl { &type_mapping, !ctx.code_block_first_pass(), )); - all_items.push(TyImplItem::Constant(decl_engine.insert( + let const_name = const_decl.name().clone(); + let const_has_value = const_decl.value.is_some(); + let decl_id = decl_engine.insert( const_decl, decl_engine.get_parsed_decl_id(decl_ref.id()).as_ref(), - ))); + ); + all_items.push(TyImplItem::Constant(decl_id.clone())); + + // If this non-interface item has a value, then we want to overwrite the + // the previously inserted constant symbol from the interface surface. + if const_has_value { + const_symbols.insert( + const_name, + ty::TyDecl::ConstantDecl(ty::ConstantDecl { + decl_id: *decl_id.id(), + }), + ); + } } ty::TyTraitItem::Type(decl_ref) => { let mut type_decl = (*decl_engine.get_type(decl_ref)).clone(); @@ -588,6 +599,11 @@ impl TyTraitDecl { } } + // Insert the constants into the namespace. + for (name, decl) in const_symbols.into_iter() { + let _ = ctx.insert_symbol(handler, name, decl); + } + // Insert the methods of the trait into the namespace. // Specifically do not check for conflicting definitions because // this is just a temporary namespace for type checking and diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs index fc2b0fc6c3f..0777b17bcf5 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs @@ -24,13 +24,11 @@ impl ty::TyTraitFn { decl_id: &ParsedDeclId, ) -> Result<(), ErrorEmitted> { let trait_fn = engines.pe().get_trait_fn(decl_id); - ctx.insert_parsed_symbol( - handler, - engines, - trait_fn.name.clone(), - Declaration::TraitFnDeclaration(*decl_id), - )?; - let _ = ctx.scoped(engines, trait_fn.span.clone(), |_scoped_ctx| Ok(())); + let decl = Declaration::TraitFnDeclaration(*decl_id); + ctx.insert_parsed_symbol(handler, engines, trait_fn.name.clone(), decl.clone())?; + let _ = ctx.scoped(engines, trait_fn.span.clone(), Some(decl), |_scoped_ctx| { + Ok(()) + }); Ok(()) } diff --git a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_match_branch.rs b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_match_branch.rs index e00b968c4ce..09ac73392db 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_match_branch.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_match_branch.rs @@ -92,9 +92,8 @@ impl ty::TyMatchBranch { for (ident, is_struct_field) in variables { let default_handler = &Handler::default(); // If there exist a configurable with the same name as the pattern variable. - if let Ok(ty::TyDecl::ConfigurableDecl(configurable_decl)) = ctx - .namespace() - .resolve_symbol_typed(default_handler, engines, &ident, ctx.self_type()) + if let Ok(ty::TyDecl::ConfigurableDecl(configurable_decl)) = + ctx.resolve_symbol(default_handler, &ident) { let name = (&ident).into(); let configurable_span = engines diff --git a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_scrutinee.rs b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_scrutinee.rs index f81916fa337..acfec750058 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_scrutinee.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/typed/typed_scrutinee.rs @@ -155,11 +155,7 @@ fn type_check_variable( let type_engine = engines.te(); let decl_engine = engines.de(); - let typed_scrutinee = match ctx - .namespace() - .resolve_symbol_typed(&Handler::default(), engines, &name, ctx.self_type()) - .ok() - { + let typed_scrutinee = match ctx.resolve_symbol(&Handler::default(), &name).ok() { // If the name represents a constant, then we turn it into a [ty::TyScrutineeVariant::Constant]. Some(ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id, .. })) => { let constant_decl = (*decl_engine.get_constant(&decl_id)).clone(); @@ -219,9 +215,7 @@ fn type_check_struct( let decl_engine = engines.de(); // find the struct definition from the name - let unknown_decl = - ctx.namespace() - .resolve_symbol_typed(handler, engines, &struct_name, ctx.self_type())?; + let unknown_decl = ctx.resolve_symbol(handler, &struct_name)?; let struct_id = unknown_decl.to_struct_decl(handler, ctx.engines())?; let mut struct_decl = (*decl_engine.get_struct(&struct_id)).clone(); @@ -484,23 +478,13 @@ fn type_check_enum( is_absolute: call_path.is_absolute, }; // find the enum definition from the name - let unknown_decl = ctx.namespace().resolve_call_path_typed( - handler, - engines, - &enum_callpath, - ctx.self_type(), - )?; + let unknown_decl = ctx.resolve_call_path(handler, &enum_callpath)?; let enum_id = unknown_decl.to_enum_id(handler, ctx.engines())?; (enum_callpath.span(), enum_id, unknown_decl) } None => { // we may have an imported variant - let decl = ctx.namespace().resolve_call_path_typed( - handler, - engines, - &call_path, - ctx.self_type(), - )?; + let decl = ctx.resolve_call_path(handler, &call_path)?; if let TyDecl::EnumVariantDecl(ty::EnumVariantDecl { enum_ref, .. }) = decl.clone() { (call_path.suffix.span(), *enum_ref.id(), decl) } else { diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 3d6204c46ce..62f88759358 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -23,8 +23,8 @@ use crate::{ language::{ parsed::*, ty::{ - self, GetDeclIdent, TyCodeBlock, TyDecl, TyExpression, TyExpressionVariant, TyImplItem, - TyReassignmentTarget, VariableMutability, + self, GetDeclIdent, StructAccessInfo, TyCodeBlock, TyDecl, TyExpression, + TyExpressionVariant, TyImplItem, TyReassignmentTarget, VariableMutability, }, *, }, @@ -38,17 +38,19 @@ use crate::{ use ast_node::declaration::{insert_supertraits_into_namespace, SupertraitOf}; use either::Either; use indexmap::IndexMap; +use namespace::{LexicalScope, Module, ResolvedDeclaration}; use rustc_hash::FxHashSet; use std::collections::{HashMap, VecDeque}; use sway_ast::intrinsics::Intrinsic; use sway_error::{ convert_parse_tree_error::ConvertParseTreeError, - error::CompileError, + error::{CompileError, StructFieldUsageContext}, handler::{ErrorEmitted, Handler}, warning::{CompileWarning, Warning}, }; use sway_types::{integer_bits::IntegerBits, u256::U256, Ident, Named, Span, Spanned}; use symbol_collection_context::SymbolCollectionContext; +use type_resolve::{resolve_call_path, VisibilityCheck}; #[allow(clippy::too_many_arguments)] impl ty::TyExpression { @@ -214,7 +216,7 @@ impl ty::TyExpression { .iter() .map(|branch| { // create a new namespace for this branch result - ctx.scoped(engines, branch.span.clone(), |scoped_ctx| { + ctx.scoped(engines, branch.span.clone(), None, |scoped_ctx| { Self::collect(handler, engines, scoped_ctx, &branch.result) }) .0 @@ -309,14 +311,7 @@ impl ty::TyExpression { is_absolute: false, }; if matches!( - ctx.namespace() - .resolve_call_path_typed( - &Handler::default(), - engines, - &call_path, - ctx.self_type() - ) - .ok(), + ctx.resolve_call_path(&Handler::default(), &call_path,).ok(), Some(ty::TyDecl::EnumVariantDecl { .. }) ) { Self::type_check_delineated_path( @@ -506,9 +501,18 @@ impl ty::TyExpression { arguments, span, ), - ExpressionKind::WhileLoop(WhileLoopExpression { condition, body }) => { - Self::type_check_while_loop(handler, ctx.by_ref(), condition, body, span) - } + ExpressionKind::WhileLoop(WhileLoopExpression { + condition, + body, + is_desugared_for_loop, + }) => Self::type_check_while_loop( + handler, + ctx.by_ref(), + condition, + body, + *is_desugared_for_loop, + span, + ), ExpressionKind::ForLoop(ForLoopExpression { desugared }) => { Self::type_check_for_loop(handler, ctx.by_ref(), desugared) } @@ -642,11 +646,7 @@ impl ty::TyExpression { let decl_engine = ctx.engines.de(); let engines = ctx.engines(); - let exp = match ctx - .namespace() - .resolve_symbol_typed(&Handler::default(), engines, &name, ctx.self_type()) - .ok() - { + let exp = match ctx.resolve_symbol(&Handler::default(), &name).ok() { Some(ty::TyDecl::VariableDecl(decl)) => { let ty::TyVariableDecl { name: decl_name, @@ -1251,12 +1251,14 @@ impl ty::TyExpression { let storage_key_ident = Ident::new_with_override("StorageKey".into(), span.clone()); // Search for the struct declaration with the call path above. - let storage_key_decl = ctx.namespace().root().resolve_symbol( + let storage_key_decl = resolve_call_path( handler, engines, + ctx.namespace().root(), &storage_key_mod_path, - &storage_key_ident, + &storage_key_ident.into(), None, + VisibilityCheck::No, )?; let storage_key_struct_decl_id = storage_key_decl @@ -1411,12 +1413,7 @@ impl ty::TyExpression { is_absolute, }; if matches!( - ctx.namespace().resolve_call_path_typed( - &Handler::default(), - engines, - &call_path, - ctx.self_type() - ), + ctx.resolve_call_path(&Handler::default(), &call_path,), Ok(ty::TyDecl::EnumVariantDecl { .. }) ) { // if it's a singleton it's either an enum variant or a function @@ -1471,13 +1468,7 @@ impl ty::TyExpression { suffix: before.inner.clone(), is_absolute, }; - ctx.namespace() - .resolve_call_path_typed( - &Handler::default(), - engines, - &probe_call_path, - ctx.self_type(), - ) + ctx.resolve_call_path(&Handler::default(), &probe_call_path) .and_then(|decl| decl.to_enum_id(&Handler::default(), ctx.engines())) .map(|decl_ref| decl_engine.get_enum(&decl_ref)) .and_then(|decl| { @@ -1790,12 +1781,7 @@ impl ty::TyExpression { }; // look up the call path and get the declaration it references - let abi = ctx.namespace().resolve_call_path_typed( - handler, - engines, - &abi_name, - ctx.self_type(), - )?; + let abi = ctx.resolve_call_path(handler, &abi_name)?; let abi_ref = match abi { ty::TyDecl::AbiDecl(ty::AbiDecl { decl_id }) => { let abi_decl = engines.de().get(&decl_id); @@ -1816,12 +1802,7 @@ impl ty::TyExpression { match abi_name { // look up the call path and get the declaration it references AbiName::Known(abi_name) => { - let unknown_decl = ctx.namespace().resolve_call_path_typed( - handler, - engines, - abi_name, - ctx.self_type(), - )?; + let unknown_decl = ctx.resolve_call_path(handler, abi_name)?; unknown_decl.to_abi_ref(handler, engines)? } AbiName::Deferred => { @@ -2131,6 +2112,7 @@ impl ty::TyExpression { mut ctx: TypeCheckContext, condition: &Expression, body: &CodeBlock, + is_desugared_for_loop: bool, span: Span, ) -> Result { let type_engine = ctx.engines.te(); @@ -2144,11 +2126,17 @@ impl ty::TyExpression { }; let unit_ty = type_engine.id_of_unit(); - let mut ctx = ctx.with_type_annotation(unit_ty).with_help_text( - "A while loop's loop body cannot implicitly return a value. Try \ + let mut ctx = ctx + .with_type_annotation(unit_ty) + .with_help_text(if is_desugared_for_loop { + "A for loop's loop body cannot implicitly return a value. Try \ assigning it to a mutable variable declared outside of the loop \ - instead.", - ); + instead." + } else { + "A while loop's loop body cannot implicitly return a value. Try \ + assigning it to a mutable variable declared outside of the loop \ + instead." + }); let typed_body = ty::TyCodeBlock::type_check(handler, ctx.by_ref(), body, false)?; let exp = ty::TyExpression { @@ -2228,12 +2216,7 @@ impl ty::TyExpression { let (decl_reference_name, decl_reference_rhs, decl_reference_type) = match &reference_exp.expression { TyExpressionVariant::VariableExpression { name, .. } => { - let var_decl = ctx.namespace().resolve_symbol_typed( - handler, - engines, - name, - ctx.self_type(), - )?; + let var_decl = ctx.resolve_symbol(handler, name)?; let TyDecl::VariableDecl(var_decl) = var_decl else { return Err(handler.emit_err(CompileError::Internal( @@ -2293,12 +2276,7 @@ impl ty::TyExpression { match expr.kind { ExpressionKind::Variable(name) => { // check that the reassigned name exists - let unknown_decl = ctx.namespace().resolve_symbol_typed( - handler, - engines, - &name, - ctx.self_type(), - )?; + let unknown_decl = ctx.resolve_symbol(handler, &name)?; match unknown_decl { TyDecl::VariableDecl(variable_decl) => { @@ -2392,7 +2370,8 @@ impl ty::TyExpression { let indices = indices.into_iter().rev().collect::>(); let (ty_of_field, _ty_of_parent) = ctx.namespace().program_id(engines).read(engines, |m| { - m.current_items().find_subfield_type( + Self::find_subfield_type( + m, handler, ctx.engines(), ctx.namespace(), @@ -2429,6 +2408,198 @@ impl ty::TyExpression { }) } + pub fn find_subfield_type( + module: &Module, + handler: &Handler, + engines: &Engines, + namespace: &Namespace, + base_name: &Ident, + projections: &[ty::ProjectionKind], + ) -> Result<(TypeId, TypeId), ErrorEmitted> { + let ret = module.walk_scope_chain(|lexical_scope| { + Self::find_subfield_type_helper( + lexical_scope, + handler, + engines, + namespace, + base_name, + projections, + ) + })?; + + if let Some(ret) = ret { + Ok(ret) + } else { + // Symbol not found + Err(handler.emit_err(CompileError::UnknownVariable { + var_name: base_name.clone(), + span: base_name.span(), + })) + } + } + + /// Returns a tuple where the first element is the [TypeId] of the actual expression, and + /// the second is the [TypeId] of its parent. + fn find_subfield_type_helper( + lexical_scope: &LexicalScope, + handler: &Handler, + engines: &Engines, + namespace: &Namespace, + base_name: &Ident, + projections: &[ty::ProjectionKind], + ) -> Result, ErrorEmitted> { + let type_engine = engines.te(); + let decl_engine = engines.de(); + + let symbol = match lexical_scope.items.symbols.get(base_name).cloned() { + Some(s) => s, + None => { + return Ok(None); + } + }; + let mut symbol = match symbol { + ResolvedDeclaration::Parsed(_) => unreachable!(), + ResolvedDeclaration::Typed(ty_decl) => ty_decl.return_type(handler, engines)?, + }; + let mut symbol_span = base_name.span(); + let mut parent_rover = symbol; + let mut full_span_for_error = base_name.span(); + for projection in projections { + let resolved_type = match type_engine.to_typeinfo(symbol, &symbol_span) { + Ok(resolved_type) => resolved_type, + Err(error) => { + return Err(handler.emit_err(CompileError::TypeError(error))); + } + }; + match (resolved_type, projection) { + ( + TypeInfo::Struct(decl_ref), + ty::ProjectionKind::StructField { name: field_name }, + ) => { + let struct_decl = decl_engine.get_struct(&decl_ref); + let (struct_can_be_changed, is_public_struct_access) = + StructAccessInfo::get_info(engines, &struct_decl, namespace).into(); + + let field_type_id = match struct_decl.find_field(field_name) { + Some(struct_field) => { + if is_public_struct_access && struct_field.is_private() { + return Err(handler.emit_err(CompileError::StructFieldIsPrivate { + field_name: field_name.into(), + struct_name: struct_decl.call_path.suffix.clone(), + field_decl_span: struct_field.name.span(), + struct_can_be_changed, + usage_context: StructFieldUsageContext::StructFieldAccess, + })); + } + + struct_field.type_argument.type_id + } + None => { + return Err(handler.emit_err(CompileError::StructFieldDoesNotExist { + field_name: field_name.into(), + available_fields: struct_decl + .accessible_fields_names(is_public_struct_access), + is_public_struct_access, + struct_name: struct_decl.call_path.suffix.clone(), + struct_decl_span: struct_decl.span(), + struct_is_empty: struct_decl.is_empty(), + usage_context: StructFieldUsageContext::StructFieldAccess, + })); + } + }; + parent_rover = symbol; + symbol = field_type_id; + symbol_span = field_name.span().clone(); + full_span_for_error = Span::join(full_span_for_error, &field_name.span()); + } + (TypeInfo::Tuple(fields), ty::ProjectionKind::TupleField { index, index_span }) => { + let field_type_opt = { + fields + .get(*index) + .map(|TypeArgument { type_id, .. }| type_id) + }; + let field_type = match field_type_opt { + Some(field_type) => field_type, + None => { + return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds { + index: *index, + count: fields.len(), + tuple_type: engines.help_out(symbol).to_string(), + span: index_span.clone(), + prefix_span: full_span_for_error.clone(), + })); + } + }; + parent_rover = symbol; + symbol = *field_type; + symbol_span = index_span.clone(); + full_span_for_error = Span::join(full_span_for_error, index_span); + } + ( + TypeInfo::Array(elem_ty, array_length), + ty::ProjectionKind::ArrayIndex { index, index_span }, + ) => { + parent_rover = symbol; + symbol = elem_ty.type_id; + symbol_span = index_span.clone(); + + if let Some(index_literal) = index + .expression + .as_literal() + .and_then(|x| x.cast_value_to_u64()) + { + if index_literal >= array_length.val() as u64 { + return Err(handler.emit_err(CompileError::ArrayOutOfBounds { + index: index_literal, + count: array_length.val() as u64, + span: index.span.clone(), + })); + } + } + + // `index_span` does not contain the enclosing square brackets. + // Which means, if this array index access is the last one before the + // erroneous expression, the `full_span_for_error` will be missing the + // closing `]`. We can live with this small glitch so far. To fix it, + // we would need to bring the full span of the index all the way from + // the parsing stage. An effort that doesn't pay off at the moment. + // TODO: Include the closing square bracket into the error span. + full_span_for_error = Span::join(full_span_for_error, index_span); + } + (actually, ty::ProjectionKind::StructField { name }) => { + return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct { + actually: engines.help_out(actually).to_string(), + storage_variable: None, + field_name: name.into(), + span: full_span_for_error, + })); + } + ( + actually, + ty::ProjectionKind::TupleField { + index, index_span, .. + }, + ) => { + return Err( + handler.emit_err(CompileError::TupleElementAccessOnNonTuple { + actually: engines.help_out(actually).to_string(), + span: full_span_for_error, + index: *index, + index_span: index_span.clone(), + }), + ); + } + (actually, ty::ProjectionKind::ArrayIndex { .. }) => { + return Err(handler.emit_err(CompileError::NotIndexable { + actually: engines.help_out(actually).to_string(), + span: full_span_for_error, + })); + } + } + } + Ok(Some((symbol, parent_rover))) + } + fn type_check_ref( handler: &Handler, mut ctx: TypeCheckContext<'_>, @@ -2787,15 +2958,13 @@ fn check_asm_block_validity( // Emit warning if this register shadows a constant, or a configurable, or a variable. let temp_handler = Handler::default(); - let decl = ctx.namespace().resolve_call_path_typed( + let decl = ctx.resolve_call_path( &temp_handler, - ctx.engines, &CallPath { prefixes: vec![], suffix: sway_types::BaseIdent::new(span.clone()), is_absolute: true, }, - None, ); let shadowing_item = match decl { diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 6a836f56f3c..c5cb709b70c 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -225,13 +225,10 @@ pub(crate) fn type_check_method_application( // if the coins contract call parameter is not specified // it's considered to be zero and hence no error needs to be reported if let Some(coins_expr) = contract_call_params_map.get(CONTRACT_CALL_COINS_PARAMETER_NAME) { - if coins_analysis::possibly_nonzero_u64_expression( - ctx.namespace(), - ctx.engines, - coins_expr, - ) && !method - .attributes - .contains_key(&crate::transform::AttributeKind::Payable) + if coins_analysis::possibly_nonzero_u64_expression(&ctx, coins_expr) + && !method + .attributes + .contains_key(&crate::transform::AttributeKind::Payable) { return Err( handler.emit_err(CompileError::CoinsPassedToNonPayableMethod { @@ -288,12 +285,7 @@ pub(crate) fn type_check_method_application( ) -> Result<(), ErrorEmitted> { match exp { ty::TyExpressionVariant::VariableExpression { name, .. } => { - let unknown_decl = ctx.namespace().resolve_symbol_typed( - &Handler::default(), - ctx.engines, - name, - ctx.self_type(), - )?; + let unknown_decl = ctx.resolve_symbol(&Handler::default(), name)?; let is_decl_mutable = match unknown_decl { ty::TyDecl::ConstantDecl { .. } => false, @@ -816,11 +808,9 @@ pub(crate) fn resolve_method_name( let type_info_prefix = ctx .namespace() .prepend_module_path(&call_path_binding.inner.prefixes); - ctx.namespace().lookup_submodule_from_absolute_path( - handler, - engines, - &type_info_prefix, - )?; + ctx.namespace() + .root_module() + .lookup_submodule(handler, engines, &type_info_prefix)?; // find the method let decl_ref = ctx.find_method_for_type( diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs index bf52842ea81..ef900927271 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/struct_instantiation.rs @@ -74,7 +74,8 @@ pub(crate) fn struct_instantiation( // find the module that the struct decl is in let type_info_prefix = ctx.namespace().prepend_module_path(prefixes); ctx.namespace() - .lookup_submodule_from_absolute_path(handler, engines, &type_info_prefix)?; + .root_module() + .lookup_submodule(handler, engines, &type_info_prefix)?; // resolve the type of the struct decl let type_id = ctx @@ -333,8 +334,7 @@ fn collect_struct_constructors( // but that would be a way too much of suggestions, and moreover, it is also not a design pattern/guideline // that we wish to encourage. namespace.program_id(engines).read(engines, |m| { - m.current_items() - .get_items_for_type(engines, struct_type_id) + m.get_items_for_type(engines, struct_type_id) .iter() .filter_map(|item| match item { ResolvedTraitImplItem::Parsed(_) => unreachable!(), diff --git a/sway-core/src/semantic_analysis/ast_node/modes.rs b/sway-core/src/semantic_analysis/ast_node/modes.rs index b2e3f25aaf4..b4d5a4aaf6c 100644 --- a/sway-core/src/semantic_analysis/ast_node/modes.rs +++ b/sway-core/src/semantic_analysis/ast_node/modes.rs @@ -9,6 +9,7 @@ pub enum AbiMode { #[derive(Clone, Copy, PartialEq, Eq, Default)] pub enum ConstShadowingMode { + Allow, Sequential, #[default] ItemStyle, diff --git a/sway-core/src/semantic_analysis/coins_analysis.rs b/sway-core/src/semantic_analysis/coins_analysis.rs index 3b2b352f714..c88be235b84 100644 --- a/sway-core/src/semantic_analysis/coins_analysis.rs +++ b/sway-core/src/semantic_analysis/coins_analysis.rs @@ -1,16 +1,14 @@ use sway_error::handler::Handler; -use crate::{language::ty, Engines, Namespace}; +use crate::language::ty; + +use super::TypeCheckContext; // This analysis checks if an expression is known statically to evaluate // to a non-zero value at runtime. // It's intended to be used in the payability analysis to check if a non-payable // method gets called with a non-zero amount of `coins` -pub fn possibly_nonzero_u64_expression( - namespace: &Namespace, - engines: &Engines, - expr: &ty::TyExpression, -) -> bool { +pub fn possibly_nonzero_u64_expression(ctx: &TypeCheckContext, expr: &ty::TyExpression) -> bool { use ty::TyExpressionVariant::*; match &expr.expression { Literal(crate::language::Literal::U64(value)) => *value != 0, @@ -18,29 +16,24 @@ pub fn possibly_nonzero_u64_expression( // not a u64 literal, hence we return true to be on the safe side Literal(_) => true, ConstantExpression { decl, .. } => match &decl.value { - Some(expr) => possibly_nonzero_u64_expression(namespace, engines, expr), + Some(expr) => possibly_nonzero_u64_expression(ctx, expr), None => false, }, ConfigurableExpression { decl, .. } => match &decl.value { - Some(expr) => possibly_nonzero_u64_expression(namespace, engines, expr), + Some(expr) => possibly_nonzero_u64_expression(ctx, expr), None => false, }, VariableExpression { name, .. } => { - match namespace - .resolve_symbol_typed(&Handler::default(), engines, name, None) - .ok() - { + match ctx.resolve_symbol(&Handler::default(), name).ok() { Some(ty_decl) => { match ty_decl { ty::TyDecl::VariableDecl(var_decl) => { - possibly_nonzero_u64_expression(namespace, engines, &var_decl.body) + possibly_nonzero_u64_expression(ctx, &var_decl.body) } ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id, .. }) => { - let const_decl = engines.de().get_constant(&decl_id); + let const_decl = ctx.engines.de().get_constant(&decl_id); match &const_decl.value { - Some(value) => { - possibly_nonzero_u64_expression(namespace, engines, value) - } + Some(value) => possibly_nonzero_u64_expression(ctx, value), None => true, } } diff --git a/sway-core/src/semantic_analysis/namespace/lexical_scope.rs b/sway-core/src/semantic_analysis/namespace/lexical_scope.rs index 62203b13dda..308b5c0b989 100644 --- a/sway-core/src/semantic_analysis/namespace/lexical_scope.rs +++ b/sway-core/src/semantic_analysis/namespace/lexical_scope.rs @@ -3,7 +3,7 @@ use crate::{ engine_threading::{Engines, PartialEqWithEngines, PartialEqWithEnginesContext}, language::{ parsed::{Declaration, FunctionDeclaration}, - ty::{self, StructAccessInfo, TyDecl, TyStorageDecl}, + ty::{self, TyDecl, TyStorageDecl}, CallPath, Visibility, }, namespace::*, @@ -15,7 +15,7 @@ use super::{root::ResolvedDeclaration, TraitMap}; use parking_lot::RwLock; use sway_error::{ - error::{CompileError, ShadowingSource, StructFieldUsageContext}, + error::{CompileError, ShadowingSource}, handler::{ErrorEmitted, Handler}, }; use sway_types::{span::Span, IdentUnique, Named, Spanned}; @@ -71,6 +71,9 @@ pub struct LexicalScope { pub children: Vec, /// The parent scope associated with this scope. Will be None for a root scope. pub parent: Option, + /// The declaration associated with this scope. This will initially be a [ParsedDeclId], + /// but can be replaced to be a [DeclId] once the declaration is type checked. + pub declaration: Option, } /// The set of items that exist within some lexical scope via declaration or importing. @@ -161,21 +164,21 @@ impl Items { handler: &Handler, engines: &Engines, symbol: &Ident, - ) -> Result { + ) -> Result, ErrorEmitted> { // Check locally declared items. Any name clash with imports will have already been reported as an error. if let Some(decl) = self.symbols.get(symbol) { - return Ok(decl.clone()); + return Ok(Some(decl.clone())); } // Check item imports if let Some((_, _, decl, _)) = self.use_item_synonyms.get(symbol) { - return Ok(decl.clone()); + return Ok(Some(decl.clone())); } // Check glob imports if let Some(decls) = self.use_glob_synonyms.get(symbol) { if decls.len() == 1 { - return Ok(decls[0].1.clone()); + return Ok(Some(decls[0].1.clone())); } else if decls.is_empty() { return Err(handler.emit_err(CompileError::Internal( "The name {symbol} was bound in a star import, but no corresponding module paths were found", @@ -193,11 +196,7 @@ impl Items { } } - // Symbol not found - Err(handler.emit_err(CompileError::SymbolNotFound { - name: symbol.clone(), - span: symbol.span(), - })) + Ok(None) } pub(crate) fn insert_parsed_symbol( @@ -644,6 +643,9 @@ impl Items { is_alias: bool, item: &ResolvedDeclaration, const_shadowing_mode: ConstShadowingMode| { + if const_shadowing_mode == ConstShadowingMode::Allow { + return; + } match (decl, item) { // TODO: Do not handle any shadowing errors while handling parsed declarations yet, // or else we will emit errors in a different order from the source code order. @@ -768,14 +770,6 @@ impl Items { .clear(); } - pub fn get_items_for_type( - &self, - engines: &Engines, - type_id: TypeId, - ) -> Vec { - self.implemented_traits.get_items_for_type(engines, type_id) - } - pub fn get_impl_spans_for_decl(&self, engines: &Engines, ty_decl: &TyDecl) -> Vec { let handler = Handler::default(); ty_decl @@ -797,24 +791,6 @@ impl Items { .get_impl_spans_for_trait_name(trait_name) } - pub fn get_methods_for_type( - &self, - engines: &Engines, - type_id: TypeId, - ) -> Vec { - self.get_items_for_type(engines, type_id) - .into_iter() - .filter_map(|item| match item { - ResolvedTraitImplItem::Parsed(_) => todo!(), - ResolvedTraitImplItem::Typed(item) => match item { - ty::TyTraitItem::Fn(decl_ref) => Some(ResolvedFunctionDecl::Typed(decl_ref)), - ty::TyTraitItem::Constant(_decl_ref) => None, - ty::TyTraitItem::Type(_decl_ref) => None, - }, - }) - .collect::>() - } - pub(crate) fn has_storage_declared(&self) -> bool { self.declared_storage.is_some() } @@ -839,156 +815,6 @@ impl Items { } } } - - /// Returns a tuple where the first element is the [TypeId] of the actual expression, and - /// the second is the [TypeId] of its parent. - pub(crate) fn find_subfield_type( - &self, - handler: &Handler, - engines: &Engines, - namespace: &Namespace, - base_name: &Ident, - projections: &[ty::ProjectionKind], - ) -> Result<(TypeId, TypeId), ErrorEmitted> { - let type_engine = engines.te(); - let decl_engine = engines.de(); - - let symbol = match self.symbols.get(base_name).cloned() { - Some(s) => s, - None => { - return Err(handler.emit_err(CompileError::UnknownVariable { - var_name: base_name.clone(), - span: base_name.span(), - })); - } - }; - let mut symbol = match symbol { - ResolvedDeclaration::Parsed(_) => unreachable!(), - ResolvedDeclaration::Typed(ty_decl) => ty_decl.return_type(handler, engines)?, - }; - let mut symbol_span = base_name.span(); - let mut parent_rover = symbol; - let mut full_span_for_error = base_name.span(); - for projection in projections { - let resolved_type = match type_engine.to_typeinfo(symbol, &symbol_span) { - Ok(resolved_type) => resolved_type, - Err(error) => { - return Err(handler.emit_err(CompileError::TypeError(error))); - } - }; - match (resolved_type, projection) { - ( - TypeInfo::Struct(decl_ref), - ty::ProjectionKind::StructField { name: field_name }, - ) => { - let struct_decl = decl_engine.get_struct(&decl_ref); - let (struct_can_be_changed, is_public_struct_access) = - StructAccessInfo::get_info(engines, &struct_decl, namespace).into(); - - let field_type_id = match struct_decl.find_field(field_name) { - Some(struct_field) => { - if is_public_struct_access && struct_field.is_private() { - return Err(handler.emit_err(CompileError::StructFieldIsPrivate { - field_name: field_name.into(), - struct_name: struct_decl.call_path.suffix.clone(), - field_decl_span: struct_field.name.span(), - struct_can_be_changed, - usage_context: StructFieldUsageContext::StructFieldAccess, - })); - } - - struct_field.type_argument.type_id - } - None => { - return Err(handler.emit_err(CompileError::StructFieldDoesNotExist { - field_name: field_name.into(), - available_fields: struct_decl - .accessible_fields_names(is_public_struct_access), - is_public_struct_access, - struct_name: struct_decl.call_path.suffix.clone(), - struct_decl_span: struct_decl.span(), - struct_is_empty: struct_decl.is_empty(), - usage_context: StructFieldUsageContext::StructFieldAccess, - })); - } - }; - parent_rover = symbol; - symbol = field_type_id; - symbol_span = field_name.span().clone(); - full_span_for_error = Span::join(full_span_for_error, &field_name.span()); - } - (TypeInfo::Tuple(fields), ty::ProjectionKind::TupleField { index, index_span }) => { - let field_type_opt = { - fields - .get(*index) - .map(|TypeArgument { type_id, .. }| type_id) - }; - let field_type = match field_type_opt { - Some(field_type) => field_type, - None => { - return Err(handler.emit_err(CompileError::TupleIndexOutOfBounds { - index: *index, - count: fields.len(), - tuple_type: engines.help_out(symbol).to_string(), - span: index_span.clone(), - prefix_span: full_span_for_error.clone(), - })); - } - }; - parent_rover = symbol; - symbol = *field_type; - symbol_span = index_span.clone(); - full_span_for_error = Span::join(full_span_for_error, index_span); - } - ( - TypeInfo::Array(elem_ty, _), - ty::ProjectionKind::ArrayIndex { index_span, .. }, - ) => { - parent_rover = symbol; - symbol = elem_ty.type_id; - symbol_span = index_span.clone(); - // `index_span` does not contain the enclosing square brackets. - // Which means, if this array index access is the last one before the - // erroneous expression, the `full_span_for_error` will be missing the - // closing `]`. We can live with this small glitch so far. To fix it, - // we would need to bring the full span of the index all the way from - // the parsing stage. An effort that doesn't pay off at the moment. - // TODO: Include the closing square bracket into the error span. - full_span_for_error = Span::join(full_span_for_error, index_span); - } - (actually, ty::ProjectionKind::StructField { name }) => { - return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct { - actually: engines.help_out(actually).to_string(), - storage_variable: None, - field_name: name.into(), - span: full_span_for_error, - })); - } - ( - actually, - ty::ProjectionKind::TupleField { - index, index_span, .. - }, - ) => { - return Err( - handler.emit_err(CompileError::TupleElementAccessOnNonTuple { - actually: engines.help_out(actually).to_string(), - span: full_span_for_error, - index: *index, - index_span: index_span.clone(), - }), - ); - } - (actually, ty::ProjectionKind::ArrayIndex { .. }) => { - return Err(handler.emit_err(CompileError::NotIndexable { - actually: engines.help_out(actually).to_string(), - span: full_span_for_error, - })); - } - } - } - Ok((symbol, parent_rover)) - } } fn get_path_for_decl( diff --git a/sway-core/src/semantic_analysis/namespace/mod.rs b/sway-core/src/semantic_analysis/namespace/mod.rs index 5fba8b2d90e..8947e8495b0 100644 --- a/sway-core/src/semantic_analysis/namespace/mod.rs +++ b/sway-core/src/semantic_analysis/namespace/mod.rs @@ -17,7 +17,7 @@ pub(super) use trait_map::CodeBlockFirstPass; pub(super) use trait_map::IsExtendingExistingImpl; pub(super) use trait_map::IsImplSelf; pub(super) use trait_map::ResolvedTraitImplItem; -pub(super) use trait_map::TraitMap; +pub use trait_map::TraitMap; pub use trait_map::TryInsertingTraitImplOnFailure; use sway_types::Ident; diff --git a/sway-core/src/semantic_analysis/namespace/module.rs b/sway-core/src/semantic_analysis/namespace/module.rs index 77aa192b3be..af89e4469c9 100644 --- a/sway-core/src/semantic_analysis/namespace/module.rs +++ b/sway-core/src/semantic_analysis/namespace/module.rs @@ -1,9 +1,14 @@ -use crate::{engine_threading::Engines, language::Visibility, Ident}; +use crate::{ + engine_threading::Engines, + language::{ty, Visibility}, + Ident, TypeId, +}; use super::{ - lexical_scope::{Items, LexicalScope}, + lexical_scope::{Items, LexicalScope, ResolvedFunctionDecl}, root::Root, - LexicalScopeId, ModuleName, ModulePath, ModulePathBuf, + LexicalScopeId, ModuleName, ModulePath, ModulePathBuf, ResolvedDeclaration, + ResolvedTraitImplItem, }; use rustc_hash::FxHasher; @@ -215,6 +220,14 @@ impl Module { .unwrap() } + pub fn get_lexical_scope(&self, id: LexicalScopeId) -> Option<&LexicalScope> { + self.lexical_scopes.get(id) + } + + pub fn get_lexical_scope_mut(&mut self, id: LexicalScopeId) -> Option<&mut LexicalScope> { + self.lexical_scopes.get_mut(id) + } + /// Returns the current lexical scope associated with this module. pub fn current_lexical_scope(&self) -> &LexicalScope { self.lexical_scopes @@ -264,11 +277,16 @@ impl Module { } /// Pushes a new scope to the module's lexical scope hierarchy. - pub fn push_new_lexical_scope(&mut self, span: Span) -> LexicalScopeId { + pub fn push_new_lexical_scope( + &mut self, + span: Span, + declaration: Option, + ) -> LexicalScopeId { let previous_scope_id = self.current_lexical_scope_id(); let new_scoped_id = { self.lexical_scopes.push(LexicalScope { parent: Some(previous_scope_id), + declaration, ..Default::default() }); self.lexical_scopes.len() - 1 @@ -285,6 +303,82 @@ impl Module { let parent_scope_id = self.current_lexical_scope().parent; self.current_lexical_scope_id = parent_scope_id.unwrap_or(0); } + + pub fn walk_scope_chain( + &self, + mut f: impl FnMut(&LexicalScope) -> Result, ErrorEmitted>, + ) -> Result, ErrorEmitted> { + let mut lexical_scope_opt = Some(self.current_lexical_scope()); + while let Some(lexical_scope) = lexical_scope_opt { + let result = f(lexical_scope)?; + if let Some(result) = result { + return Ok(Some(result)); + } + if let Some(parent_scope_id) = lexical_scope.parent { + lexical_scope_opt = self.get_lexical_scope(parent_scope_id); + } else { + lexical_scope_opt = None; + } + } + Ok(None) + } + + pub fn get_items_for_type( + &self, + engines: &Engines, + type_id: TypeId, + ) -> Vec { + let mut vec = vec![]; + let _ = self.walk_scope_chain(|lexical_scope| { + vec.extend( + lexical_scope + .items + .implemented_traits + .get_items_for_type(engines, type_id), + ); + Ok(Some(())) + }); + vec + } + + pub fn resolve_symbol( + &self, + handler: &Handler, + engines: &Engines, + symbol: &Ident, + ) -> Result { + let ret = self.walk_scope_chain(|lexical_scope| { + lexical_scope.items.resolve_symbol(handler, engines, symbol) + })?; + + if let Some(ret) = ret { + Ok(ret) + } else { + // Symbol not found + Err(handler.emit_err(CompileError::SymbolNotFound { + name: symbol.clone(), + span: symbol.span(), + })) + } + } + + pub fn get_methods_for_type( + &self, + engines: &Engines, + type_id: TypeId, + ) -> Vec { + self.get_items_for_type(engines, type_id) + .into_iter() + .filter_map(|item| match item { + ResolvedTraitImplItem::Parsed(_) => unreachable!(), + ResolvedTraitImplItem::Typed(item) => match item { + ty::TyTraitItem::Fn(decl_ref) => Some(ResolvedFunctionDecl::Typed(decl_ref)), + ty::TyTraitItem::Constant(_decl_ref) => None, + ty::TyTraitItem::Type(_decl_ref) => None, + }, + }) + .collect::>() + } } impl From for Module { diff --git a/sway-core/src/semantic_analysis/namespace/namespace.rs b/sway-core/src/semantic_analysis/namespace/namespace.rs index 6ed7fe8007b..17f5a50a996 100644 --- a/sway-core/src/semantic_analysis/namespace/namespace.rs +++ b/sway-core/src/semantic_analysis/namespace/namespace.rs @@ -1,16 +1,10 @@ -use crate::{ - language::{ty, CallPath, Visibility}, - Engines, Ident, TypeId, -}; +use crate::{language::Visibility, Engines, Ident}; use super::{ - module::Module, - root::{ResolvedDeclaration, Root}, - submodule_namespace::SubmoduleNamespace, - ModulePath, ModulePathBuf, + module::Module, root::Root, submodule_namespace::SubmoduleNamespace, ModulePath, ModulePathBuf, }; -use sway_error::handler::{ErrorEmitted, Handler}; +use sway_error::handler::Handler; use sway_types::span::Span; /// The set of items that represent the namespace context passed throughout type checking. @@ -124,15 +118,6 @@ impl Namespace { .unwrap() } - pub fn lookup_submodule_from_absolute_path( - &self, - handler: &Handler, - engines: &Engines, - path: &ModulePath, - ) -> Result<&Module, ErrorEmitted> { - self.root.module.lookup_submodule(handler, engines, path) - } - /// Returns true if the current module being checked is a direct or indirect submodule of /// the module given by the `absolute_module_path`. /// @@ -189,53 +174,6 @@ impl Namespace { root_name != &absolute_module_path[0] } - /// Short-hand for calling [Root::resolve_symbol] on `root` with the `mod_path`. - pub(crate) fn resolve_symbol( - &self, - handler: &Handler, - engines: &Engines, - symbol: &Ident, - self_type: Option, - ) -> Result { - self.root - .resolve_symbol(handler, engines, &self.mod_path, symbol, self_type) - } - - /// Short-hand for calling [Root::resolve_symbol] on `root` with the `mod_path`. - pub(crate) fn resolve_symbol_typed( - &self, - handler: &Handler, - engines: &Engines, - symbol: &Ident, - self_type: Option, - ) -> Result { - self.resolve_symbol(handler, engines, symbol, self_type) - .map(|resolved_decl| resolved_decl.expect_typed()) - } - - /// Short-hand for calling [Root::resolve_call_path] on `root` with the `mod_path`. - pub(crate) fn resolve_call_path_typed( - &self, - handler: &Handler, - engines: &Engines, - call_path: &CallPath, - self_type: Option, - ) -> Result { - self.resolve_call_path(handler, engines, call_path, self_type) - .map(|resolved_decl| resolved_decl.expect_typed()) - } - - /// Short-hand for calling [Root::resolve_call_path] on `root` with the `mod_path`. - pub(crate) fn resolve_call_path( - &self, - handler: &Handler, - engines: &Engines, - call_path: &CallPath, - self_type: Option, - ) -> Result { - self.root - .resolve_call_path(handler, engines, &self.mod_path, call_path, self_type) - } /// "Enter" the submodule at the given path by returning a new [SubmoduleNamespace]. /// diff --git a/sway-core/src/semantic_analysis/namespace/root.rs b/sway-core/src/semantic_analysis/namespace/root.rs index 086d91b31aa..99fd5eafc2b 100644 --- a/sway-core/src/semantic_analysis/namespace/root.rs +++ b/sway-core/src/semantic_analysis/namespace/root.rs @@ -7,10 +7,9 @@ use crate::{ language::{ parsed::*, ty::{self, StructDecl, TyDecl}, - CallPath, Visibility, + Visibility, }, namespace::{ModulePath, ModulePathBuf}, - semantic_analysis::type_resolve::{resolve_associated_item, resolve_associated_type}, TypeId, }; use sway_error::{ @@ -723,116 +722,6 @@ impl Root { Ok(()) } - - ////// NAME RESOLUTION ////// - - /// Resolve a symbol that is potentially prefixed with some path, e.g. `foo::bar::symbol`. - /// - /// This is short-hand for concatenating the `mod_path` with the `call_path`'s prefixes and - /// then calling `resolve_symbol` with the resulting path and call_path's suffix. - pub(crate) fn resolve_call_path( - &self, - handler: &Handler, - engines: &Engines, - mod_path: &ModulePath, - call_path: &CallPath, - self_type: Option, - ) -> Result { - let (decl, _) = - self.resolve_call_path_and_mod_path(handler, engines, mod_path, call_path, self_type)?; - Ok(decl) - } - - pub(crate) fn resolve_call_path_and_mod_path( - &self, - handler: &Handler, - engines: &Engines, - mod_path: &ModulePath, - call_path: &CallPath, - self_type: Option, - ) -> Result<(ResolvedDeclaration, ModulePathBuf), ErrorEmitted> { - let symbol_path: Vec<_> = mod_path - .iter() - .chain(&call_path.prefixes) - .cloned() - .collect(); - self.resolve_symbol_and_mod_path( - handler, - engines, - &symbol_path, - &call_path.suffix, - self_type, - ) - } - - /// Given a path to a module and the identifier of a symbol within that module, resolve its - /// declaration. - /// - /// If the symbol is within the given module's namespace via import, we recursively traverse - /// imports until we find the original declaration. - pub(crate) fn resolve_symbol( - &self, - handler: &Handler, - engines: &Engines, - mod_path: &ModulePath, - symbol: &Ident, - self_type: Option, - ) -> Result { - let (decl, _) = - self.resolve_symbol_and_mod_path(handler, engines, mod_path, symbol, self_type)?; - Ok(decl) - } - - fn resolve_symbol_and_mod_path( - &self, - handler: &Handler, - engines: &Engines, - mod_path: &ModulePath, - symbol: &Ident, - self_type: Option, - ) -> Result<(ResolvedDeclaration, Vec), ErrorEmitted> { - // This block tries to resolve associated types - let mut module = &self.module; - let mut current_mod_path = vec![]; - let mut decl_opt = None; - for ident in mod_path.iter() { - if let Some(decl) = decl_opt { - decl_opt = Some(resolve_associated_type( - handler, engines, module, ident, decl, None, self_type, - )?); - } else { - match module.submodules.get(ident.as_str()) { - Some(ns) => { - module = ns; - current_mod_path.push(ident.clone()); - } - None => { - decl_opt = Some( - module - .current_lexical_scope() - .items - .resolve_symbol(handler, engines, ident)?, - ); - } - } - } - } - if let Some(decl) = decl_opt { - let decl = - resolve_associated_item(handler, engines, module, symbol, decl, None, self_type)?; - return Ok((decl, current_mod_path)); - } - - self.module - .lookup_submodule(handler, engines, mod_path) - .and_then(|module| { - let decl = module - .current_lexical_scope() - .items - .resolve_symbol(handler, engines, symbol)?; - Ok((decl, mod_path.to_vec())) - }) - } } impl From for Root { diff --git a/sway-core/src/semantic_analysis/namespace/trait_map.rs b/sway-core/src/semantic_analysis/namespace/trait_map.rs index e37c30dfd61..9eb5e8b3439 100644 --- a/sway-core/src/semantic_analysis/namespace/trait_map.rs +++ b/sway-core/src/semantic_analysis/namespace/trait_map.rs @@ -27,9 +27,11 @@ use crate::{ TypeSubstMap, UnifyCheck, }; +use super::Module; + /// Enum used to pass a value asking for insertion of type into trait map when an implementation /// of the trait cannot be found. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TryInsertingTraitImplOnFailure { Yes, No, @@ -189,7 +191,7 @@ enum TypeRootFilter { /// Note: "impl self" blocks are considered traits and are stored in the /// [TraitMap]. #[derive(Clone, Debug, Default)] -pub(crate) struct TraitMap { +pub struct TraitMap { trait_impls: TraitImpls, satisfied_cache: im::HashSet, } @@ -1326,8 +1328,8 @@ impl TraitMap { /// Checks to see if the trait constraints are satisfied for a given type. #[allow(clippy::too_many_arguments)] pub(crate) fn check_if_trait_constraints_are_satisfied_for_type( - &mut self, handler: &Handler, + module: &mut Module, type_id: TypeId, constraints: &[TraitConstraint], access_span: &Span, @@ -1352,42 +1354,61 @@ impl TraitMap { } let hash = hasher.finish(); - if self.satisfied_cache.contains(&hash) { - return Ok(()); + { + let trait_map = &mut module.current_lexical_scope_mut().items.implemented_traits; + if trait_map.satisfied_cache.contains(&hash) { + return Ok(()); + } } + let all_impld_traits: BTreeSet<(Ident, TypeId)> = + Self::get_all_implemented_traits(module, type_id, engines); + // Call the real implementation and cache when true - match self.check_if_trait_constraints_are_satisfied_for_type_inner( + match Self::check_if_trait_constraints_are_satisfied_for_type_inner( handler, + module, type_id, constraints, access_span, engines, + all_impld_traits, try_inserting_trait_impl_on_failure, code_block_first_pass, ) { Ok(()) => { - self.satisfied_cache.insert(hash); + let trait_map = &mut module.current_lexical_scope_mut().items.implemented_traits; + trait_map.satisfied_cache.insert(hash); Ok(()) } r => r, } } - #[allow(clippy::too_many_arguments)] - fn check_if_trait_constraints_are_satisfied_for_type_inner( - &mut self, - handler: &Handler, + fn get_all_implemented_traits( + module: &Module, type_id: TypeId, - constraints: &[TraitConstraint], - access_span: &Span, engines: &Engines, - try_inserting_trait_impl_on_failure: TryInsertingTraitImplOnFailure, - code_block_first_pass: CodeBlockFirstPass, - ) -> Result<(), ErrorEmitted> { - let type_engine = engines.te(); + ) -> BTreeSet<(Ident, TypeId)> { + let mut all_impld_traits: BTreeSet<(Ident, TypeId)> = Default::default(); + let _ = module.walk_scope_chain(|lexical_scope| { + all_impld_traits.extend( + lexical_scope + .items + .implemented_traits + .get_implemented_traits(type_id, engines), + ); + Ok(Some(())) + }); + all_impld_traits + } - let _decl_engine = engines.de(); + fn get_implemented_traits( + &self, + type_id: TypeId, + engines: &Engines, + ) -> BTreeSet<(Ident, TypeId)> { + let type_engine = engines.te(); let unify_check = UnifyCheck::non_dynamic_equality(engines); let impls = self.get_impls(engines, type_id); @@ -1413,6 +1434,24 @@ impl TraitMap { }) .collect(); + all_impld_traits + } + + #[allow(clippy::too_many_arguments)] + fn check_if_trait_constraints_are_satisfied_for_type_inner( + handler: &Handler, + module: &mut Module, + type_id: TypeId, + constraints: &[TraitConstraint], + access_span: &Span, + engines: &Engines, + all_impld_traits: BTreeSet<(Ident, TypeId)>, + try_inserting_trait_impl_on_failure: TryInsertingTraitImplOnFailure, + code_block_first_pass: CodeBlockFirstPass, + ) -> Result<(), ErrorEmitted> { + let type_engine = engines.te(); + let unify_check = UnifyCheck::non_dynamic_equality(engines); + let required_traits: BTreeSet<(Ident, TypeId)> = constraints .iter() .map(|c| { @@ -1451,9 +1490,13 @@ impl TraitMap { try_inserting_trait_impl_on_failure, TryInsertingTraitImplOnFailure::Yes ) { - self.insert_for_type(engines, type_id, code_block_first_pass.clone()); - return self.check_if_trait_constraints_are_satisfied_for_type( + let trait_map = + &mut module.current_lexical_scope_mut().items.implemented_traits; + trait_map.insert_for_type(engines, type_id, code_block_first_pass.clone()); + + return Self::check_if_trait_constraints_are_satisfied_for_type( handler, + module, type_id, constraints, access_span, diff --git a/sway-core/src/semantic_analysis/symbol_collection_context.rs b/sway-core/src/semantic_analysis/symbol_collection_context.rs index 2a3be6aa35d..f831c2226ea 100644 --- a/sway-core/src/semantic_analysis/symbol_collection_context.rs +++ b/sway-core/src/semantic_analysis/symbol_collection_context.rs @@ -1,7 +1,6 @@ use crate::{ language::{parsed::Declaration, Visibility}, - namespace::LexicalScopeId, - namespace::ModulePath, + namespace::{LexicalScopeId, ModulePath, ResolvedDeclaration}, semantic_analysis::Namespace, Engines, }; @@ -41,12 +40,14 @@ impl SymbolCollectionContext { &mut self, engines: &Engines, span: Span, + decl: Option, with_scoped_ctx: impl FnOnce(&mut SymbolCollectionContext) -> Result, ) -> (Result, LexicalScopeId) { - let lexical_scope_id: LexicalScopeId = self - .namespace - .module_mut(engines) - .write(engines, |m| m.push_new_lexical_scope(span.clone())); + let decl = decl.map(ResolvedDeclaration::Parsed); + let lexical_scope_id: LexicalScopeId = + self.namespace.module_mut(engines).write(engines, |m| { + m.push_new_lexical_scope(span.clone(), decl.clone()) + }); let ret = with_scoped_ctx(self); self.namespace .module_mut(engines) diff --git a/sway-core/src/semantic_analysis/symbol_resolve_context.rs b/sway-core/src/semantic_analysis/symbol_resolve_context.rs index 01bc631ec0e..7a9155a193a 100644 --- a/sway-core/src/semantic_analysis/symbol_resolve_context.rs +++ b/sway-core/src/semantic_analysis/symbol_resolve_context.rs @@ -1,18 +1,18 @@ use crate::{ engine_threading::*, language::{CallPath, Visibility}, - namespace::{ModulePath, ResolvedDeclaration}, + namespace::ResolvedDeclaration, semantic_analysis::{ast_node::ConstShadowingMode, Namespace}, type_system::TypeId, }; -use sway_error::{ - error::CompileError, - handler::{ErrorEmitted, Handler}, -}; -use sway_types::{span::Span, Ident, Spanned}; -use sway_utils::iter_prefixes; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{span::Span, Ident}; -use super::{symbol_collection_context::SymbolCollectionContext, GenericShadowingMode}; +use super::{ + symbol_collection_context::SymbolCollectionContext, + type_resolve::{resolve_call_path, VisibilityCheck}, + GenericShadowingMode, +}; /// Contextual state tracked and accumulated throughout symbol resolving. pub struct SymbolResolveContext<'a> { @@ -184,70 +184,14 @@ impl<'a> SymbolResolveContext<'a> { handler: &Handler, call_path: &CallPath, ) -> Result { - self.resolve_call_path_with_visibility_check_and_modpath( + resolve_call_path( handler, + self.engines(), + self.namespace().root(), &self.namespace().mod_path, call_path, + self.self_type(), + VisibilityCheck::Yes, ) } - - /// Resolve a symbol that is potentially prefixed with some path, e.g. `foo::bar::symbol`. - /// - /// This will concatenate the `mod_path` with the `call_path`'s prefixes and - /// then calling `resolve_symbol` with the resulting path and call_path's suffix. - /// - /// The `mod_path` is significant here as we assume the resolution is done within the - /// context of the module pointed to by `mod_path` and will only check the call path prefixes - /// and the symbol's own visibility. - pub(crate) fn resolve_call_path_with_visibility_check_and_modpath( - &self, - handler: &Handler, - mod_path: &ModulePath, - call_path: &CallPath, - ) -> Result { - let (decl, mod_path) = self.namespace().root.resolve_call_path_and_mod_path( - handler, - self.engines, - mod_path, - call_path, - self.self_type, - )?; - - // In case there is no mod path we don't need to check visibility - if mod_path.is_empty() { - return Ok(decl); - } - - // In case there are no prefixes we don't need to check visibility - if call_path.prefixes.is_empty() { - return Ok(decl); - } - - // check the visibility of the call path elements - // we don't check the first prefix because direct children are always accessible - for prefix in iter_prefixes(&call_path.prefixes).skip(1) { - let module = self.namespace().lookup_submodule_from_absolute_path( - handler, - self.engines(), - prefix, - )?; - if module.visibility().is_private() { - let prefix_last = prefix[prefix.len() - 1].clone(); - handler.emit_err(CompileError::ImportPrivateModule { - span: prefix_last.span(), - name: prefix_last, - }); - } - } - - // check the visibility of the symbol itself - if !decl.visibility(self.engines).is_public() { - handler.emit_err(CompileError::ImportPrivateSymbol { - name: call_path.suffix.clone(), - span: call_path.suffix.span(), - }); - } - - Ok(decl) - } } diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index 2a6b71b5687..9078be6ff4d 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -30,7 +30,7 @@ use sway_types::{span::Span, Ident, Spanned}; use super::{ symbol_collection_context::SymbolCollectionContext, - type_resolve::{resolve_call_path, resolve_qualified_call_path, resolve_type}, + type_resolve::{resolve_call_path, resolve_qualified_call_path, resolve_type, VisibilityCheck}, GenericShadowingMode, }; @@ -82,7 +82,7 @@ pub struct TypeCheckContext<'a> { /// Whether or not a const declaration shadows previous const declarations sequentially. /// /// This is `Sequential` while checking const declarations in functions, otherwise `ItemStyle`. - const_shadowing_mode: ConstShadowingMode, + pub(crate) const_shadowing_mode: ConstShadowingMode, /// Whether or not a generic type parameters shadows previous generic type parameters. /// /// This is `Disallow` everywhere except while checking type parameters bounds in struct instantiation. @@ -671,39 +671,80 @@ impl<'a> TypeCheckContext<'a> { type_info_prefix, self.self_type(), &self.subst_ctx(), + VisibilityCheck::Yes, ) } + pub(crate) fn resolve_qualified_call_path( + &mut self, + handler: &Handler, + qualified_call_path: &QualifiedCallPath, + ) -> Result { + resolve_qualified_call_path( + handler, + self.engines(), + self.namespace(), + &self.namespace().mod_path.clone(), + qualified_call_path, + self.self_type(), + &self.subst_ctx(), + VisibilityCheck::Yes, + ) + .map(|d| d.expect_typed()) + } + + /// Short-hand for calling [Root::resolve_symbol] on `root` with the `mod_path`. + pub(crate) fn resolve_symbol( + &self, + handler: &Handler, + symbol: &Ident, + ) -> Result { + resolve_call_path( + handler, + self.engines(), + self.namespace().root(), + self.namespace().mod_path(), + &symbol.clone().into(), + self.self_type(), + VisibilityCheck::No, + ) + .map(|d| d.expect_typed()) + } + /// Short-hand for calling [Root::resolve_call_path_with_visibility_check] on `root` with the `mod_path`. pub(crate) fn resolve_call_path_with_visibility_check( &self, handler: &Handler, call_path: &CallPath, - ) -> Result { + ) -> Result { resolve_call_path( handler, self.engines(), - self.namespace(), + self.namespace().root(), &self.namespace().mod_path, call_path, self.self_type(), + VisibilityCheck::Yes, ) + .map(|d| d.expect_typed()) } - pub(crate) fn resolve_qualified_call_path_with_visibility_check( - &mut self, + /// Short-hand for calling [Root::resolve_call_path] on `root` with the `mod_path`. + pub(crate) fn resolve_call_path( + &self, handler: &Handler, - qualified_call_path: &QualifiedCallPath, - ) -> Result { - resolve_qualified_call_path( + call_path: &CallPath, + ) -> Result { + resolve_call_path( handler, self.engines(), - self.namespace(), - &self.namespace().mod_path.clone(), - qualified_call_path, + self.namespace().root(), + self.namespace().mod_path(), + call_path, self.self_type(), - &self.subst_ctx(), + VisibilityCheck::No, ) + .map(|d| d.expect_typed()) } /// Given a name and a type (plus a `self_type` to potentially @@ -726,16 +767,14 @@ impl<'a> TypeCheckContext<'a> { } // grab the local module - let local_module = self.namespace().lookup_submodule_from_absolute_path( + let local_module = self.namespace().root_module().lookup_submodule( handler, self.engines(), &self.namespace().mod_path, )?; // grab the local items from the local module - let local_items = local_module - .current_items() - .get_items_for_type(self.engines, type_id); + let local_items = local_module.get_items_for_type(self.engines, type_id); // resolve the type let type_id = resolve_type( @@ -749,20 +788,19 @@ impl<'a> TypeCheckContext<'a> { None, self.self_type(), &self.subst_ctx(), + VisibilityCheck::Yes, ) .unwrap_or_else(|err| type_engine.id_of_error_recovery(err)); // grab the module where the type itself is declared - let type_module = self.namespace().lookup_submodule_from_absolute_path( + let type_module = self.namespace().root_module().lookup_submodule( handler, self.engines(), item_prefix, )?; // grab the items from where the type is declared - let mut type_items = type_module - .current_items() - .get_items_for_type(self.engines, type_id); + let mut type_items = type_module.get_items_for_type(self.engines, type_id); let mut items = local_items; items.append(&mut type_items); @@ -1387,19 +1425,16 @@ impl<'a> TypeCheckContext<'a> { let handler = Handler::default(); let engines = self.engines; let code_block_first_pass = self.code_block_first_pass(); - self.namespace_mut() - .module_mut(engines) - .current_items_mut() - .implemented_traits - .check_if_trait_constraints_are_satisfied_for_type( - &handler, - type_id, - constraints, - &Span::dummy(), - engines, - crate::namespace::TryInsertingTraitImplOnFailure::Yes, - code_block_first_pass.into(), - ) - .is_ok() + TraitMap::check_if_trait_constraints_are_satisfied_for_type( + &handler, + self.namespace_mut().module_mut(engines), + type_id, + constraints, + &Span::dummy(), + engines, + crate::namespace::TryInsertingTraitImplOnFailure::Yes, + code_block_first_pass.into(), + ) + .is_ok() } } diff --git a/sway-core/src/semantic_analysis/type_resolve.rs b/sway-core/src/semantic_analysis/type_resolve.rs index 387d3f48b2d..174817fdb5f 100644 --- a/sway-core/src/semantic_analysis/type_resolve.rs +++ b/sway-core/src/semantic_analysis/type_resolve.rs @@ -11,11 +11,18 @@ use crate::{ CallPath, QualifiedCallPath, }, monomorphization::type_decl_opt_to_type_id, - namespace::{Module, ModulePath, ResolvedDeclaration, ResolvedTraitImplItem}, + namespace::{Module, ModulePath, ResolvedDeclaration, ResolvedTraitImplItem, Root}, type_system::SubstTypes, EnforceTypeArguments, Engines, Namespace, SubstTypesContext, TypeId, TypeInfo, }; +/// Specifies if visibility checks should be performed as part of name resolution. +#[derive(Clone, Copy, PartialEq)] +pub enum VisibilityCheck { + Yes, + No, +} + /// Resolve the type of the given [TypeId], replacing any instances of /// [TypeInfo::Custom] with either a monomorphized struct, monomorphized /// enum, or a reference to a type parameter. @@ -31,6 +38,7 @@ pub fn resolve_type( type_info_prefix: Option<&ModulePath>, self_type: Option, subst_ctx: &SubstTypesContext, + check_visibility: VisibilityCheck, ) -> Result { let type_engine = engines.te(); let module_path = type_info_prefix.unwrap_or(mod_path); @@ -47,6 +55,7 @@ pub fn resolve_type( &qualified_call_path, self_type, subst_ctx, + check_visibility, ) .ok(); type_decl_opt_to_type_id( @@ -75,6 +84,7 @@ pub fn resolve_type( None, self_type, subst_ctx, + check_visibility, ) .unwrap_or_else(|err| engines.te().id_of_error_recovery(err)); @@ -92,6 +102,7 @@ pub fn resolve_type( None, self_type, subst_ctx, + check_visibility, ) .unwrap_or_else(|err| engines.te().id_of_error_recovery(err)); @@ -110,6 +121,7 @@ pub fn resolve_type( None, self_type, subst_ctx, + check_visibility, ) .unwrap_or_else(|err| engines.te().id_of_error_recovery(err)); } @@ -156,6 +168,7 @@ pub fn resolve_type( None, self_type, subst_ctx, + check_visibility, ) .unwrap_or_else(|err| engines.te().id_of_error_recovery(err)); @@ -170,6 +183,7 @@ pub fn resolve_type( Ok(type_id) } +#[allow(clippy::too_many_arguments)] pub fn resolve_qualified_call_path( handler: &Handler, engines: &Engines, @@ -178,6 +192,7 @@ pub fn resolve_qualified_call_path( qualified_call_path: &QualifiedCallPath, self_type: Option, subst_ctx: &SubstTypesContext, + check_visibility: VisibilityCheck, ) -> Result { let type_engine = engines.te(); if let Some(qualified_path_root) = qualified_call_path.clone().qualified_path_root { @@ -190,10 +205,11 @@ pub fn resolve_qualified_call_path( let type_decl = resolve_call_path( handler, engines, - namespace, + namespace.root(), mod_path, &qualified_call_path.clone().to_call_path(handler)?, self_type, + check_visibility, )?; type_decl_opt_to_type_id( handler, @@ -238,10 +254,11 @@ pub fn resolve_qualified_call_path( resolve_call_path( handler, engines, - namespace, + namespace.root(), mod_path, &qualified_call_path.call_path, self_type, + check_visibility, ) } } @@ -257,14 +274,30 @@ pub fn resolve_qualified_call_path( pub fn resolve_call_path( handler: &Handler, engines: &Engines, - namespace: &Namespace, + root: &Root, mod_path: &ModulePath, call_path: &CallPath, self_type: Option, + check_visibility: VisibilityCheck, ) -> Result { - let (decl, mod_path) = namespace - .root - .resolve_call_path_and_mod_path(handler, engines, mod_path, call_path, self_type)?; + let symbol_path: Vec<_> = mod_path + .iter() + .chain(&call_path.prefixes) + .cloned() + .collect(); + + let (decl, mod_path) = resolve_symbol_and_mod_path( + handler, + engines, + &root.module, + &symbol_path, + &call_path.suffix, + self_type, + )?; + + if check_visibility == VisibilityCheck::No { + return Ok(decl); + } // In case there is no mod path we don't need to check visibility if mod_path.is_empty() { @@ -279,7 +312,7 @@ pub fn resolve_call_path( // check the visibility of the call path elements // we don't check the first prefix because direct children are always accessible for prefix in iter_prefixes(&call_path.prefixes).skip(1) { - let module = namespace.lookup_submodule_from_absolute_path(handler, engines, prefix)?; + let module = root.module.lookup_submodule(handler, engines, prefix)?; if module.visibility().is_private() { let prefix_last = prefix[prefix.len() - 1].clone(); handler.emit_err(CompileError::ImportPrivateModule { @@ -300,7 +333,63 @@ pub fn resolve_call_path( Ok(decl) } -pub fn decl_to_type_info( +fn resolve_symbol_and_mod_path( + handler: &Handler, + engines: &Engines, + module: &Module, + mod_path: &ModulePath, + symbol: &Ident, + self_type: Option, +) -> Result<(ResolvedDeclaration, Vec), ErrorEmitted> { + let mut current_module = module; + // This block tries to resolve associated types + let mut current_mod_path = vec![]; + let mut decl_opt = None; + for ident in mod_path.iter() { + if let Some(decl) = decl_opt { + decl_opt = Some(resolve_associated_type_or_item( + handler, + engines, + current_module, + ident, + decl, + None, + self_type, + )?); + } else { + match current_module.submodules.get(ident.as_str()) { + Some(ns) => { + current_module = ns; + current_mod_path.push(ident.clone()); + } + None => { + decl_opt = Some(current_module.resolve_symbol(handler, engines, ident)?); + } + } + } + } + if let Some(decl) = decl_opt { + let decl = resolve_associated_type_or_item( + handler, + engines, + current_module, + symbol, + decl, + None, + self_type, + )?; + return Ok((decl, current_mod_path)); + } + + module + .lookup_submodule(handler, engines, mod_path) + .and_then(|module| { + let decl = module.resolve_symbol(handler, engines, symbol)?; + Ok((decl, mod_path.to_vec())) + }) +} + +fn decl_to_type_info( handler: &Handler, engines: &Engines, symbol: &Ident, @@ -335,7 +424,7 @@ pub fn decl_to_type_info( } #[allow(clippy::too_many_arguments)] -pub fn resolve_associated_item_from_type_id( +fn resolve_associated_item_from_type_id( handler: &Handler, engines: &Engines, module: &Module, @@ -371,27 +460,7 @@ pub fn resolve_associated_item_from_type_id( } #[allow(clippy::too_many_arguments)] -pub fn resolve_associated_type( - handler: &Handler, - engines: &Engines, - module: &Module, - symbol: &Ident, - decl: ResolvedDeclaration, - as_trait: Option, - self_type: Option, -) -> Result { - let type_info = decl_to_type_info(handler, engines, symbol, decl)?; - let type_id = engines - .te() - .insert(engines, type_info, symbol.span().source_id()); - - resolve_associated_item_from_type_id( - handler, engines, module, symbol, type_id, as_trait, self_type, - ) -} - -#[allow(clippy::too_many_arguments)] -pub fn resolve_associated_item( +fn resolve_associated_type_or_item( handler: &Handler, engines: &Engines, module: &Module, @@ -411,7 +480,7 @@ pub fn resolve_associated_item( } #[allow(clippy::too_many_arguments)] -pub(crate) fn resolve_call_path_and_root_type_id( +fn resolve_call_path_and_root_type_id( handler: &Handler, engines: &Engines, module: &Module, @@ -437,7 +506,7 @@ pub(crate) fn resolve_call_path_and_root_type_id( )?); as_trait = None; } else if let Some(decl) = decl_opt { - decl_opt = Some(resolve_associated_type( + decl_opt = Some(resolve_associated_type_or_item( handler, engines, module, @@ -462,7 +531,7 @@ pub(crate) fn resolve_call_path_and_root_type_id( return Ok(decl); } if let Some(decl) = decl_opt { - let decl = resolve_associated_item( + let decl = resolve_associated_type_or_item( handler, engines, module, diff --git a/sway-core/src/transform/attribute.rs b/sway-core/src/transform/attribute.rs index d29978205c8..d44538befa2 100644 --- a/sway-core/src/transform/attribute.rs +++ b/sway-core/src/transform/attribute.rs @@ -21,6 +21,8 @@ //! #[foo(bar, bar)] use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use std::{hash::Hash, sync::Arc}; use sway_ast::Literal; use sway_types::{ constants::{ @@ -29,9 +31,7 @@ use sway_types::{ Ident, Span, Spanned, }; -use std::{hash::Hash, sync::Arc}; - -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AttributeArg { pub name: Ident, pub value: Option, @@ -47,7 +47,7 @@ impl Spanned for AttributeArg { /// An attribute has a name (i.e "doc", "storage"), /// a vector of possible arguments and /// a span from its declaration. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct Attribute { pub name: Ident, pub args: Vec, @@ -55,7 +55,7 @@ pub struct Attribute { } /// Valid kinds of attributes supported by the compiler -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum AttributeKind { Doc, DocComment, @@ -104,7 +104,7 @@ impl AttributeKind { } /// Stores the attributes associated with the type. -#[derive(Default, Clone, Debug, Eq, PartialEq)] +#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AttributesMap(Arc>>); impl AttributesMap { diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 0cbf326b070..9dc3792c07d 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -765,7 +765,7 @@ pub fn item_impl_to_declaration( ) .map(ImplItem::Fn), sway_ast::ItemImplItem::Const(const_item) => item_const_to_constant_declaration( - context, handler, engines, const_item, attributes, true, + context, handler, engines, const_item, attributes, false, ) .map(ImplItem::Constant), sway_ast::ItemImplItem::Type(type_item) => trait_type_to_trait_type_declaration( @@ -1446,7 +1446,7 @@ pub(crate) fn type_name_to_type_info_opt(name: &Ident) -> Option { "str" => Some(TypeInfo::StringSlice), "raw_ptr" => Some(TypeInfo::RawUntypedPtr), "raw_slice" => Some(TypeInfo::RawUntypedSlice), - "Self" | "self" => Some(TypeInfo::new_self_type(name.span())), + "Self" => Some(TypeInfo::new_self_type(name.span())), "Contract" => Some(TypeInfo::Contract), _other => None, } @@ -2131,6 +2131,7 @@ fn expr_to_expression( kind: ExpressionKind::WhileLoop(WhileLoopExpression { condition: Box::new(expr_to_expression(context, handler, engines, *condition)?), body: braced_code_block_contents_to_code_block(context, handler, engines, block)?, + is_desugared_for_loop: false, }), span, }, @@ -3278,6 +3279,7 @@ fn for_expr_to_expression( span: Span::dummy(), }), body: while_body, + is_desugared_for_loop: true, }), span: Span::dummy(), }), @@ -3333,22 +3335,18 @@ fn path_root_opt_to_bool_and_qualified_path_root( close_angle_bracket_token: _, }), _, - )) => ( - false, - if let Some((_, path_type)) = as_trait { - Some(QualifiedPathType { - ty: ty_to_type_argument(context, handler, engines, *ty)?, - as_trait: engines.te().insert( - engines, - path_type_to_type_info(context, handler, engines, *path_type.clone())?, - path_type.span().source_id(), - ), - as_trait_span: path_type.span(), - }) - } else { - None - }, - ), + )) => (false, { + let (_, path_type) = as_trait; + Some(QualifiedPathType { + ty: ty_to_type_argument(context, handler, engines, *ty)?, + as_trait: engines.te().insert( + engines, + path_type_to_type_info(context, handler, engines, *path_type.clone())?, + path_type.span().source_id(), + ), + as_trait_span: path_type.span(), + }) + }), }) } @@ -4598,7 +4596,10 @@ fn path_type_to_type_info( } } None => { - if name.as_str() == "ContractCaller" { + if name.as_str() == "self" { + let error = ConvertParseTreeError::UnknownTypeNameSelf { span }; + return Err(handler.emit_err(error.into())); + } else if name.as_str() == "ContractCaller" { if root_opt.is_some() || !suffix.is_empty() { let error = ConvertParseTreeError::FullySpecifiedTypesNotSupported { span }; return Err(handler.emit_err(error.into())); diff --git a/sway-core/src/type_system/ast_elements/binding.rs b/sway-core/src/type_system/ast_elements/binding.rs index 3ea45397a51..78b4696e313 100644 --- a/sway-core/src/type_system/ast_elements/binding.rs +++ b/sway-core/src/type_system/ast_elements/binding.rs @@ -1,7 +1,3 @@ -use sway_ast::Intrinsic; -use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::{Span, Spanned}; - use crate::{ decl_engine::{ parsed_id::ParsedDeclId, DeclEngineGetParsedDeclId, DeclEngineInsert, DeclId, DeclRef, @@ -18,6 +14,10 @@ use crate::{ type_system::priv_prelude::*, EnforceTypeArguments, Ident, }; +use serde::{Deserialize, Serialize}; +use sway_ast::Intrinsic; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::{Span, Spanned}; /// A `TypeBinding` is the result of using turbofish to bind types to /// generic parameters. @@ -78,7 +78,7 @@ use crate::{ /// - `data4` has a type ascription and has type arguments in the `TypeBinding`, /// so, with the type from the value passed to `value`, all three are unified /// together -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TypeBinding { pub inner: T, pub type_arguments: TypeArgs, @@ -103,7 +103,7 @@ pub struct TypeBinding { /// ``` /// So we can have type parameters in the `Prefix` or `Regular` variant but not /// in both. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum TypeArgs { /// `Regular` variant indicates the type arguments are located after the suffix. Regular(Vec), @@ -220,7 +220,8 @@ impl TypeBinding> { // find the module that the symbol is in let type_info_prefix = ctx.namespace().prepend_module_path(&self.inner.prefixes); ctx.namespace() - .lookup_submodule_from_absolute_path(handler, engines, &type_info_prefix)?; + .root_module() + .lookup_submodule(handler, engines, &type_info_prefix)?; // create the type info object let type_info = type_info.apply_type_arguments( @@ -286,6 +287,7 @@ impl SymbolResolveTypeBinding for TypeBinding { let engines = ctx.engines(); // Grab the declaration. let unknown_decl = ctx.resolve_call_path_with_visibility_check(handler, &self.inner)?; + // Check to see if this is a function declaration. let fn_decl = unknown_decl .resolve_parsed(engines.de()) @@ -311,9 +313,7 @@ impl TypeCheckTypeBinding for TypeBinding { let decl_engine = ctx.engines.de(); // Grab the declaration. - let unknown_decl = ctx - .resolve_call_path_with_visibility_check(handler, &self.inner)? - .expect_typed(); + let unknown_decl = ctx.resolve_call_path_with_visibility_check(handler, &self.inner)?; // Check to see if this is a fn declaration. let fn_ref = unknown_decl.to_fn_ref(handler, ctx.engines())?; // Get a new copy from the declaration engine. @@ -389,9 +389,7 @@ impl TypeCheckTypeBinding for TypeBinding { let decl_engine = ctx.engines.de(); let engines = ctx.engines(); // Grab the declaration. - let unknown_decl = ctx - .resolve_call_path_with_visibility_check(handler, &self.inner)? - .expect_typed(); + let unknown_decl = ctx.resolve_call_path_with_visibility_check(handler, &self.inner)?; // Check to see if this is a struct declaration. let struct_id = unknown_decl.to_struct_decl(handler, engines)?; // Get a new copy from the declaration engine. @@ -431,9 +429,7 @@ impl TypeCheckTypeBinding for TypeBinding { let decl_engine = ctx.engines.de(); let engines = ctx.engines(); // Grab the declaration. - let unknown_decl = ctx - .resolve_call_path_with_visibility_check(handler, &self.inner)? - .expect_typed(); + let unknown_decl = ctx.resolve_call_path_with_visibility_check(handler, &self.inner)?; // Get a new copy from the declaration engine. let enum_id = if let ty::TyDecl::EnumVariantDecl(ty::EnumVariantDecl { enum_ref, .. }) = @@ -470,9 +466,7 @@ impl TypeBinding { ctx: &mut TypeCheckContext, ) -> Result>, ErrorEmitted> { // Grab the declaration. - let unknown_decl = ctx - .resolve_qualified_call_path_with_visibility_check(handler, &self.inner)? - .expect_typed(); + let unknown_decl = ctx.resolve_qualified_call_path(handler, &self.inner)?; // Check to see if this is a const declaration. let const_ref = unknown_decl.to_const_ref(handler, ctx.engines())?; @@ -495,9 +489,7 @@ impl TypeCheckTypeBinding for TypeBinding { ErrorEmitted, > { // Grab the declaration. - let unknown_decl = ctx - .resolve_call_path_with_visibility_check(handler, &self.inner)? - .expect_typed(); + let unknown_decl = ctx.resolve_call_path_with_visibility_check(handler, &self.inner)?; // Check to see if this is a const declaration. let const_ref = unknown_decl.to_const_ref(handler, ctx.engines())?; diff --git a/sway-core/src/type_system/ast_elements/trait_constraint.rs b/sway-core/src/type_system/ast_elements/trait_constraint.rs index cfe0b41bcb1..d0ed5e3b0dd 100644 --- a/sway-core/src/type_system/ast_elements/trait_constraint.rs +++ b/sway-core/src/type_system/ast_elements/trait_constraint.rs @@ -1,15 +1,3 @@ -use std::{ - cmp::Ordering, - fmt, - hash::{Hash, Hasher}, -}; - -use sway_error::{ - error::CompileError, - handler::{ErrorEmitted, Handler}, -}; -use sway_types::Spanned; - use crate::{ engine_threading::*, language::{parsed::Supertrait, ty, CallPath}, @@ -21,8 +9,19 @@ use crate::{ types::{CollectTypesMetadata, CollectTypesMetadataContext, TypeMetadata}, EnforceTypeArguments, }; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, +}; +use sway_error::{ + error::CompileError, + handler::{ErrorEmitted, Handler}, +}; +use sway_types::Spanned; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TraitConstraint { pub trait_name: CallPath, pub type_arguments: Vec, @@ -193,9 +192,8 @@ impl TraitConstraint { let mut type_arguments = type_arguments.clone(); match ctx - .namespace() // Use the default Handler to avoid emitting the redundant SymbolNotFound error. - .resolve_call_path_typed(&Handler::default(), engines, trait_name, ctx.self_type()) + .resolve_call_path(&Handler::default(), trait_name) .ok() { Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => { diff --git a/sway-core/src/type_system/ast_elements/type_argument.rs b/sway-core/src/type_system/ast_elements/type_argument.rs index bdddde93ad4..3ab7e5a0322 100644 --- a/sway-core/src/type_system/ast_elements/type_argument.rs +++ b/sway-core/src/type_system/ast_elements/type_argument.rs @@ -1,4 +1,5 @@ use crate::{engine_threading::*, language::CallPathTree, type_system::priv_prelude::*}; +use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt, hash::Hasher}; use sway_types::{Span, Spanned}; @@ -13,7 +14,7 @@ use sway_types::{Span, Spanned}; /// /// The annotations are ignored when calculating the [TypeArgument]'s hash /// (with engines) and equality (with engines). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TypeArgument { /// The [TypeId] of the "referenced" [TypeInfo]. pub type_id: TypeId, diff --git a/sway-core/src/type_system/ast_elements/type_parameter.rs b/sway-core/src/type_system/ast_elements/type_parameter.rs index cdd656aaef0..9f3d24cac81 100644 --- a/sway-core/src/type_system/ast_elements/type_parameter.rs +++ b/sway-core/src/type_system/ast_elements/type_parameter.rs @@ -3,23 +3,22 @@ use crate::{ engine_threading::*, has_changes, language::{ty, CallPath}, - namespace::TryInsertingTraitImplOnFailure, + namespace::{TraitMap, TryInsertingTraitImplOnFailure}, semantic_analysis::{GenericShadowingMode, TypeCheckContext}, type_system::priv_prelude::*, }; - -use sway_error::{ - error::CompileError, - handler::{ErrorEmitted, Handler}, -}; -use sway_types::{ident::Ident, span::Span, BaseIdent, Spanned}; - +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, collections::BTreeMap, fmt, hash::{Hash, Hasher}, }; +use sway_error::{ + error::CompileError, + handler::{ErrorEmitted, Handler}, +}; +use sway_types::{ident::Ident, span::Span, BaseIdent, Spanned}; /// [TypeParameter] describes a generic type parameter, including its /// monomorphized version. It holds the `name` of the parameter, its @@ -33,7 +32,7 @@ use std::{ /// /// The annotations are ignored when calculating the [TypeParameter]'s hash /// (with engines) and equality (with engines). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TypeParameter { pub type_id: TypeId, /// Denotes the initial type represented by the [TypeParameter], before @@ -275,11 +274,7 @@ impl TypeParameter { ctx: &TypeCheckContext, tc: &TraitConstraint, ) -> Vec { - match ctx - .namespace() - .resolve_call_path_typed(handler, ctx.engines, &tc.trait_name, ctx.self_type()) - .ok() - { + match ctx.resolve_call_path(handler, &tc.trait_name).ok() { Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => { let trait_decl = ctx.engines.de().get_trait(&decl_id); let mut result = trait_decl @@ -449,13 +444,11 @@ impl TypeParameter { if *is_from_parent { ctx = ctx.with_generic_shadowing_mode(GenericShadowingMode::Allow); - let sy = ctx - .namespace() - .module(ctx.engines()) - .current_items() - .symbols - .get(name) - .unwrap(); + let sy = ctx.namespace().module(ctx.engines()).resolve_symbol( + handler, + ctx.engines(), + name, + )?; match sy.expect_typed_ref() { ty::TyDecl::GenericTypeForFunctionScope(ty::GenericTypeForFunctionScope { @@ -576,13 +569,9 @@ impl TypeParameter { } } // Check to see if the trait constraints are satisfied. - match ctx - .namespace_mut() - .module_mut(engines) - .current_items_mut() - .implemented_traits - .check_if_trait_constraints_are_satisfied_for_type( + match TraitMap::check_if_trait_constraints_are_satisfied_for_type( handler, + ctx.namespace_mut().module_mut(engines), *type_id, trait_constraints, access_span, @@ -648,9 +637,8 @@ fn handle_trait( handler.scope(|handler| { match ctx - .namespace() // Use the default Handler to avoid emitting the redundant SymbolNotFound error. - .resolve_call_path_typed(&Handler::default(), engines, trait_name, ctx.self_type()) + .resolve_call_path(&Handler::default(), trait_name) .ok() { Some(ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. })) => { diff --git a/sway-core/src/type_system/id.rs b/sway-core/src/type_system/id.rs index 090ed6b370a..f429a0d2283 100644 --- a/sway-core/src/type_system/id.rs +++ b/sway-core/src/type_system/id.rs @@ -1,5 +1,6 @@ #![allow(clippy::mutable_key_type)] use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; use sway_error::{ error::CompileError, handler::{ErrorEmitted, Handler}, @@ -34,7 +35,7 @@ pub enum TreatNumericAs { } /// A identifier to uniquely refer to our type terms -#[derive(PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd, Debug)] +#[derive(PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd, Debug, Deserialize, Serialize)] pub struct TypeId(usize); impl DisplayWithEngines for TypeId { @@ -94,7 +95,10 @@ impl CollectTypesMetadata for TypeId { impl SubstTypes for TypeId { fn subst_inner(&mut self, ctx: &SubstTypesContext) -> HasChanges { let type_engine = ctx.engines.te(); - if let Some(matching_id) = ctx.type_subst_map.find_match(*self, ctx.engines) { + if let Some(matching_id) = ctx + .type_subst_map + .and_then(|tsm| tsm.find_match(*self, ctx.engines)) + { if !matches!(&*type_engine.get(matching_id), TypeInfo::ErrorRecovery(_)) { *self = matching_id; HasChanges::Yes diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index dc089c9c25f..e0304d62ed2 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -12,19 +12,19 @@ use crate::{ type_system::priv_prelude::*, Ident, }; -use sway_error::{ - error::{CompileError, InvalidImplementingForType}, - handler::{ErrorEmitted, Handler}, -}; -use sway_types::{integer_bits::IntegerBits, span::Span}; - +use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, fmt, hash::{Hash, Hasher}, }; +use sway_error::{ + error::{CompileError, InvalidImplementingForType}, + handler::{ErrorEmitted, Handler}, +}; +use sway_types::{integer_bits::IntegerBits, span::Span}; -#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum AbiName { Deferred, Known(CallPath), diff --git a/sway-core/src/type_system/monomorphization.rs b/sway-core/src/type_system/monomorphization.rs index ffe432f2292..6eb6581e96a 100644 --- a/sway-core/src/type_system/monomorphization.rs +++ b/sway-core/src/type_system/monomorphization.rs @@ -11,7 +11,7 @@ use crate::{ CallPath, }, namespace::{ModulePath, ResolvedDeclaration}, - semantic_analysis::type_resolve::resolve_type, + semantic_analysis::type_resolve::{resolve_type, VisibilityCheck}, type_system::ast_elements::create_type_id::CreateTypeId, EnforceTypeArguments, Engines, Namespace, SubstTypes, SubstTypesContext, TypeArgument, TypeId, TypeParameter, TypeSubstMap, @@ -129,6 +129,7 @@ where None, self_type, subst_ctx, + VisibilityCheck::Yes, ) .unwrap_or_else(|err| engines.te().id_of_error_recovery(err)); } diff --git a/sway-core/src/type_system/substitute/subst_types.rs b/sway-core/src/type_system/substitute/subst_types.rs index f5107ebd59a..8b2ea5068e1 100644 --- a/sway-core/src/type_system/substitute/subst_types.rs +++ b/sway-core/src/type_system/substitute/subst_types.rs @@ -24,31 +24,39 @@ impl std::ops::BitOr for HasChanges { } } -pub struct SubstTypesContext<'a, 'b> { - pub engines: &'a Engines, - pub type_subst_map: &'b TypeSubstMap, +pub struct SubstTypesContext<'eng, 'tsm> { + pub engines: &'eng Engines, + pub type_subst_map: Option<&'tsm TypeSubstMap>, pub subst_function_body: bool, } -impl<'a, 'b> SubstTypesContext<'a, 'b> { +impl<'eng, 'tsm> SubstTypesContext<'eng, 'tsm> { pub fn new( - engines: &'a Engines, - type_subst_map: &'b TypeSubstMap, + engines: &'eng Engines, + type_subst_map: &'tsm TypeSubstMap, subst_function_body: bool, - ) -> SubstTypesContext<'a, 'b> { + ) -> SubstTypesContext<'eng, 'tsm> { SubstTypesContext { engines, - type_subst_map, + type_subst_map: Some(type_subst_map), subst_function_body, } } + + pub fn dummy(engines: &'eng Engines) -> SubstTypesContext<'eng, 'tsm> { + SubstTypesContext { + engines, + type_subst_map: None, + subst_function_body: false, + } + } } pub trait SubstTypes { fn subst_inner(&mut self, ctx: &SubstTypesContext) -> HasChanges; fn subst(&mut self, ctx: &SubstTypesContext) -> HasChanges { - if ctx.type_subst_map.is_empty() { + if ctx.type_subst_map.is_some_and(|tsm| tsm.is_empty()) { HasChanges::No } else { self.subst_inner(ctx) diff --git a/sway-error/src/convert_parse_tree_error.rs b/sway-error/src/convert_parse_tree_error.rs index 8b8a96528a0..fc95d213b37 100644 --- a/sway-error/src/convert_parse_tree_error.rs +++ b/sway-error/src/convert_parse_tree_error.rs @@ -121,6 +121,8 @@ pub enum ConvertParseTreeError { UnexpectedValueForCfgExperimental { span: Span }, #[error("Unexpected attribute value: \"{value}\" for attribute: \"cfg\"")] InvalidCfgArg { span: Span, value: String }, + #[error("Unknown type name \"self\". A self type with a similar name exists (notice the capitalization): `Self`")] + UnknownTypeNameSelf { span: Span }, } impl Spanned for ConvertParseTreeError { @@ -185,6 +187,7 @@ impl Spanned for ConvertParseTreeError { ConvertParseTreeError::ExpectedCfgProgramTypeArgValue { span } => span.clone(), ConvertParseTreeError::UnexpectedValueForCfgExperimental { span } => span.clone(), ConvertParseTreeError::InvalidCfgArg { span, .. } => span.clone(), + ConvertParseTreeError::UnknownTypeNameSelf { span } => span.clone(), } } } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index fb129fa9903..d9ce634fa1b 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -679,6 +679,8 @@ pub enum CompileError { span: Span, prefix_span: Span, }, + #[error("Constant requires expression.")] + ConstantRequiresExpression { span: Span }, #[error("Constants cannot be shadowed. {shadowing_source} \"{name}\" shadows constant of the same name.")] ConstantsCannotBeShadowed { /// Defines what shadows the constant. @@ -1161,6 +1163,7 @@ impl Spanned for CompileError { ContractStorageFromExternalContext { span, .. } => span.clone(), InvalidOpcodeFromPredicate { span, .. } => span.clone(), ArrayOutOfBounds { span, .. } => span.clone(), + ConstantRequiresExpression { span, .. } => span.clone(), ConstantsCannotBeShadowed { name, .. } => name.span(), ConfigurablesCannotBeShadowed { name, .. } => name.span(), ConfigurablesCannotBeMatchedAgainst { name, .. } => name.span(), diff --git a/sway-error/src/handler.rs b/sway-error/src/handler.rs index 24d2222da1b..ddb4d3b24f5 100644 --- a/sway-error/src/handler.rs +++ b/sway-error/src/handler.rs @@ -1,5 +1,4 @@ use crate::{error::CompileError, warning::CompileWarning}; - use core::cell::RefCell; /// A handler with which you can emit diagnostics. @@ -127,7 +126,7 @@ impl Handler { } /// Proof that an error was emitted through a `Handler`. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub struct ErrorEmitted { _priv: (), } diff --git a/sway-ir/src/optimize/memcpyopt.rs b/sway-ir/src/optimize/memcpyopt.rs index 265db81695b..fbce2059c59 100644 --- a/sway-ir/src/optimize/memcpyopt.rs +++ b/sway-ir/src/optimize/memcpyopt.rs @@ -301,7 +301,6 @@ fn local_copy_prop( available_copies.remove(copy); } } - copies.retain(|copy| available_copies.contains(copy)); } if let Some(copies) = dest_to_copies.get_mut(&sym) { for copy in &*copies { @@ -333,9 +332,17 @@ fn local_copy_prop( available_copies.remove(copy); } } - copies.retain(|copy| available_copies.contains(copy)); } } + // Update src_to_copies and dest_to_copies to remove every copy not in available_copies. + src_to_copies.retain(|_, copies| { + copies.retain(|copy| available_copies.contains(copy)); + !copies.is_empty() + }); + dest_to_copies.retain(|_, copies| { + copies.retain(|copy| available_copies.contains(copy)); + !copies.is_empty() + }); } ReferredSymbols::Incomplete(_) => { // The only safe thing we can do is to clear all information. diff --git a/sway-lib-core/src/primitive_conversions.sw b/sway-lib-core/src/primitive_conversions.sw index 3160a5eca02..f0a1a14de81 100644 --- a/sway-lib-core/src/primitive_conversions.sw +++ b/sway-lib-core/src/primitive_conversions.sw @@ -45,10 +45,7 @@ impl u32 { input: u64 } } -} -// TODO: This must be in a separate impl block until https://github.com/FuelLabs/sway/issues/1548 is resolved -impl u32 { /// Extends a `u32` to a `u256`. /// /// # Returns @@ -114,10 +111,7 @@ impl u16 { input: u64 } } -} -// TODO: This must be in a separate impl block until https://github.com/FuelLabs/sway/issues/1548 is resolved -impl u16 { /// Extends a `u16` to a `u256`. /// /// # Returns @@ -204,10 +198,7 @@ impl u8 { input: u64 } } -} -// TODO: This must be in a separate impl block until https://github.com/FuelLabs/sway/issues/1548 is resolved -impl u8 { /// Extends a `u8` to a `u256`. /// /// # Returns diff --git a/sway-lib-std/src/bytes.sw b/sway-lib-std/src/bytes.sw index c02b097811f..5b5c817e914 100644 --- a/sway-lib-std/src/bytes.sw +++ b/sway-lib-std/src/bytes.sw @@ -584,10 +584,7 @@ impl Bytes { pub fn ptr(self) -> raw_ptr { self.buf.ptr() } -} -// Need to use separate impl blocks for now: https://github.com/FuelLabs/sway/issues/1548 -impl Bytes { /// Divides one Bytes into two at an index. /// /// # Additional Information @@ -692,7 +689,7 @@ impl Bytes { /// assert(bytes.capacity() == first_cap + second_cap); /// } /// ``` - pub fn append(ref mut self, ref mut other: self) { + pub fn append(ref mut self, ref mut other: Self) { let other_len = other.len(); if other_len == 0 { return diff --git a/sway-lib-std/src/outputs.sw b/sway-lib-std/src/outputs.sw index 69ccac1b23b..527a94de2fc 100644 --- a/sway-lib-std/src/outputs.sw +++ b/sway-lib-std/src/outputs.sw @@ -28,6 +28,9 @@ pub const GTF_OUTPUT_COIN_ASSET_ID = 0x303; // pub const GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID = 0x307; // pub const GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT = 0x308; +const OUTPUT_VARIABLE_ASSET_ID_OFFSET = 48; +const OUTPUT_VARIABLE_TO_OFFSET = 8; + /// The output type for a transaction. pub enum Output { /// A coin output. @@ -228,6 +231,10 @@ pub fn output_asset_id(index: u64) -> Option { match output_type(index) { Some(Output::Coin) => Some(AssetId::from(__gtf::(index, GTF_OUTPUT_COIN_ASSET_ID))), Some(Output::Change) => Some(AssetId::from(__gtf::(index, GTF_OUTPUT_COIN_ASSET_ID))), + Some(Output::Variable) => { + let ptr = output_pointer(index).unwrap(); + Some(AssetId::from(ptr.add_uint_offset(OUTPUT_VARIABLE_ASSET_ID_OFFSET).read::())) + }, _ => None, } } @@ -260,6 +267,10 @@ pub fn output_asset_to(index: u64) -> Option

{ match output_type(index) { Some(Output::Coin) => Some(__gtf::
(index, GTF_OUTPUT_COIN_TO)), Some(Output::Change) => Some(__gtf::
(index, GTF_OUTPUT_COIN_TO)), + Some(Output::Variable) => { + let ptr = output_pointer(index).unwrap(); + Some(Address::from(ptr.add_uint_offset(OUTPUT_VARIABLE_TO_OFFSET).read::())) + }, _ => None, } } diff --git a/sway-lib-std/src/prelude.sw b/sway-lib-std/src/prelude.sw index f8d42cf0044..b86eb26ec31 100644 --- a/sway-lib-std/src/prelude.sw +++ b/sway-lib-std/src/prelude.sw @@ -27,7 +27,7 @@ pub use ::revert::{require, revert, revert_with_log}; pub use ::convert::From; // Primitive conversions -pub use ::primitive_conversions::{b256::*, str::*, u16::*, u256::*, u32::*, u64::*, u8::*,}; +pub use ::primitive_conversions::{b256::*, str::*, u16::*, u256::*, u32::*, u64::*, u8::*}; // Logging pub use ::logging::log; diff --git a/sway-lib-std/src/u128.sw b/sway-lib-std/src/u128.sw index 8eb22317b64..ef016cfe560 100644 --- a/sway-lib-std/src/u128.sw +++ b/sway-lib-std/src/u128.sw @@ -668,6 +668,17 @@ impl core::ops::Divide for U128 { } } +impl core::ops::Mod for U128 { + fn modulo(self, other: Self) -> Self { + assert(other != Self::zero()); + + // a mod b = a - b * (a / b) + let quotient = self / other; + let product = quotient * other; + self - product + } +} + fn u64_checked_add(a: u64, b: u64) -> Option { let of = asm(a: a, b: b, res) { add res a b; diff --git a/sway-lsp/benches/lsp_benchmarks/requests.rs b/sway-lsp/benches/lsp_benchmarks/requests.rs index c2c79eaf25c..ce9081cd7a9 100644 --- a/sway-lsp/benches/lsp_benchmarks/requests.rs +++ b/sway-lsp/benches/lsp_benchmarks/requests.rs @@ -24,8 +24,8 @@ fn benchmarks(c: &mut Criterion) { c.bench_function("document_symbol", |b| { b.iter(|| { session - .symbol_information(&uri) - .map(DocumentSymbolResponse::Flat) + .document_symbols(&uri) + .map(DocumentSymbolResponse::Nested) }) }); diff --git a/sway-lsp/src/capabilities/completion.rs b/sway-lsp/src/capabilities/completion.rs index a2b77814cb9..8c057a20675 100644 --- a/sway-lsp/src/capabilities/completion.rs +++ b/sway-lsp/src/capabilities/completion.rs @@ -5,12 +5,11 @@ use lsp_types::{ }; use sway_core::{ language::ty::{TyAstNodeContent, TyDecl, TyFunctionDecl, TyFunctionParameter}, - namespace::Items, - Engines, TypeId, TypeInfo, + Engines, Namespace, TypeId, TypeInfo, }; pub(crate) fn to_completion_items( - namespace: &Items, + namespace: &Namespace, engines: &Engines, ident_to_complete: &TokenIdent, fn_decl: &TyFunctionDecl, @@ -24,7 +23,7 @@ pub(crate) fn to_completion_items( /// Gathers the given [`TypeId`] struct's fields and methods and builds completion items. fn completion_items_for_type_id( engines: &Engines, - namespace: &Items, + namespace: &Namespace, type_id: TypeId, position: Position, ) -> Vec { @@ -46,7 +45,10 @@ fn completion_items_for_type_id( } } - for method in namespace.get_methods_for_type(engines, type_id) { + for method in namespace + .module(engines) + .get_methods_for_type(engines, type_id) + { let method = method.expect_typed(); let fn_decl = engines.de().get_function(&method.id().clone()); let params = &fn_decl.parameters; @@ -134,7 +136,7 @@ fn replace_self_with_type_str( /// if it can resolve `a` in the given function. fn type_id_of_raw_ident( engines: &Engines, - namespace: &Items, + namespace: &Namespace, ident_name: &str, fn_decl: &TyFunctionDecl, ) -> Option { @@ -152,6 +154,7 @@ fn type_id_of_raw_ident( if parts[i].ends_with(')') { let method_name = parts[i].split_at(parts[i].find('(').unwrap_or(0)).0; curr_type_id = namespace + .module(engines) .get_methods_for_type(engines, curr_type_id?) .into_iter() .find_map(|method| { diff --git a/sway-lsp/src/capabilities/document_symbol.rs b/sway-lsp/src/capabilities/document_symbol.rs index 79f90b1eeba..5e31b65ff4a 100644 --- a/sway-lsp/src/capabilities/document_symbol.rs +++ b/sway-lsp/src/capabilities/document_symbol.rs @@ -1,60 +1,514 @@ -use crate::core::token::{SymbolKind, Token, TokenIdent}; -use dashmap::mapref::multiple::RefMulti; -use lsp_types::{self, Location, SymbolInformation, Url}; - -pub fn to_symbol_information<'a, I>(tokens: I, url: &Url) -> Vec -where - I: Iterator>, -{ - tokens - .map(|entry| { - let (ident, token) = entry.pair(); - symbol_info(ident, token, url.clone()) +use crate::core::{token::get_range_from_span, token_map::TokenMap}; +use lsp_types::{self, DocumentSymbol, Url}; +use std::path::PathBuf; +use sway_core::{ + language::ty::{ + TyAbiDecl, TyAstNodeContent, TyConstantDecl, TyDecl, TyEnumDecl, TyFunctionDecl, + TyFunctionParameter, TyProgram, TyStorageDecl, TyStructDecl, TyTraitInterfaceItem, + TyTraitItem, TyTraitType, + }, + Engines, TypeArgument, +}; +use sway_types::{Span, Spanned}; + +/// Generates a hierarchical document symbol tree for LSP code outline/navigation. +/// Processes declarations (functions, structs, enums, etc.) into nested symbols, +/// preserving parent-child relationships like functions with their variables, +/// structs with their fields, and traits with their methods. +pub fn to_document_symbols( + uri: &Url, + path: &PathBuf, + ty_program: &TyProgram, + engines: &Engines, + token_map: &TokenMap, +) -> Vec { + let source_id = engines.se().get_source_id(path); + + // Find if there is a configurable symbol in the token map that belongs to the current file + // We will add children symbols to this when we encounter configurable declarations below. + let mut configurable_symbol = token_map + .tokens_for_file(uri) + .find(|item| item.key().name == "configurable") + .map(|item| { + DocumentSymbolBuilder::new() + .name(item.key().name.clone()) + .kind(lsp_types::SymbolKind::STRUCT) + .range(item.key().range) + .selection_range(item.key().range) + .children(vec![]) + .build() + }); + + // Only include nodes that originate from the file. + let mut nodes: Vec<_> = (if ty_program.root.span.source_id() == Some(&source_id) { + Some(ty_program.root.all_nodes.iter()) + } else { + ty_program + .root + .submodules_recursive() + .find(|(_, submodule)| submodule.module.span.source_id() == Some(&source_id)) + .map(|(_, submodule)| submodule.module.all_nodes.iter()) + }) + .into_iter() + .flatten() + .filter_map(|node| { + match &node.content { + TyAstNodeContent::Declaration(decl) => match decl { + TyDecl::FunctionDecl(decl) => { + let fn_decl = engines.de().get_function(&decl.decl_id); + let range = get_range_from_span(&fn_decl.name.span()); + let detail = Some(fn_decl_detail(&fn_decl.parameters, &fn_decl.return_type)); + let children = collect_variables_from_func_decl(engines, &fn_decl); + let func_symbol = DocumentSymbolBuilder::new() + .name(fn_decl.name.span().str().to_string()) + .kind(lsp_types::SymbolKind::FUNCTION) + .range(range) + .selection_range(range) + .detail(detail) + .children(children) + .build(); + Some(func_symbol) + } + TyDecl::EnumDecl(decl) => { + let enum_decl = engines.de().get_enum(&decl.decl_id); + let span = enum_decl.call_path.suffix.span(); + let range = get_range_from_span(&span); + let children = collect_enum_variants(&enum_decl); + let enum_symbol = DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::ENUM) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(enum_symbol) + } + TyDecl::StructDecl(decl) => { + let struct_decl = engines.de().get_struct(&decl.decl_id); + let span = struct_decl.call_path.suffix.span(); + let range = get_range_from_span(&span); + let children = collect_struct_fields(&struct_decl); + let struct_symbol = DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::STRUCT) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(struct_symbol) + } + TyDecl::AbiDecl(decl) => { + let abi_decl = engines.de().get_abi(&decl.decl_id); + let decl_str = abi_decl.span().str(); + let name = extract_header(&decl_str); + let range = get_range_from_span(&abi_decl.name.span()); + let children = collect_fns_from_abi_decl(engines, &abi_decl); + let abi_symbol = DocumentSymbolBuilder::new() + .name(name) + .kind(lsp_types::SymbolKind::NAMESPACE) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(abi_symbol) + } + TyDecl::TraitDecl(decl) => { + let trait_decl = engines.de().get_trait(&decl.decl_id); + let decl_str = trait_decl.span().str().to_string(); + let name = extract_header(&decl_str); + let range = get_range_from_span(&trait_decl.name.span()); + let children = + collect_interface_surface(engines, &trait_decl.interface_surface); + let trait_symbol = DocumentSymbolBuilder::new() + .name(name) + .kind(lsp_types::SymbolKind::INTERFACE) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(trait_symbol) + } + TyDecl::TraitTypeDecl(decl) => { + let trait_type_decl = engines.de().get_type(&decl.decl_id); + Some(build_trait_symbol(&trait_type_decl)) + } + TyDecl::ImplSelfOrTrait(decl) => { + let impl_trait_decl = engines.de().get_impl_self_or_trait(&decl.decl_id); + let decl_str = impl_trait_decl.span().str().to_string(); + let name = extract_header(&decl_str); + let range = get_range_from_span(&impl_trait_decl.trait_name.suffix.span()); + let children = collect_ty_trait_items(engines, &impl_trait_decl.items); + let symbol = DocumentSymbolBuilder::new() + .name(name) + .kind(lsp_types::SymbolKind::NAMESPACE) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(symbol) + } + TyDecl::ConstantDecl(decl) => { + let const_decl = engines.de().get_constant(&decl.decl_id); + Some(build_constant_symbol(&const_decl)) + } + TyDecl::StorageDecl(decl) => { + let storage_decl = engines.de().get_storage(&decl.decl_id); + let span = storage_decl.storage_keyword.span(); + let range = get_range_from_span(&span); + let children = collect_fields_from_storage(&storage_decl); + let storage_symbol = DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::STRUCT) + .range(range) + .selection_range(range) + .children(children) + .build(); + Some(storage_symbol) + } + TyDecl::ConfigurableDecl(decl) => { + let configurable_decl = engines.de().get_configurable(&decl.decl_id); + let span = configurable_decl.call_path.suffix.span(); + let range = get_range_from_span(&span); + let symbol = DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::FIELD) + .detail(Some( + configurable_decl.type_ascription.span.as_str().to_string(), + )) + .range(range) + .selection_range(range) + .build(); + // Add symbol to the end of configurable_symbol's children field + configurable_symbol + .as_mut()? + .children + .as_mut()? + .push(symbol); + None + } + _ => None, + }, + _ => None, + } + }) + .collect(); + + // Add configurable symbol to the end after all children symbols have been added + if let Some(symbol) = configurable_symbol { + nodes.push(symbol); + } + + // Sort by range start position + nodes.sort_by_key(|node| node.range.start); + nodes +} + +fn build_constant_symbol(const_decl: &TyConstantDecl) -> DocumentSymbol { + let span = const_decl.call_path.suffix.span(); + let range = get_range_from_span(&span); + DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::CONSTANT) + .detail(Some(const_decl.type_ascription.span.as_str().to_string())) + .range(range) + .selection_range(range) + .build() +} + +fn build_trait_symbol(trait_type_decl: &TyTraitType) -> DocumentSymbol { + let span = trait_type_decl.name.span(); + let range = get_range_from_span(&span); + DocumentSymbolBuilder::new() + .name(span.str().to_string()) + .kind(lsp_types::SymbolKind::TYPE_PARAMETER) + .range(range) + .selection_range(range) + .build() +} + +fn collect_interface_surface( + engines: &Engines, + items: &[TyTraitInterfaceItem], +) -> Vec { + items + .iter() + .map(|item| match item { + TyTraitInterfaceItem::TraitFn(decl_ref) => { + let fn_decl = engines.de().get_trait_fn(decl_ref); + build_function_symbol( + &fn_decl.name.span(), + &fn_decl.parameters, + &fn_decl.return_type, + ) + } + TyTraitInterfaceItem::Constant(decl_ref) => { + let const_decl = engines.de().get_constant(decl_ref); + build_constant_symbol(&const_decl) + } + TyTraitInterfaceItem::Type(decl_ref) => { + let trait_type_decl = engines.de().get_type(decl_ref); + build_trait_symbol(&trait_type_decl) + } + }) + .collect() +} + +fn collect_ty_trait_items(engines: &Engines, items: &[TyTraitItem]) -> Vec { + items + .iter() + .filter_map(|item| match item { + TyTraitItem::Fn(decl_ref) => Some(engines.de().get_function(decl_ref)), + _ => None, + }) + .map(|fn_decl| { + let children = collect_variables_from_func_decl(engines, &fn_decl); + let mut symbol = build_function_symbol( + &fn_decl.name.span(), + &fn_decl.parameters, + &fn_decl.return_type, + ); + symbol.children = Some(children); + symbol }) .collect() } -/// Given a `token::SymbolKind`, return the `lsp_types::SymbolKind` that corresponds to it. -pub(crate) fn symbol_kind(symbol_kind: &SymbolKind) -> lsp_types::SymbolKind { - match symbol_kind { - SymbolKind::Field => lsp_types::SymbolKind::FIELD, - SymbolKind::BuiltinType | SymbolKind::TypeParameter => { - lsp_types::SymbolKind::TYPE_PARAMETER +fn collect_fields_from_storage(decl: &TyStorageDecl) -> Vec { + decl.fields + .iter() + .map(|field| build_field_symbol(&field.name.span(), &field.type_argument)) + .collect() +} + +fn build_field_symbol(span: &Span, type_argument: &TypeArgument) -> DocumentSymbol { + let range = get_range_from_span(span); + DocumentSymbolBuilder::new() + .name(span.clone().str().to_string()) + .detail(Some(type_argument.span.as_str().to_string())) + .kind(lsp_types::SymbolKind::FIELD) + .range(range) + .selection_range(range) + .build() +} + +fn build_function_symbol( + span: &Span, + parameters: &[TyFunctionParameter], + return_type: &TypeArgument, +) -> DocumentSymbol { + let range = get_range_from_span(span); + DocumentSymbolBuilder::new() + .name(span.clone().str().to_string()) + .detail(Some(fn_decl_detail(parameters, return_type))) + .kind(lsp_types::SymbolKind::FUNCTION) + .range(range) + .selection_range(range) + .build() +} + +fn collect_fns_from_abi_decl(engines: &Engines, decl: &TyAbiDecl) -> Vec { + decl.interface_surface + .iter() + .filter_map(|item| match item { + TyTraitInterfaceItem::TraitFn(decl_ref) => Some(engines.de().get_trait_fn(decl_ref)), + _ => None, + }) + .map(|trait_fn| { + build_function_symbol( + &trait_fn.name.span(), + &trait_fn.parameters, + &trait_fn.return_type, + ) + }) + .collect() +} + +fn collect_struct_fields(decl: &TyStructDecl) -> Vec { + decl.fields + .iter() + .map(|field| build_field_symbol(&field.name.span(), &field.type_argument)) + .collect() +} + +// Collect all enum variants +fn collect_enum_variants(decl: &TyEnumDecl) -> Vec { + decl.variants + .iter() + .map(|variant| { + let range = get_range_from_span(&variant.name.span()); + // Check for the presence of a CallPathTree, and if it exists, use the type information as the detail. + let detail = variant + .type_argument + .call_path_tree + .as_ref() + .map(|_| Some(variant.type_argument.span.as_str().to_string())) + .unwrap_or(None); + + DocumentSymbolBuilder::new() + .name(variant.name.span().str().to_string()) + .kind(lsp_types::SymbolKind::ENUM_MEMBER) + .range(range) + .selection_range(range) + .detail(detail) + .build() + }) + .collect() +} + +// Collect all variables declared within the function body +fn collect_variables_from_func_decl( + engines: &Engines, + decl: &TyFunctionDecl, +) -> Vec { + decl.body + .contents + .iter() + .filter_map(|node| { + if let TyAstNodeContent::Declaration(TyDecl::VariableDecl(var_decl)) = &node.content { + let range = get_range_from_span(&var_decl.name.span()); + let type_name = format!("{}", engines.help_out(var_decl.type_ascription.type_id)); + let symbol = DocumentSymbolBuilder::new() + .name(var_decl.name.span().str().to_string()) + .kind(lsp_types::SymbolKind::VARIABLE) + .range(range) + .selection_range(range) + .detail((!type_name.is_empty()).then_some(type_name)) + .build(); + Some(symbol) + } else { + None + } + }) + .collect() +} + +// Generate the signature for functions +fn fn_decl_detail(parameters: &[TyFunctionParameter], return_type: &TypeArgument) -> String { + let params = parameters + .iter() + .map(|p| format!("{}: {}", p.name, p.type_argument.span.as_str())) + .collect::>() + .join(", "); + + // Check for the presence of a CallPathTree, and if it exists, add it to the return type. + let return_type = return_type + .call_path_tree + .as_ref() + .map(|_| format!(" -> {}", return_type.span.as_str())) + .unwrap_or_default(); + format!("fn({}){}", params, return_type) +} + +/// Extracts the header of a sway construct such as an `impl` block or `abi` declaration, +/// including any generic parameters, traits, or super traits, up to (but not including) +/// the opening `{` character. Trims any trailing whitespace. +/// +/// If the `{` character is not found, the entire string is returned without trailing whitespace. +/// +/// # Examples +/// +/// ``` +/// let impl_example = "impl Setter for FooBarData {\n fn set(self, new_value: T) -> Self {\n FooBarData {\n value: new_value,\n }\n }\n}"; +/// let result = extract_header(impl_example); +/// assert_eq!(result, "impl Setter for FooBarData"); +/// +/// let abi_example = "abi MyAbi : MySuperAbi {\n fn bar();\n}"; +/// let result = extract_header(abi_example); +/// assert_eq!(result, "abi MyAbi : MySuperAbi"); +/// ``` +fn extract_header(s: &str) -> &str { + if let Some(pos) = s.find('{') { + s[..pos].trim_end() + } else { + s.trim_end() + } +} + +/// Builder for creating [`DocumentSymbol`] instances with method chaining. +/// Initializes with empty name, NULL kind, and zero position ranges. +pub struct DocumentSymbolBuilder { + name: String, + detail: Option, + kind: lsp_types::SymbolKind, + tags: Option>, + range: lsp_types::Range, + selection_range: lsp_types::Range, + children: Option>, + deprecated: Option, +} + +impl Default for DocumentSymbolBuilder { + fn default() -> Self { + Self::new() + } +} + +impl DocumentSymbolBuilder { + pub fn new() -> Self { + Self { + name: String::new(), + kind: lsp_types::SymbolKind::NULL, + range: lsp_types::Range::new( + lsp_types::Position::new(0, 0), + lsp_types::Position::new(0, 0), + ), + selection_range: lsp_types::Range::new( + lsp_types::Position::new(0, 0), + lsp_types::Position::new(0, 0), + ), + detail: None, + tags: None, + children: None, + deprecated: None, } - SymbolKind::Function | SymbolKind::DeriveHelper | SymbolKind::Intrinsic => { - lsp_types::SymbolKind::FUNCTION + } + + pub fn name(mut self, name: impl Into) -> Self { + self.name = name.into(); + self + } + + pub fn kind(mut self, kind: lsp_types::SymbolKind) -> Self { + self.kind = kind; + self + } + + pub fn range(mut self, range: lsp_types::Range) -> Self { + self.range = range; + self + } + + pub fn selection_range(mut self, range: lsp_types::Range) -> Self { + self.selection_range = range; + self + } + + pub fn detail(mut self, detail: Option) -> Self { + self.detail = detail; + self + } + + pub fn tags(mut self, tags: Vec) -> Self { + self.tags = Some(tags); + self + } + + pub fn children(mut self, children: Vec) -> Self { + self.children = Some(children); + self + } + + pub fn build(self) -> DocumentSymbol { + #[allow(warnings)] + DocumentSymbol { + name: self.name, + detail: self.detail, + kind: self.kind, + tags: self.tags, + range: self.range, + selection_range: self.selection_range, + children: self.children, + deprecated: self.deprecated, } - SymbolKind::Const => lsp_types::SymbolKind::CONSTANT, - SymbolKind::Struct => lsp_types::SymbolKind::STRUCT, - SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE, - SymbolKind::Module => lsp_types::SymbolKind::MODULE, - SymbolKind::Enum => lsp_types::SymbolKind::ENUM, - SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER, - SymbolKind::BoolLiteral => lsp_types::SymbolKind::BOOLEAN, - SymbolKind::StringLiteral => lsp_types::SymbolKind::STRING, - SymbolKind::NumericLiteral => lsp_types::SymbolKind::NUMBER, - SymbolKind::ValueParam - | SymbolKind::ByteLiteral - | SymbolKind::Variable - | SymbolKind::TypeAlias - | SymbolKind::TraitType - | SymbolKind::Keyword - | SymbolKind::SelfKeyword - | SymbolKind::SelfTypeKeyword - | SymbolKind::ProgramTypeKeyword - | SymbolKind::Unknown => lsp_types::SymbolKind::VARIABLE, - } -} - -#[allow(warnings)] -// TODO: the "deprecated: None" field is deprecated according to this library -fn symbol_info(ident: &TokenIdent, token: &Token, url: Url) -> SymbolInformation { - SymbolInformation { - name: ident.name.to_string(), - kind: symbol_kind(&token.kind), - location: Location::new(url, ident.range), - tags: None, - container_name: None, - deprecated: None, } } diff --git a/sway-lsp/src/capabilities/formatting.rs b/sway-lsp/src/capabilities/formatting.rs index e4b1596991b..06d5b58b59b 100644 --- a/sway-lsp/src/capabilities/formatting.rs +++ b/sway-lsp/src/capabilities/formatting.rs @@ -24,7 +24,7 @@ pub fn get_page_text_edit( ) -> Result { // we only format if code is correct let formatted_code = formatter - .format(text.clone(), None) + .format(text.clone()) .map_err(LanguageServerError::FormatError)?; let text_lines_count = text.split('\n').count(); diff --git a/sway-lsp/src/core/session.rs b/sway-lsp/src/core/session.rs index 0129c8fa583..864cac9811a 100644 --- a/sway-lsp/src/core/session.rs +++ b/sway-lsp/src/core/session.rs @@ -19,7 +19,7 @@ use crate::{ use dashmap::DashMap; use forc_pkg as pkg; use lsp_types::{ - CompletionItem, GotoDefinitionResponse, Location, Position, Range, SymbolInformation, Url, + CompletionItem, DocumentSymbol, GotoDefinitionResponse, Location, Position, Range, Url, }; use parking_lot::RwLock; use pkg::{ @@ -231,7 +231,7 @@ impl Session { let program = compiled_program.typed.clone()?; let engines = self.engines.read(); return Some(capabilities::completion::to_completion_items( - program.root.namespace.module(&engines).current_items(), + &program.root.namespace, &engines, ident_to_complete, fn_decl, @@ -248,12 +248,23 @@ impl Session { Some(program.root.namespace) } - pub fn symbol_information(&self, url: &Url) -> Option> { - let _p = tracing::trace_span!("symbol_information").entered(); - let tokens = self.token_map.tokens_for_file(url); - self.sync - .to_workspace_url(url.clone()) - .map(|url| capabilities::document_symbol::to_symbol_information(tokens, &url)) + /// Generate hierarchical document symbols for the given file. + pub fn document_symbols(&self, url: &Url) -> Option> { + let _p = tracing::trace_span!("document_symbols").entered(); + let path = url.to_file_path().ok()?; + self.compiled_program + .read() + .typed + .as_ref() + .map(|ty_program| { + capabilities::document_symbol::to_document_symbols( + url, + &path, + ty_program, + &self.engines.read(), + &self.token_map, + ) + }) } /// Populate [Documents] with sway files found in the workspace. diff --git a/sway-lsp/src/handlers/request.rs b/sway-lsp/src/handlers/request.rs index fb4b72d86a8..68d9de0b27c 100644 --- a/sway-lsp/src/handlers/request.rs +++ b/sway-lsp/src/handlers/request.rs @@ -67,8 +67,8 @@ pub async fn handle_document_symbol( .await { Ok((uri, session)) => Ok(session - .symbol_information(&uri) - .map(DocumentSymbolResponse::Flat)), + .document_symbols(&uri) + .map(DocumentSymbolResponse::Nested)), Err(err) => { tracing::error!("{}", err.to_string()); Ok(None) diff --git a/sway-lsp/tests/integration/lsp.rs b/sway-lsp/tests/integration/lsp.rs index b187510b5fb..0b5303994cb 100644 --- a/sway-lsp/tests/integration/lsp.rs +++ b/sway-lsp/tests/integration/lsp.rs @@ -247,7 +247,7 @@ pub(crate) async fn semantic_tokens_request(server: &ServerState, uri: &Url) { } } -pub(crate) async fn document_symbol_request(server: &ServerState, uri: &Url) { +pub(crate) async fn document_symbols_request(server: &ServerState, uri: &Url) { let params = DocumentSymbolParams { text_document: TextDocumentIdentifier { uri: uri.clone() }, work_done_progress_params: Default::default(), @@ -256,8 +256,62 @@ pub(crate) async fn document_symbol_request(server: &ServerState, uri: &Url) { let response = request::handle_document_symbol(server, params) .await .unwrap(); - if let Some(DocumentSymbolResponse::Flat(res)) = response { - assert!(!res.is_empty()); + + if let Some(DocumentSymbolResponse::Nested(symbols)) = response { + // Check for enum with its variants + let enum_symbol = symbols + .iter() + .find(|s| s.name == "NumberOrString") + .expect("Should find NumberOrString enum"); + assert_eq!(enum_symbol.kind, SymbolKind::ENUM); + let variants = enum_symbol + .children + .as_ref() + .expect("Enum should have variants"); + assert_eq!(variants.len(), 2); + assert!(variants.iter().any(|v| v.name == "Number")); + assert!(variants.iter().any(|v| v.name == "String")); + + // Check for struct with its fields + let struct_symbol = symbols + .iter() + .find(|s| s.name == "Data") + .expect("Should find Data struct"); + assert_eq!(struct_symbol.kind, SymbolKind::STRUCT); + let fields = struct_symbol + .children + .as_ref() + .expect("Struct should have fields"); + assert_eq!(fields.len(), 2); + assert!(fields + .iter() + .any(|f| f.name == "value" && f.detail.as_deref() == Some("NumberOrString"))); + assert!(fields + .iter() + .any(|f| f.name == "address" && f.detail.as_deref() == Some("u64"))); + + // Check for impl with nested function and variable + let impl_symbol = symbols + .iter() + .find(|s| s.name == "impl FooABI for Contract") + .expect("Should find impl block"); + let impl_fns = impl_symbol + .children + .as_ref() + .expect("Impl should have functions"); + let main_fn = impl_fns + .iter() + .find(|f| f.name == "main") + .expect("Should find main function"); + let vars = main_fn + .children + .as_ref() + .expect("Function should have variables"); + assert!(vars + .iter() + .any(|v| v.name == "_data" && v.detail.as_deref() == Some("Data"))); + } else { + panic!("Expected nested document symbols response"); } } diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs index 39e9174a96f..f37a8cc1da9 100644 --- a/sway-lsp/tests/lib.rs +++ b/sway-lsp/tests/lib.rs @@ -1952,7 +1952,7 @@ lsp_capability_test!( ); lsp_capability_test!( document_symbol, - lsp::document_symbol_request, + lsp::document_symbols_request, doc_comments_dir().join("src/main.sw") ); lsp_capability_test!( diff --git a/sway-parse/src/attribute.rs b/sway-parse/src/attribute.rs index 84a174f3bf2..4b3d545eb8c 100644 --- a/sway-parse/src/attribute.rs +++ b/sway-parse/src/attribute.rs @@ -188,67 +188,121 @@ mod tests { fn main() { () } - "#,), @r###" + "#,), @r#" Annotated( attribute_list: [ AttributeDecl( hash_kind: Outer(HashToken( - span: (82, 108), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 82, + end: 108, + source_id: None, + ), )), attribute: SquareBrackets( inner: Punctuated( value_separator_pairs: [], final_value_opt: Some(Attribute( - name: Ident( - to_string: "doc-comment", - span: (82, 108), + name: BaseIdent( + name_override_opt: Some("doc-comment"), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 82, + end: 108, + source_id: None, + ), + is_raw_ident: false, ), args: Some(Parens( inner: Punctuated( value_separator_pairs: [], final_value_opt: Some(AttributeArg( - name: Ident( - to_string: " This is a doc comment.", - span: (85, 108), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 85, + end: 108, + source_id: None, + ), + is_raw_ident: false, ), value: None, )), ), - span: (85, 108), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 85, + end: 108, + source_id: None, + ), )), )), ), - span: (82, 108), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 82, + end: 108, + source_id: None, + ), ), ), AttributeDecl( hash_kind: Outer(HashToken( - span: (121, 122), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 121, + end: 122, + source_id: None, + ), )), attribute: SquareBrackets( inner: Punctuated( value_separator_pairs: [], final_value_opt: Some(Attribute( - name: Ident( - to_string: "storage", - span: (123, 130), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 123, + end: 130, + source_id: None, + ), + is_raw_ident: false, ), args: Some(Parens( inner: Punctuated( value_separator_pairs: [], final_value_opt: Some(AttributeArg( - name: Ident( - to_string: "read", - span: (131, 135), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 131, + end: 135, + source_id: None, + ), + is_raw_ident: false, ), value: None, )), ), - span: (130, 136), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 130, + end: 136, + source_id: None, + ), )), )), ), - span: (122, 137), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 122, + end: 137, + source_id: None, + ), ), ), ], @@ -256,11 +310,22 @@ mod tests { fn_signature: FnSignature( visibility: None, fn_token: FnToken( - span: (150, 152), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 150, + end: 152, + source_id: None, + ), ), - name: Ident( - to_string: "main", - span: (153, 157), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 153, + end: 157, + source_id: None, + ), + is_raw_ident: false, ), generics: None, arguments: Parens( @@ -268,7 +333,12 @@ mod tests { value_separator_pairs: [], final_value_opt: None, )), - span: (157, 159), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 157, + end: 159, + source_id: None, + ), ), return_type_opt: None, where_clause_opt: None, @@ -278,63 +348,122 @@ mod tests { statements: [], final_expr_opt: Some(Tuple(Parens( inner: Nil, - span: (178, 180), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 178, + end: 180, + source_id: None, + ), ))), - span: (161, 193), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 161, + end: 193, + source_id: None, + ), + ), + span: Span( + src: "\n // I will be ignored.\n //! I will be ignored.\n /// This is a doc comment.\n #[storage(read)]\n fn main() {\n ()\n }\n ", + start: 160, + end: 194, + source_id: None, ), - span: (160, 194), ), ), ) - "###); + "#); } #[test] fn parse_attribute() { assert_ron_snapshot!(parse::(r#" name(arg1, arg2 = "value", arg3) - "#,), @r###" + "#,), @r#" Attribute( - name: Ident( - to_string: "name", - span: (13, 17), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 13, + end: 17, + source_id: None, + ), + is_raw_ident: false, ), args: Some(Parens( inner: Punctuated( value_separator_pairs: [ (AttributeArg( - name: Ident( - to_string: "arg1", - span: (18, 22), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 18, + end: 22, + source_id: None, + ), + is_raw_ident: false, ), value: None, ), CommaToken( - span: (22, 23), + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 22, + end: 23, + source_id: None, + ), )), (AttributeArg( - name: Ident( - to_string: "arg2", - span: (24, 28), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 24, + end: 28, + source_id: None, + ), + is_raw_ident: false, ), value: Some(String(LitString( - span: (31, 38), + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 31, + end: 38, + source_id: None, + ), parsed: "value", ))), ), CommaToken( - span: (38, 39), + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 38, + end: 39, + source_id: None, + ), )), ], final_value_opt: Some(AttributeArg( - name: Ident( - to_string: "arg3", - span: (40, 44), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 40, + end: 44, + source_id: None, + ), + is_raw_ident: false, ), value: None, )), ), - span: (17, 45), + span: Span( + src: "\n name(arg1, arg2 = \"value\", arg3)\n ", + start: 17, + end: 45, + source_id: None, + ), )), ) - "###); + "#); } } diff --git a/sway-parse/src/module.rs b/sway-parse/src/module.rs index 7936012e96a..05d97d82b42 100644 --- a/sway-parse/src/module.rs +++ b/sway-parse/src/module.rs @@ -87,17 +87,27 @@ mod tests { fn main() { () } - "#,), @r###" + "#,), @r#" Annotated( attribute_list: [], value: Module( kind: Script( script_token: ScriptToken( - span: (13, 19), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 13, + end: 19, + source_id: None, + ), ), ), semicolon_token: SemicolonToken( - span: (19, 20), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 19, + end: 20, + source_id: None, + ), ), items: [ Annotated( @@ -106,11 +116,22 @@ mod tests { fn_signature: FnSignature( visibility: None, fn_token: FnToken( - span: (42, 44), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 42, + end: 44, + source_id: None, + ), ), - name: Ident( - to_string: "main", - span: (45, 49), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 45, + end: 49, + source_id: None, + ), + is_raw_ident: false, ), generics: None, arguments: Parens( @@ -118,7 +139,12 @@ mod tests { value_separator_pairs: [], final_value_opt: None, )), - span: (49, 51), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 49, + end: 51, + source_id: None, + ), ), return_type_opt: None, where_clause_opt: None, @@ -128,17 +154,32 @@ mod tests { statements: [], final_expr_opt: Some(Tuple(Parens( inner: Nil, - span: (70, 72), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 70, + end: 72, + source_id: None, + ), ))), - span: (53, 85), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 53, + end: 85, + source_id: None, + ), + ), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 52, + end: 86, + source_id: None, ), - span: (52, 86), ), )), ), ], ), ) - "###); + "#); } } diff --git a/sway-parse/src/path.rs b/sway-parse/src/path.rs index a9bb8c3826a..4a2a57ac706 100644 --- a/sway-parse/src/path.rs +++ b/sway-parse/src/path.rs @@ -121,10 +121,7 @@ impl Parse for PathTypeSegment { impl Parse for QualifiedPathRoot { fn parse(parser: &mut Parser) -> ParseResult { let ty = parser.parse()?; - let as_trait = match parser.take() { - Some(as_token) => Some((as_token, parser.parse()?)), - None => None, - }; + let as_trait = (parser.parse()?, parser.parse()?); Ok(QualifiedPathRoot { ty, as_trait }) } } @@ -139,37 +136,65 @@ mod tests { fn parse_nested_path() { assert_ron_snapshot!(parse::(r#" std::vec::Vec - "#,), @r###" + "#,), @r#" PathExpr( root_opt: None, prefix: PathExprSegment( - name: Ident( - to_string: "std", - span: (13, 16), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n std::vec::Vec\n ", + start: 13, + end: 16, + source_id: None, + ), + is_raw_ident: false, ), generics_opt: None, ), suffix: [ (DoubleColonToken( - span: (16, 18), + span: Span( + src: "\n std::vec::Vec\n ", + start: 16, + end: 18, + source_id: None, + ), ), PathExprSegment( - name: Ident( - to_string: "vec", - span: (18, 21), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n std::vec::Vec\n ", + start: 18, + end: 21, + source_id: None, + ), + is_raw_ident: false, ), generics_opt: None, )), (DoubleColonToken( - span: (21, 23), + span: Span( + src: "\n std::vec::Vec\n ", + start: 21, + end: 23, + source_id: None, + ), ), PathExprSegment( - name: Ident( - to_string: "Vec", - span: (23, 26), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n std::vec::Vec\n ", + start: 23, + end: 26, + source_id: None, + ), + is_raw_ident: false, ), generics_opt: None, )), ], ) - "###); + "#); } } diff --git a/sway-parse/tests/noop_script.rs b/sway-parse/tests/noop_script.rs index fd3fe83cf0e..709da28de33 100644 --- a/sway-parse/tests/noop_script.rs +++ b/sway-parse/tests/noop_script.rs @@ -12,17 +12,27 @@ fn noop_script_file() { fn main() { () } - "#,), @r###" + "#,), @r#" Some(Annotated( attribute_list: [], value: Module( kind: Script( script_token: ScriptToken( - span: (7, 13), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 7, + end: 13, + source_id: None, + ), ), ), semicolon_token: SemicolonToken( - span: (13, 14), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 13, + end: 14, + source_id: None, + ), ), items: [ Annotated( @@ -31,11 +41,22 @@ fn noop_script_file() { fn_signature: FnSignature( visibility: None, fn_token: FnToken( - span: (28, 30), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 28, + end: 30, + source_id: None, + ), ), - name: Ident( - to_string: "main", - span: (31, 35), + name: BaseIdent( + name_override_opt: None, + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 31, + end: 35, + source_id: None, + ), + is_raw_ident: false, ), generics: None, arguments: Parens( @@ -43,7 +64,12 @@ fn noop_script_file() { value_separator_pairs: [], final_value_opt: None, )), - span: (35, 37), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 35, + end: 37, + source_id: None, + ), ), return_type_opt: None, where_clause_opt: None, @@ -53,16 +79,31 @@ fn noop_script_file() { statements: [], final_expr_opt: Some(Tuple(Parens( inner: Nil, - span: (48, 50), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 48, + end: 50, + source_id: None, + ), ))), - span: (39, 57), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 39, + end: 57, + source_id: None, + ), + ), + span: Span( + src: "\n script;\n \n fn main() {\n ()\n }\n ", + start: 38, + end: 58, + source_id: None, ), - span: (38, 58), ), )), ), ], ), )) - "###); + "#); } diff --git a/sway-types/Cargo.toml b/sway-types/Cargo.toml index aad3c5e52ac..89931ff9ab7 100644 --- a/sway-types/Cargo.toml +++ b/sway-types/Cargo.toml @@ -19,7 +19,7 @@ num-bigint.workspace = true num-traits.workspace = true parking_lot.workspace = true rustc-hash.workspace = true -serde = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive", "std", "rc"] } sway-utils.workspace = true thiserror.workspace = true diff --git a/sway-types/src/ast.rs b/sway-types/src/ast.rs index b137ad9bd09..4db1c3b6c7c 100644 --- a/sway-types/src/ast.rs +++ b/sway-types/src/ast.rs @@ -6,14 +6,14 @@ pub enum Delimiter { } impl Delimiter { - pub fn as_open_char(self) -> char { + pub const fn as_open_char(self) -> char { match self { Delimiter::Parenthesis => '(', Delimiter::Brace => '{', Delimiter::Bracket => '[', } } - pub fn as_close_char(self) -> char { + pub const fn as_close_char(self) -> char { match self { Delimiter::Parenthesis => ')', Delimiter::Brace => '}', diff --git a/sway-types/src/ident.rs b/sway-types/src/ident.rs index f4aa577bc5d..ff1c168e9e6 100644 --- a/sway-types/src/ident.rs +++ b/sway-types/src/ident.rs @@ -1,7 +1,5 @@ -use serde::Serialize; - use crate::{span::Span, Spanned}; - +use serde::{Deserialize, Serialize}; use std::{ cmp::{Ord, Ordering}, fmt, @@ -13,7 +11,7 @@ pub trait Named { fn name(&self) -> &BaseIdent; } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct BaseIdent { name_override_opt: Option>, span: Span, @@ -93,18 +91,6 @@ impl BaseIdent { /// often be different. pub type Ident = BaseIdent; -impl Serialize for Ident { - // Serialize an `Ident` struct with two fields: `to_string` and `span`. - fn serialize(&self, serializer: S) -> Result { - use serde::ser::SerializeStruct; - - let mut state = serializer.serialize_struct("Ident", 2)?; - state.serialize_field("to_string", &self.to_string())?; - state.serialize_field("span", &self.span)?; - state.end() - } -} - impl Hash for Ident { fn hash(&self, state: &mut H) { self.as_str().hash(state); diff --git a/sway-types/src/span.rs b/sway-types/src/span.rs index c757fb523da..5ea94b3c2a0 100644 --- a/sway-types/src/span.rs +++ b/sway-types/src/span.rs @@ -1,12 +1,11 @@ -use std::fmt::Display; - -use serde::{Deserialize, Serialize}; - use crate::SourceId; - -use { - lazy_static::lazy_static, - std::{cmp, fmt, hash::Hash, sync::Arc}, +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::{ + cmp, + fmt::{self, Display}, + hash::Hash, + sync::Arc, }; lazy_static! { @@ -43,7 +42,7 @@ impl<'a> Position<'a> { } /// Represents a span of the source code in a specific file. -#[derive(Clone, Ord, PartialOrd)] +#[derive(Clone, Ord, PartialOrd, Serialize, Deserialize)] pub struct Span { // The original source code. src: Arc, @@ -71,18 +70,6 @@ impl PartialEq for Span { impl Eq for Span {} -impl Serialize for Span { - // Serialize a tuple two fields: `start` and `end`. - fn serialize(&self, serializer: S) -> Result { - use serde::ser::SerializeTuple; - - let mut state = serializer.serialize_tuple(2)?; - state.serialize_element(&self.start)?; - state.serialize_element(&self.end)?; - state.end() - } -} - impl From for std::ops::Range { fn from(value: Span) -> Self { Self { @@ -216,6 +203,10 @@ impl Span { pub fn is_dummy(&self) -> bool { self.eq(&DUMMY_SPAN) } + + pub fn is_empty(&self) -> bool { + self.start == self.end + } } impl fmt::Debug for Span { diff --git a/sway-types/src/u256.rs b/sway-types/src/u256.rs index e116308f169..2f42b43b0f3 100644 --- a/sway-types/src/u256.rs +++ b/sway-types/src/u256.rs @@ -1,10 +1,10 @@ -use std::ops::{Not, Shl, Shr}; - use num_bigint::{BigUint, ParseBigIntError, TryFromBigIntError}; use num_traits::Zero; +use serde::{Deserialize, Serialize}; +use std::ops::{Not, Shl, Shr}; use thiserror::Error; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize)] pub struct U256(BigUint); impl U256 { diff --git a/swayfmt/Cargo.toml b/swayfmt/Cargo.toml index 85749c30ab2..71eb6d60824 100644 --- a/swayfmt/Cargo.toml +++ b/swayfmt/Cargo.toml @@ -16,7 +16,6 @@ ropey.workspace = true serde = { workspace = true, features = ["derive"] } serde_ignored.workspace = true sway-ast.workspace = true -sway-core.workspace = true sway-error.workspace = true sway-parse.workspace = true sway-types.workspace = true diff --git a/swayfmt/src/comments.rs b/swayfmt/src/comments.rs index e891ca15666..dfd5590d7f7 100644 --- a/swayfmt/src/comments.rs +++ b/swayfmt/src/comments.rs @@ -76,15 +76,18 @@ fn write_trailing_comment( } /// Given a range, writes comments contained within the range. This function -/// removes comments that are written here from the CommentMap for later use. +/// removes comments that are written here from the [CommentMap] for later use. /// -/// Most comment formatting should be done using `rewrite_with_comments` in +/// Most comment formatting should be done using [rewrite_with_comments] in /// the context of the AST, but in some cases (eg. at the end of module) we require this function. /// /// Returns: /// `Ok(true)` on successful execution with comments written, /// `Ok(false)` on successful execution and if there are no comments within the given range, /// `Err` if a FormatterError was encountered. +/// +/// The `range` can be an empty [Range], or have its start being greater then its end. +/// This is to support formatting arbitrary lexed trees, that are not necessarily backed by source code. pub fn write_comments( formatted_code: &mut FormattedCode, range: Range, @@ -127,7 +130,7 @@ pub fn write_comments( // We do a trim and truncate here to ensure that only a single whitespace separates // the inlined comment from the previous token. formatted_code.truncate(formatted_code.trim_end().len()); - write!(formatted_code, " {} ", comment.span().as_str(),)?; + write!(formatted_code, " {} ", comment.span().as_str())?; } CommentKind::Multilined => { write!( @@ -157,6 +160,9 @@ pub fn write_comments( /// This takes a given AST node's unformatted span, its leaf spans and its formatted code (a string) and /// parses the equivalent formatted version to get its leaf spans. We traverse the spaces between both /// formatted and unformatted leaf spans to find possible comments and inserts them between. +/// +/// The `unformatted_span` can be an empty [Span]. This is to support formatting arbitrary lexed trees, +/// that are not necessarily backed by source code. pub fn rewrite_with_comments( formatter: &mut Formatter, unformatted_span: Span, diff --git a/swayfmt/src/formatter/mod.rs b/swayfmt/src/formatter/mod.rs index 96ead29f09d..0802d0c7727 100644 --- a/swayfmt/src/formatter/mod.rs +++ b/swayfmt/src/formatter/mod.rs @@ -8,7 +8,8 @@ pub use crate::{ error::{ConfigError, FormatterError}, }; use std::{borrow::Cow, fmt::Write, path::Path, sync::Arc}; -use sway_core::BuildConfig; +use sway_ast::attribute::Annotated; +use sway_ast::Module; use sway_types::{SourceEngine, Spanned}; pub(crate) mod shape; @@ -80,10 +81,14 @@ impl Formatter { Ok(self) } - pub fn format( + pub fn format(&mut self, src: Arc) -> Result { + let annotated_module = parse_file(src)?; + self.format_module(&annotated_module) + } + + pub fn format_module( &mut self, - src: Arc, - build_config: Option<&BuildConfig>, + annotated_module: &Annotated, ) -> Result { // apply the width heuristics settings from the `Config` self.shape.apply_width_heuristics( @@ -92,9 +97,11 @@ impl Formatter { .heuristics_pref .to_width_heuristics(self.config.whitespace.max_width), ); - let src = src.trim(); - let path = build_config.map(|build_config| build_config.canonical_root_module()); + // Get the original trimmed source code. + let module_kind_span = annotated_module.value.kind.span(); + let src = module_kind_span.src().trim(); + // Formatted code will be pushed here with raw newline style. // Which means newlines are not converted into system-specific versions until `apply_newline_style()`. // Use the length of src as a hint of the memory size needed for `raw_formatted_code`, @@ -103,7 +110,6 @@ impl Formatter { self.with_comments_context(src)?; - let annotated_module = parse_file(&self.source_engine, Arc::from(src), path.clone())?; annotated_module.format(&mut raw_formatted_code, self)?; let mut formatted_code = String::from(&raw_formatted_code); @@ -117,14 +123,13 @@ impl Formatter { // Add newline sequences handle_newlines( - &self.source_engine, Arc::from(src), &annotated_module.value, Arc::from(formatted_code.clone()), - path, &mut formatted_code, self, )?; + // Replace newlines with specified `NewlineStyle` apply_newline_style( self.config.whitespace.newline_style, @@ -137,6 +142,7 @@ impl Formatter { Ok(formatted_code) } + pub(crate) fn with_shape(&mut self, new_shape: Shape, f: F) -> O where F: FnOnce(&mut Self) -> O, diff --git a/swayfmt/src/formatter/shape.rs b/swayfmt/src/formatter/shape.rs index 0630c73e851..9c9def85548 100644 --- a/swayfmt/src/formatter/shape.rs +++ b/swayfmt/src/formatter/shape.rs @@ -163,6 +163,12 @@ pub(crate) enum LineStyle { Multiline, } +impl LineStyle { + pub fn is_multiline(&self) -> bool { + matches!(self, Self::Multiline) + } +} + impl Default for LineStyle { fn default() -> Self { Self::Normal diff --git a/swayfmt/src/items/item_abi/mod.rs b/swayfmt/src/items/item_abi/mod.rs index ddf1fabe22f..e47fa3b269d 100644 --- a/swayfmt/src/items/item_abi/mod.rs +++ b/swayfmt/src/items/item_abi/mod.rs @@ -8,7 +8,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{keywords::Token, ItemAbi}; +use sway_ast::{ + keywords::{AbiToken, ColonToken, Keyword, Token}, + ItemAbi, +}; use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] @@ -22,12 +25,12 @@ impl Format for ItemAbi { ) -> Result<(), FormatterError> { let start_len = formatted_code.len(); // `abi name` - write!(formatted_code, "{} ", self.abi_token.span().as_str())?; + write!(formatted_code, "{} ", AbiToken::AS_STR)?; self.name.format(formatted_code, formatter)?; // ` : super_trait + super_trait` - if let Some((colon_token, traits)) = &self.super_traits { - write!(formatted_code, " {} ", colon_token.ident().as_str())?; + if let Some((_colon_token, traits)) = &self.super_traits { + write!(formatted_code, " {} ", ColonToken::AS_STR)?; traits.format(formatted_code, formatter)?; } diff --git a/swayfmt/src/items/item_configurable/mod.rs b/swayfmt/src/items/item_configurable/mod.rs index 550b8916d8d..c870b537a1d 100644 --- a/swayfmt/src/items/item_configurable/mod.rs +++ b/swayfmt/src/items/item_configurable/mod.rs @@ -10,7 +10,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{keywords::Token, ConfigurableField, ItemConfigurable}; +use sway_ast::{ + keywords::{ColonToken, ConfigurableToken, EqToken, Keyword, Token}, + CommaToken, ConfigurableField, ItemConfigurable, +}; use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] @@ -28,11 +31,7 @@ impl Format for ItemConfigurable { .with_code_line_from(LineStyle::Multiline, ExprKind::default()), |formatter| -> Result<(), FormatterError> { // Add configurable token - write!( - formatted_code, - "{}", - self.configurable_token.span().as_str() - )?; + write!(formatted_code, "{}", ConfigurableToken::AS_STR)?; let fields = self.fields.get(); // Handle opening brace @@ -44,19 +43,18 @@ impl Format for ItemConfigurable { match formatter.config.structures.field_alignment { FieldAlignment::AlignFields(configurable_field_align_threshold) => { writeln!(formatted_code)?; - let value_pairs = &fields + let configurable_fields = &fields .value_separator_pairs .iter() - // TODO: Handle annotations instead of stripping them - .map(|(configurable_field, comma_token)| { - (&configurable_field.value, comma_token) - }) + // TODO: Handle annotations instead of stripping them. + // See: https://github.com/FuelLabs/sway/issues/6802 + .map(|(configurable_field, _comma_token)| &configurable_field.value) .collect::>(); // In first iteration we are going to be collecting the lengths of the // struct fields. - let field_length: Vec = value_pairs + let field_length: Vec = configurable_fields .iter() - .map(|(configurable_field, _)| configurable_field.name.as_str().len()) + .map(|configurable_field| configurable_field.name.as_str().len()) .collect(); // Find the maximum length in the `field_length` vector that is still @@ -72,11 +70,10 @@ impl Format for ItemConfigurable { } }); - let value_pairs_iter = value_pairs.iter().enumerate(); - for (field_index, (configurable_field, comma_token)) in - value_pairs_iter.clone() + for (field_index, configurable_field) in + configurable_fields.iter().enumerate() { - write!(formatted_code, "{}", &formatter.indent_to_str()?)?; + write!(formatted_code, "{}", formatter.indent_to_str()?)?; // Add name configurable_field.name.format(formatted_code, formatter)?; @@ -94,24 +91,17 @@ impl Format for ItemConfigurable { } } // Add `:`, `ty` & `CommaToken` - write!( - formatted_code, - " {} ", - configurable_field.colon_token.ident().as_str(), - )?; + write!(formatted_code, " {} ", ColonToken::AS_STR)?; configurable_field.ty.format(formatted_code, formatter)?; - write!( - formatted_code, - " {} ", - configurable_field.eq_token.ident().as_str() - )?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; configurable_field .initializer .format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", comma_token.ident().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &fields.final_value_opt { final_value.format(formatted_code, formatter)?; + writeln!(formatted_code)?; } } FieldAlignment::Off => fields.format(formatted_code, formatter)?, diff --git a/swayfmt/src/items/item_const.rs b/swayfmt/src/items/item_const.rs index e853f2a97dc..0d78ef24eeb 100644 --- a/swayfmt/src/items/item_const.rs +++ b/swayfmt/src/items/item_const.rs @@ -4,7 +4,10 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{keywords::Token, ItemConst}; +use sway_ast::{ + keywords::{ColonToken, ConstToken, EqToken, Keyword, SemicolonToken, Token}, + ItemConst, PubToken, +}; use sway_types::Spanned; impl Format for ItemConst { @@ -17,26 +20,26 @@ impl Format for ItemConst { let start_len = formatted_code.len(); // Check if visibility token exists if so add it. - if let Some(visibility_token) = &self.visibility { - write!(formatted_code, "{} ", visibility_token.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // Add the const token - write!(formatted_code, "{} ", self.const_token.span().as_str())?; + write!(formatted_code, "{} ", ConstToken::AS_STR)?; // Add name of the const self.name.format(formatted_code, formatter)?; // Check if ty exists - if let Some((colon_token, ty)) = &self.ty_opt { + if let Some((_colon_token, ty)) = &self.ty_opt { // Add colon - write!(formatted_code, "{} ", colon_token.ident().as_str())?; + write!(formatted_code, "{} ", ColonToken::AS_STR)?; ty.format(formatted_code, formatter)?; } // Check if ` = ` exists - if let Some(eq_token) = &self.eq_token_opt { - write!(formatted_code, " {} ", eq_token.ident().as_str())?; + if self.eq_token_opt.is_some() { + write!(formatted_code, " {} ", EqToken::AS_STR)?; } // Check if expression exists @@ -44,7 +47,7 @@ impl Format for ItemConst { expr.format(formatted_code, formatter)?; } - write!(formatted_code, "{}", self.semicolon_token.ident().as_str())?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; rewrite_with_comments::( formatter, diff --git a/swayfmt/src/items/item_enum/mod.rs b/swayfmt/src/items/item_enum/mod.rs index 561c2b436f6..11fa3d6c78d 100644 --- a/swayfmt/src/items/item_enum/mod.rs +++ b/swayfmt/src/items/item_enum/mod.rs @@ -11,11 +11,11 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::ItemEnum; -use sway_types::{ - ast::{Delimiter, PunctKind}, - Spanned, +use sway_ast::{ + keywords::{ColonToken, EnumToken, Keyword, Token}, + CommaToken, ItemEnum, PubToken, }; +use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] mod tests; @@ -34,11 +34,11 @@ impl Format for ItemEnum { // Required for comment formatting let start_len = formatted_code.len(); // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &self.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // Add enum token and name - write!(formatted_code, "{} ", self.enum_token.span().as_str())?; + write!(formatted_code, "{} ", EnumToken::AS_STR)?; self.name.format(formatted_code, formatter)?; // Format `GenericParams`, if any if let Some(generics) = &self.generics { @@ -55,21 +55,22 @@ impl Format for ItemEnum { match formatter.config.structures.field_alignment { FieldAlignment::AlignFields(enum_variant_align_threshold) => { writeln!(formatted_code)?; - let value_pairs = &fields + let type_fields = &fields .value_separator_pairs .iter() - // TODO: Handle annotations instead of stripping them - .map(|pair| (&pair.0.value, &pair.1)) + // TODO: Handle annotations instead of stripping them. + // See: https://github.com/FuelLabs/sway/issues/6802 + .map(|(type_field, _comma_token)| &type_field.value) .collect::>(); // In first iteration we are going to be collecting the lengths of the enum variants. - let variant_length: Vec = value_pairs + let variants_lengths: Vec = type_fields .iter() - .map(|(type_field, _)| type_field.name.as_str().len()) + .map(|type_field| type_field.name.as_str().len()) .collect(); - // Find the maximum length in the variant_length vector that is still smaller than enum_field_align_threshold. + // Find the maximum length that is still smaller than the align threshold. let mut max_valid_variant_length = 0; - variant_length.iter().for_each(|length| { + variants_lengths.iter().for_each(|length| { if *length > max_valid_variant_length && *length < enum_variant_align_threshold { @@ -77,13 +78,12 @@ impl Format for ItemEnum { } }); - let value_pairs_iter = value_pairs.iter().enumerate(); - for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { - write!(formatted_code, "{}", &formatter.indent_to_str()?)?; + for (var_index, type_field) in type_fields.iter().enumerate() { + write!(formatted_code, "{}", formatter.indent_to_str()?)?; // Add name type_field.name.format(formatted_code, formatter)?; - let current_variant_length = variant_length[var_index]; + let current_variant_length = variants_lengths[var_index]; if current_variant_length < max_valid_variant_length { // We need to add alignment between : and ty // max_valid_variant_length: the length of the variant that we are taking as a reference to align @@ -96,23 +96,13 @@ impl Format for ItemEnum { } } // Add `:`, ty & `CommaToken` - write!( - formatted_code, - " {} ", - type_field.colon_token.span().as_str(), - )?; + write!(formatted_code, " {} ", ColonToken::AS_STR)?; type_field.ty.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", comma_token.span().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &fields.final_value_opt { - // TODO: Handle annotation - let final_value = &final_value.value; - writeln!( - formatted_code, - "{}{}", - final_value.span().as_str(), - PunctKind::Comma.as_char(), - )?; + final_value.format(formatted_code, formatter)?; + writeln!(formatted_code)?; } } FieldAlignment::Off => fields.format(formatted_code, formatter)?, @@ -164,6 +154,7 @@ impl CurlyBrace for ItemEnum { Ok(()) } } + impl LeafSpans for ItemEnum { fn leaf_spans(&self) -> Vec { let mut collected_spans = Vec::new(); diff --git a/swayfmt/src/items/item_fn/mod.rs b/swayfmt/src/items/item_fn/mod.rs index 596f37efad2..235fc5a2179 100644 --- a/swayfmt/src/items/item_fn/mod.rs +++ b/swayfmt/src/items/item_fn/mod.rs @@ -11,8 +11,10 @@ use crate::{ }; use std::fmt::Write; use sway_ast::{ - keywords::{MutToken, RefToken, SelfToken, Token}, - FnArg, FnArgs, FnSignature, ItemFn, + keywords::{ + ColonToken, FnToken, Keyword, MutToken, RefToken, RightArrowToken, SelfToken, Token, + }, + CommaToken, FnArg, FnArgs, FnSignature, ItemFn, PubToken, }; use sway_types::{ast::Delimiter, Spanned}; @@ -152,11 +154,11 @@ fn format_fn_sig( formatter: &mut Formatter, ) -> Result<(), FormatterError> { // `pub ` - if let Some(visibility_token) = &fn_sig.visibility { - write!(formatted_code, "{} ", visibility_token.span().as_str())?; + if fn_sig.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // `fn ` + name - write!(formatted_code, "{} ", fn_sig.fn_token.span().as_str())?; + write!(formatted_code, "{} ", FnToken::AS_STR)?; fn_sig.name.format(formatted_code, formatter)?; // `` if let Some(generics) = &fn_sig.generics { @@ -169,12 +171,8 @@ fn format_fn_sig( // `)` FnSignature::close_parenthesis(formatted_code, formatter)?; // `return_type_opt` - if let Some((right_arrow, ty)) = &fn_sig.return_type_opt { - write!( - formatted_code, - " {} ", - right_arrow.ident().as_str() // `->` - )?; + if let Some((_right_arrow, ty)) = &fn_sig.return_type_opt { + write!(formatted_code, " {} ", RightArrowToken::AS_STR)?; ty.format(formatted_code, formatter)?; // `Ty` } // `WhereClause` @@ -196,7 +194,7 @@ fn format_fn_args( FnArgs::Static(args) => match formatter.shape.code_line.line_style { LineStyle::Multiline => { formatter.shape.code_line.update_expr_new_line(true); - if !args.value_separator_pairs.is_empty() || args.final_value_opt.is_some() { + if !args.is_empty() { formatter.indent(); args.format(formatted_code, formatter)?; formatter.unindent(); @@ -218,9 +216,9 @@ fn format_fn_args( write!(formatted_code, "\n{}", formatter.indent_to_str()?)?; format_self(self_token, ref_self, mutable_self, formatted_code)?; // `args_opt` - if let Some((comma, args)) = args_opt { + if let Some((_comma, args)) = args_opt { // `, ` - write!(formatted_code, "{}", comma.ident().as_str())?; + write!(formatted_code, "{}", CommaToken::AS_STR)?; // `Punctuated` args.format(formatted_code, formatter)?; } @@ -228,9 +226,9 @@ fn format_fn_args( _ => { format_self(self_token, ref_self, mutable_self, formatted_code)?; // `args_opt` - if let Some((comma, args)) = args_opt { + if let Some((_comma, args)) = args_opt { // `, ` - write!(formatted_code, "{} ", comma.ident().as_str())?; + write!(formatted_code, "{} ", CommaToken::AS_STR)?; // `Punctuated` args.format(formatted_code, formatter)?; } @@ -243,21 +241,21 @@ fn format_fn_args( } fn format_self( - self_token: &SelfToken, + _self_token: &SelfToken, ref_self: &Option, mutable_self: &Option, formatted_code: &mut FormattedCode, ) -> Result<(), FormatterError> { // `ref ` - if let Some(ref_token) = ref_self { - write!(formatted_code, "{} ", ref_token.span().as_str())?; + if ref_self.is_some() { + write!(formatted_code, "{} ", RefToken::AS_STR)?; } // `mut ` - if let Some(mut_token) = mutable_self { - write!(formatted_code, "{} ", mut_token.span().as_str())?; + if mutable_self.is_some() { + write!(formatted_code, "{} ", MutToken::AS_STR)?; } // `self` - write!(formatted_code, "{}", self_token.span().as_str())?; + write!(formatted_code, "{}", SelfToken::AS_STR)?; Ok(()) } @@ -289,7 +287,7 @@ impl Format for FnArg { ) -> Result<(), FormatterError> { self.pattern.format(formatted_code, formatter)?; // `: ` - write!(formatted_code, "{} ", self.colon_token.span().as_str())?; + write!(formatted_code, "{} ", ColonToken::AS_STR)?; write_comments( formatted_code, diff --git a/swayfmt/src/items/item_impl/mod.rs b/swayfmt/src/items/item_impl/mod.rs index 164f157ba2c..fcadf3b040c 100644 --- a/swayfmt/src/items/item_impl/mod.rs +++ b/swayfmt/src/items/item_impl/mod.rs @@ -8,7 +8,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{ItemImpl, ItemImplItem}; +use sway_ast::{ + keywords::{ForToken, ImplToken, Keyword}, + ItemImpl, ItemImplItem, +}; use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] @@ -23,14 +26,14 @@ impl Format for ItemImpl { // Required for comment formatting let start_len = formatted_code.len(); - write!(formatted_code, "{}", self.impl_token.span().as_str())?; + write!(formatted_code, "{}", ImplToken::AS_STR)?; if let Some(generic_params) = &self.generic_params_opt { generic_params.format(formatted_code, formatter)?; } write!(formatted_code, " ")?; - if let Some((path_type, for_token)) = &self.trait_opt { + if let Some((path_type, _for_token)) = &self.trait_opt { path_type.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", for_token.span().as_str())?; + write!(formatted_code, " {} ", ForToken::AS_STR)?; } self.ty.format(formatted_code, formatter)?; if let Some(where_clause) = &self.where_clause_opt { diff --git a/swayfmt/src/items/item_storage/mod.rs b/swayfmt/src/items/item_storage/mod.rs index c6fa0fdf189..acbcf5937c3 100644 --- a/swayfmt/src/items/item_storage/mod.rs +++ b/swayfmt/src/items/item_storage/mod.rs @@ -11,7 +11,10 @@ use crate::{ }, }; use std::{collections::HashMap, fmt::Write}; -use sway_ast::{keywords::Token, ItemStorage, StorageEntry, StorageField}; +use sway_ast::{ + keywords::{ColonToken, EqToken, Keyword, StorageToken, Token}, + CommaToken, ItemStorage, StorageEntry, StorageField, +}; use sway_types::{ast::Delimiter, IdentUnique, Spanned}; #[cfg(test)] @@ -32,7 +35,7 @@ impl Format for ItemStorage { let start_len = formatted_code.len(); // Add storage token - write!(formatted_code, "{}", self.storage_token.span().as_str())?; + write!(formatted_code, "{}", StorageToken::AS_STR)?; let entries = self.entries.get(); // Handle opening brace @@ -47,7 +50,8 @@ impl Format for ItemStorage { let value_pairs = &entries .value_separator_pairs .iter() - // TODO: Handle annotations instead of stripping them + // TODO: Handle annotations instead of stripping them. + // See: https://github.com/FuelLabs/sway/issues/6802 .map(|(storage_field, comma_token)| (&storage_field.value, comma_token)) .collect::>(); // In first iteration we are going to be collecting the lengths of the @@ -114,7 +118,7 @@ impl Format for ItemStorage { ItemStorage::open_curly_brace(formatted_code, formatter)?; writeln!(formatted_code)?; - for (e, comma_token) in + for (e, _comma_token) in namespace.clone().into_inner().value_separator_pairs { format_entry( @@ -124,7 +128,7 @@ impl Format for ItemStorage { field_lengths, max_valid_field_length, )?; - writeln!(formatted_code, "{}", comma_token.ident().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &namespace.clone().into_inner().final_value_opt @@ -159,17 +163,9 @@ impl Format for ItemStorage { } } // Add `:`, `ty` & `CommaToken` - write!( - formatted_code, - " {} ", - storage_field.colon_token.ident().as_str(), - )?; + write!(formatted_code, " {} ", ColonToken::AS_STR)?; storage_field.ty.format(formatted_code, formatter)?; - write!( - formatted_code, - " {} ", - storage_field.eq_token.ident().as_str() - )?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; storage_field .initializer .format(formatted_code, formatter)?; @@ -177,7 +173,7 @@ impl Format for ItemStorage { Ok(()) } - for (storage_entry, comma_token) in value_pairs.iter().clone() { + for (storage_entry, _comma_token) in value_pairs.iter().clone() { format_entry( formatted_code, formatter, @@ -185,7 +181,7 @@ impl Format for ItemStorage { &field_lengths, max_valid_field_length, )?; - writeln!(formatted_code, "{}", comma_token.ident().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &entries.final_value_opt { format_entry( diff --git a/swayfmt/src/items/item_struct/mod.rs b/swayfmt/src/items/item_struct/mod.rs index 8ebefab2898..40946639ebb 100644 --- a/swayfmt/src/items/item_struct/mod.rs +++ b/swayfmt/src/items/item_struct/mod.rs @@ -11,7 +11,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::ItemStruct; +use sway_ast::{ + keywords::{ColonToken, Keyword, StructToken, Token}, + CommaToken, ItemStruct, PubToken, +}; use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] @@ -31,11 +34,11 @@ impl Format for ItemStruct { // Required for comment formatting let start_len = formatted_code.len(); // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &self.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // Add struct token and name - write!(formatted_code, "{} ", self.struct_token.span().as_str())?; + write!(formatted_code, "{} ", StructToken::AS_STR)?; self.name.format(formatted_code, formatter)?; // Format `GenericParams`, if any if let Some(generics) = &self.generics { @@ -52,7 +55,7 @@ impl Format for ItemStruct { // Handle opening brace Self::open_curly_brace(formatted_code, formatter)?; - if fields.final_value_opt.is_none() && fields.value_separator_pairs.is_empty() { + if fields.is_empty() { write_comments(formatted_code, self.span().into(), formatter)?; } @@ -60,74 +63,67 @@ impl Format for ItemStruct { // Determine alignment tactic match formatter.config.structures.field_alignment { - FieldAlignment::AlignFields(enum_variant_align_threshold) => { + FieldAlignment::AlignFields(struct_field_align_threshold) => { writeln!(formatted_code)?; - let value_pairs = &fields + let type_fields = &fields .value_separator_pairs .iter() - // TODO: Handle annotations instead of stripping them - .map(|(type_field, comma_token)| (&type_field.value, comma_token)) + // TODO: Handle annotations instead of stripping them. + // See: https://github.com/FuelLabs/sway/issues/6802 + .map(|(type_field, _comma_token)| &type_field.value) .collect::>(); - // In first iteration we are going to be collecting the lengths of the struct variants. + // In first iteration we are going to be collecting the lengths of the struct fields. // We need to include the `pub` keyword in the length, if the field is public, // together with one space character between the `pub` and the name. - let variant_length: Vec = value_pairs + let fields_lengths: Vec = type_fields .iter() - .map(|(type_field, _)| { + .map(|type_field| { type_field .visibility .as_ref() - // We don't want to hard code the token here to `pub` or just hardcode 4. - // This is in case we introduce e.g. `pub(crate)` one day. - .map_or(0, |token| token.span().as_str().len() + 1) + .map_or(0, |_pub_token| PubToken::AS_STR.len() + 1) + type_field.name.as_str().len() }) .collect(); - // Find the maximum length in the variant_length vector that is still smaller than struct_field_align_threshold. - let mut max_valid_variant_length = 0; - variant_length.iter().for_each(|length| { - if *length > max_valid_variant_length - && *length < enum_variant_align_threshold + // Find the maximum length that is still smaller than the align threshold. + let mut max_valid_field_length = 0; + fields_lengths.iter().for_each(|length| { + if *length > max_valid_field_length + && *length < struct_field_align_threshold { - max_valid_variant_length = *length; + max_valid_field_length = *length; } }); - let value_pairs_iter = value_pairs.iter().enumerate(); - for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { + for (var_index, type_field) in type_fields.iter().enumerate() { write!(formatted_code, "{}", formatter.indent_to_str()?)?; // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &type_field.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; + if type_field.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // Add name type_field.name.format(formatted_code, formatter)?; - let current_variant_length = variant_length[var_index]; - if current_variant_length < max_valid_variant_length { + let current_field_length = fields_lengths[var_index]; + if current_field_length < max_valid_field_length { // We need to add alignment between : and ty // max_valid_variant_length: the length of the variant that we are taking as a reference to align // current_variant_length: the length of the current variant that we are trying to format let mut required_alignment = - max_valid_variant_length - current_variant_length; + max_valid_field_length - current_field_length; while required_alignment != 0 { write!(formatted_code, " ")?; required_alignment -= 1; } } // Add `:`, ty & `CommaToken` - write!( - formatted_code, - " {} ", - type_field.colon_token.span().as_str(), - )?; + write!(formatted_code, " {} ", ColonToken::AS_STR)?; type_field.ty.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", comma_token.span().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &fields.final_value_opt { - // TODO: Handle annotation - let final_value = &final_value.value; - write!(formatted_code, "{}", final_value.span().as_str())?; + final_value.format(formatted_code, formatter)?; + writeln!(formatted_code)?; } } FieldAlignment::Off => { diff --git a/swayfmt/src/items/item_trait/mod.rs b/swayfmt/src/items/item_trait/mod.rs index 10f94eee8b4..32caff67128 100644 --- a/swayfmt/src/items/item_trait/mod.rs +++ b/swayfmt/src/items/item_trait/mod.rs @@ -8,7 +8,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{keywords::Token, ItemTrait, ItemTraitItem, Traits}; +use sway_ast::{ + keywords::{AddToken, ColonToken, Keyword, Token, TraitToken}, + ItemTrait, ItemTraitItem, PubToken, Traits, +}; use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] @@ -23,23 +26,23 @@ impl Format for ItemTrait { // Required for comment formatting let start_len = formatted_code.len(); // `pub ` - if let Some(pub_token) = &self.visibility { - write!(formatted_code, "{} ", pub_token.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // `trait name` write!( formatted_code, "{} {}", - self.trait_token.span().as_str(), - self.name.span().as_str() + TraitToken::AS_STR, + self.name.as_str(), )?; // `` if let Some(generics) = &self.generics { generics.format(formatted_code, formatter)?; } // `: super_trait + super_trait` - if let Some((colon_token, traits)) = &self.super_traits { - write!(formatted_code, "{} ", colon_token.ident().as_str())?; + if let Some((_colon_token, traits)) = &self.super_traits { + write!(formatted_code, "{} ", ColonToken::AS_STR)?; traits.format(formatted_code, formatter)?; } // `where` @@ -145,8 +148,8 @@ impl Format for Traits { // additional `PathType`s // // ` + PathType` - for (add_token, path_type) in self.suffixes.iter() { - write!(formatted_code, " {} ", add_token.span().as_str())?; + for (_add_token, path_type) in self.suffixes.iter() { + write!(formatted_code, " {} ", AddToken::AS_STR)?; path_type.format(formatted_code, formatter)?; } diff --git a/swayfmt/src/items/item_trait_type.rs b/swayfmt/src/items/item_trait_type.rs index f3af2251b2f..eb363c1ef7b 100644 --- a/swayfmt/src/items/item_trait_type.rs +++ b/swayfmt/src/items/item_trait_type.rs @@ -4,7 +4,10 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{keywords::Token, TraitType}; +use sway_ast::{ + keywords::{EqToken, Keyword, SemicolonToken, Token, TypeToken}, + TraitType, +}; use sway_types::Spanned; impl Format for TraitType { @@ -16,15 +19,15 @@ impl Format for TraitType { // Required for comment formatting let start_len = formatted_code.len(); - // Add the const token - write!(formatted_code, "{} ", self.type_token.span().as_str())?; + // Add the type token + write!(formatted_code, "{} ", TypeToken::AS_STR)?; - // Add name of the const + // Add name of the type self.name.format(formatted_code, formatter)?; // Check if ` = ` exists - if let Some(eq_token) = &self.eq_token_opt { - write!(formatted_code, " {} ", eq_token.ident().as_str())?; + if self.eq_token_opt.is_some() { + write!(formatted_code, " {} ", EqToken::AS_STR)?; } // Check if ty exists @@ -32,7 +35,7 @@ impl Format for TraitType { ty.format(formatted_code, formatter)?; } - write!(formatted_code, "{}", self.semicolon_token.ident().as_str())?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; rewrite_with_comments::( formatter, diff --git a/swayfmt/src/items/item_type_alias.rs b/swayfmt/src/items/item_type_alias.rs index 4870baeb031..86221a70f5b 100644 --- a/swayfmt/src/items/item_type_alias.rs +++ b/swayfmt/src/items/item_type_alias.rs @@ -4,7 +4,10 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{keywords::Token, ItemTypeAlias}; +use sway_ast::{ + keywords::{EqToken, Keyword, SemicolonToken, Token, TypeToken}, + ItemTypeAlias, PubToken, +}; use sway_types::Spanned; impl Format for ItemTypeAlias { @@ -17,24 +20,24 @@ impl Format for ItemTypeAlias { let start_len = formatted_code.len(); // Check if visibility token exists if so add it. - if let Some(visibility_token) = &self.visibility { - write!(formatted_code, "{} ", visibility_token.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } // Add the `type` token - write!(formatted_code, "{} ", self.type_token.span().as_str())?; + write!(formatted_code, "{} ", TypeToken::AS_STR)?; // Add name of the type alias self.name.format(formatted_code, formatter)?; // Add the `=` token - write!(formatted_code, " {} ", self.eq_token.ident().as_str())?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; // Format and add `ty` self.ty.format(formatted_code, formatter)?; // Add the `;` token - write!(formatted_code, "{}", self.semicolon_token.ident().as_str())?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; rewrite_with_comments::( formatter, diff --git a/swayfmt/src/items/item_use/mod.rs b/swayfmt/src/items/item_use/mod.rs index ce304183495..838248fb1c7 100644 --- a/swayfmt/src/items/item_use/mod.rs +++ b/swayfmt/src/items/item_use/mod.rs @@ -9,11 +9,11 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{CommaToken, ItemUse, UseTree}; -use sway_types::{ - ast::{Delimiter, PunctKind}, - Spanned, +use sway_ast::{ + keywords::{AsToken, Keyword, SemicolonToken, StarToken, Token, UseToken}, + CommaToken, DoubleColonToken, ItemUse, PubToken, UseTree, }; +use sway_types::{ast::Delimiter, Spanned}; #[cfg(test)] mod tests; @@ -64,23 +64,40 @@ impl Format for UseTree { match self { Self::Group { imports } => { // check for only one import - if imports.inner.value_separator_pairs.is_empty() { + if imports.inner.value_separator_pairs.is_empty() + && !formatter.shape.code_line.line_style.is_multiline() + { + // we can have: path::{single_import} if let Some(single_import) = &imports.inner.final_value_opt { single_import.format(formatted_code, formatter)?; } + } else if imports.inner.value_separator_pairs.len() == 1 + && imports.inner.has_trailing_punctuation() + && !formatter.shape.code_line.line_style.is_multiline() + { + // but we can also have: path::{single_import,} + // note that in the case of multiline we want to keep the trailing comma + let single_import = &imports + .inner + .value_separator_pairs + .first() + .expect("the `if` condition ensures the existence of the first element") + .0; + single_import.format(formatted_code, formatter)?; } else { Self::open_curly_brace(formatted_code, formatter)?; // sort group imports let imports = imports.get(); let value_pairs = &imports.value_separator_pairs; - let mut commas: Vec<&CommaToken> = Vec::new(); + // track how many commas we have, to simplify checking for trailing element or trailing comma + let mut commas: Vec<()> = Vec::new(); let mut ord_vec: Vec = value_pairs .iter() .map( - |(use_tree, comma_token)| -> Result { + |(use_tree, _comma_token)| -> Result { let mut buf = FormattedCode::new(); use_tree.format(&mut buf, formatter)?; - commas.push(comma_token); + commas.push(()); // we have a comma token Ok(buf) }, ) @@ -102,15 +119,16 @@ impl Format for UseTree { a.to_lowercase().cmp(&b.to_lowercase()) } }); - for (use_tree, comma) in ord_vec.iter_mut().zip(commas.iter()) { - write!(use_tree, "{}", comma.span().as_str())?; + // zip will take only the parts of `ord_vec` before the last comma + for (use_tree, _) in ord_vec.iter_mut().zip(commas.iter()) { + write!(use_tree, "{}", CommaToken::AS_STR)?; } match formatter.shape.code_line.line_style { LineStyle::Multiline => { if imports.final_value_opt.is_some() { if let Some(last) = ord_vec.iter_mut().last() { - write!(last, "{}", PunctKind::Comma.as_char())?; + write!(last, "{}", CommaToken::AS_STR)?; } } @@ -122,39 +140,48 @@ impl Format for UseTree { )?; } _ => { - write!(formatted_code, "{}", ord_vec.join(" "))?; + if imports.has_trailing_punctuation() { + // remove the trailing punctuation + write!( + formatted_code, + "{}", + ord_vec.join(" ").trim_end_matches(',') + )?; + } else { + write!(formatted_code, "{}", ord_vec.join(" "))?; + } } } Self::close_curly_brace(formatted_code, formatter)?; } } - Self::Name { name } => write!(formatted_code, "{}", name.span().as_str())?, + Self::Name { name } => write!(formatted_code, "{}", name.as_str())?, Self::Rename { name, - as_token, + as_token: _, alias, } => { write!( formatted_code, "{} {} {}", - name.span().as_str(), - as_token.span().as_str(), - alias.span().as_str() + name.as_str(), + AsToken::AS_STR, + alias.as_str(), )?; } - Self::Glob { star_token } => { - write!(formatted_code, "{}", star_token.span().as_str())?; + Self::Glob { star_token: _ } => { + write!(formatted_code, "{}", StarToken::AS_STR)?; } Self::Path { prefix, - double_colon_token, + double_colon_token: _, suffix, } => { write!( formatted_code, "{}{}", - prefix.span().as_str(), - double_colon_token.span().as_str() + prefix.as_str(), + DoubleColonToken::AS_STR, )?; suffix.format(formatted_code, formatter)?; } @@ -208,19 +235,15 @@ fn format_use_stmt( formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - if let Some(pub_token) = &item_use.visibility { - write!(formatted_code, "{} ", pub_token.span().as_str())?; + if item_use.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } - write!(formatted_code, "{} ", item_use.use_token.span().as_str())?; - if let Some(root_import) = &item_use.root_import { - write!(formatted_code, "{}", root_import.span().as_str())?; + write!(formatted_code, "{} ", UseToken::AS_STR)?; + if item_use.root_import.is_some() { + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; } item_use.tree.format(formatted_code, formatter)?; - write!( - formatted_code, - "{}", - item_use.semicolon_token.span().as_str() - )?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; Ok(()) } diff --git a/swayfmt/src/items/item_use/tests.rs b/swayfmt/src/items/item_use/tests.rs index 0b0a207bc37..64794080d65 100644 --- a/swayfmt/src/items/item_use/tests.rs +++ b/swayfmt/src/items/item_use/tests.rs @@ -10,6 +10,15 @@ fmt_test_item!(multiline "use foo::{ };", out_of_order "use foo::{yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, quux, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx};" ); + +fmt_test_item!(multiline_with_trailing_comma "use foo::{ + quux, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +};", + out_of_order "use foo::{yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, quux, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,};" +); + fmt_test_item!(multiline_nested "use foo::{ Quux::{ a, @@ -22,10 +31,42 @@ fmt_test_item!(multiline_nested "use foo::{ out_of_order "use foo::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, Quux::{b, a, C}, yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx};" ); +fmt_test_item!(multiline_nested_with_trailing_comma "use foo::{ + Quux::{ + a, + b, + C, + }, + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +};", + out_of_order "use foo::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, Quux::{b, a, C,}, yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,};" +); + fmt_test_item!(single_line_sort "use foo::{bar, baz, Quux::{a, b, C}};", out_of_order "use foo::{baz, Quux::{b, a, C}, bar};" ); +fmt_test_item!(single_line_sort_with_trailing_comma "use foo::{bar, baz, Quux::{a, b, C}};", + out_of_order "use foo::{baz, Quux::{b, a, C,}, bar,};" +); + fmt_test_item!(single_import_without_braces "use std::tx::tx_id;", braced_single_import "use std::tx::{tx_id};" ); + +fmt_test_item!(single_import_without_braces_with_trailing_comma "use std::tx::tx_id;", + braced_single_import "use std::tx::{tx_id,};" +); + +fmt_test_item!(single_import_multiline_with_braces "use std::tx::{ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +};", + braced_single_import "use std::tx::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx};" +); + +fmt_test_item!(single_import_multiline_with_braces_with_trailing_comma "use std::tx::{ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +};", + braced_single_import "use std::tx::{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,};" +); diff --git a/swayfmt/src/module/mod.rs b/swayfmt/src/module/mod.rs index db49d4f806c..de95433281c 100644 --- a/swayfmt/src/module/mod.rs +++ b/swayfmt/src/module/mod.rs @@ -4,7 +4,12 @@ use crate::{ utils::map::byte_span::{self, ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{Item, ItemKind, Module, ModuleKind}; +use sway_ast::{ + keywords::{ + ContractToken, Keyword, LibraryToken, PredicateToken, ScriptToken, SemicolonToken, Token, + }, + Item, ItemKind, Module, ModuleKind, +}; use sway_types::Spanned; pub(crate) mod item; @@ -18,7 +23,7 @@ impl Format for Module { ) -> Result<(), FormatterError> { write_comments(formatted_code, 0..self.span().start(), formatter)?; self.kind.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", self.semicolon_token.span().as_str())?; + writeln!(formatted_code, "{}", SemicolonToken::AS_STR)?; // Format comments between module kind declaration and rest of items if !self.items.is_empty() { @@ -69,17 +74,17 @@ impl Format for ModuleKind { _formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self { - ModuleKind::Script { script_token } => { - write!(formatted_code, "{}", script_token.span().as_str())? + ModuleKind::Script { script_token: _ } => { + write!(formatted_code, "{}", ScriptToken::AS_STR)? } - ModuleKind::Contract { contract_token } => { - write!(formatted_code, "{}", contract_token.span().as_str())? + ModuleKind::Contract { contract_token: _ } => { + write!(formatted_code, "{}", ContractToken::AS_STR)? } - ModuleKind::Predicate { predicate_token } => { - write!(formatted_code, "{}", predicate_token.span().as_str())? + ModuleKind::Predicate { predicate_token: _ } => { + write!(formatted_code, "{}", PredicateToken::AS_STR)? } - ModuleKind::Library { library_token } => { - write!(formatted_code, "{}", library_token.span().as_str())?; + ModuleKind::Library { library_token: _ } => { + write!(formatted_code, "{}", LibraryToken::AS_STR)?; } }; diff --git a/swayfmt/src/module/submodule.rs b/swayfmt/src/module/submodule.rs index 0c2a6c4d31b..6edb91ab574 100644 --- a/swayfmt/src/module/submodule.rs +++ b/swayfmt/src/module/submodule.rs @@ -3,7 +3,11 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::submodule::Submodule; +use sway_ast::{ + keywords::{Keyword, ModToken, SemicolonToken, Token}, + submodule::Submodule, + PubToken, +}; use sway_types::Spanned; impl Format for Submodule { @@ -12,12 +16,12 @@ impl Format for Submodule { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - if let Some(pub_token) = &self.visibility { - write!(formatted_code, "{} ", pub_token.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } - write!(formatted_code, "{} ", self.mod_token.span().as_str())?; + write!(formatted_code, "{} ", ModToken::AS_STR)?; self.name.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", self.semicolon_token.span().as_str())?; + writeln!(formatted_code, "{}", SemicolonToken::AS_STR)?; Ok(()) } } diff --git a/swayfmt/src/parse.rs b/swayfmt/src/parse.rs index 9d4ccadbbfb..17befe22cc3 100644 --- a/swayfmt/src/parse.rs +++ b/swayfmt/src/parse.rs @@ -1,9 +1,7 @@ use crate::{error::ParseFileError, Formatter, FormatterError}; -use std::path::PathBuf; use std::sync::Arc; use sway_ast::{attribute::Annotated, token::CommentedTokenStream, Module}; use sway_error::handler::{ErrorEmitted, Handler}; -use sway_types::SourceEngine; fn with_handler( run: impl FnOnce(&Handler) -> Result, @@ -16,13 +14,8 @@ fn with_handler( .ok_or(ParseFileError(errors)) } -pub fn parse_file( - source_engine: &SourceEngine, - src: Arc, - path: Option>, -) -> Result, ParseFileError> { - let source_id = path.map(|p| source_engine.get_source_id(p.as_ref())); - with_handler(|h| sway_parse::parse_file(h, src, source_id)) +pub fn parse_file(src: Arc) -> Result, ParseFileError> { + with_handler(|h| sway_parse::parse_file(h, src, None)) } pub fn lex(input: &Arc) -> Result { diff --git a/swayfmt/src/utils/language/attribute.rs b/swayfmt/src/utils/language/attribute.rs index daa6b7e5611..b9662bf8d56 100644 --- a/swayfmt/src/utils/language/attribute.rs +++ b/swayfmt/src/utils/language/attribute.rs @@ -8,12 +8,12 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::attribute::{Annotated, Attribute, AttributeArg, AttributeDecl, AttributeHashKind}; -use sway_types::{ - ast::{Delimiter, PunctKind}, - constants::DOC_COMMENT_ATTRIBUTE_NAME, - Spanned, +use sway_ast::{ + attribute::{Annotated, Attribute, AttributeArg, AttributeDecl, AttributeHashKind}, + keywords::{HashToken, Token}, + CommaToken, }; +use sway_types::{ast::Delimiter, constants::DOC_COMMENT_ATTRIBUTE_NAME, Spanned}; impl Format for Annotated { fn format( @@ -56,11 +56,12 @@ impl Format for AttributeArg { fn format( &self, formatted_code: &mut FormattedCode, - _formatter: &mut Formatter, + formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{}", self.name.span().as_str())?; + write!(formatted_code, "{}", self.name.as_str())?; if let Some(value) = &self.value { - write!(formatted_code, " = {}", value.span().as_str())?; + write!(formatted_code, " = ")?; + value.format(formatted_code, formatter)?; } Ok(()) @@ -114,11 +115,12 @@ impl Format for AttributeDecl { // invariant: attribute lists cannot be empty // `#` - let hash_type_token_span = match &self.hash_kind { - AttributeHashKind::Inner(_) => Err(FormatterError::HashBangAttributeError), - AttributeHashKind::Outer(hash_token) => Ok(hash_token.span()), + match &self.hash_kind { + AttributeHashKind::Inner(_) => return Err(FormatterError::HashBangAttributeError), + AttributeHashKind::Outer(_hash_token) => { + write!(formatted_code, "{}", HashToken::AS_STR)?; + } }; - write!(formatted_code, "{}", hash_type_token_span?.as_str())?; // `[` Self::open_square_bracket(formatted_code, formatter)?; let mut regular_attrs = regular_attrs.iter().peekable(); @@ -127,7 +129,7 @@ impl Format for AttributeDecl { formatter.shape.with_default_code_line(), |formatter| -> Result<(), FormatterError> { // name e.g. `storage` - write!(formatted_code, "{}", attr.name.span().as_str())?; + write!(formatted_code, "{}", attr.name.as_str())?; if let Some(args) = &attr.args { // `(` Self::open_parenthesis(formatted_code, formatter)?; @@ -141,7 +143,7 @@ impl Format for AttributeDecl { )?; // do not put a separator after the last attribute if regular_attrs.peek().is_some() { - write!(formatted_code, "{} ", PunctKind::Comma.as_char())?; + write!(formatted_code, "{} ", CommaToken::AS_STR)?; } } // `]\n` diff --git a/swayfmt/src/utils/language/expr/abi_cast.rs b/swayfmt/src/utils/language/expr/abi_cast.rs index 14493f3f4d3..5c6545891ab 100644 --- a/swayfmt/src/utils/language/expr/abi_cast.rs +++ b/swayfmt/src/utils/language/expr/abi_cast.rs @@ -6,7 +6,7 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::AbiCastArgs; +use sway_ast::{keywords::Token, AbiCastArgs, CommaToken}; use sway_types::{ast::Delimiter, Spanned}; impl Format for AbiCastArgs { @@ -17,7 +17,7 @@ impl Format for AbiCastArgs { ) -> Result<(), FormatterError> { Self::open_parenthesis(formatted_code, formatter)?; self.name.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", self.comma_token.span().as_str())?; + write!(formatted_code, "{} ", CommaToken::AS_STR)?; self.address.format(formatted_code, formatter)?; Self::close_parenthesis(formatted_code, formatter)?; diff --git a/swayfmt/src/utils/language/expr/asm_block.rs b/swayfmt/src/utils/language/expr/asm_block.rs index 19f92f5ea68..458bfec669f 100644 --- a/swayfmt/src/utils/language/expr/asm_block.rs +++ b/swayfmt/src/utils/language/expr/asm_block.rs @@ -9,6 +9,7 @@ use crate::{ use std::fmt::Write; use sway_ast::{ expr::asm::{AsmBlock, AsmBlockContents, AsmFinalExpr, AsmRegisterDeclaration}, + keywords::{AsmToken, ColonToken, Keyword, SemicolonToken, Token}, Instruction, }; use sway_types::{ast::Delimiter, Spanned}; @@ -56,7 +57,7 @@ fn format_asm_block( formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{}", asm_block.asm_token.span().as_str())?; + write!(formatted_code, "{}", AsmToken::AS_STR)?; formatter.with_shape( formatter.shape.with_default_code_line(), @@ -174,8 +175,8 @@ impl Format for AsmRegisterDeclaration { formatter: &mut Formatter, ) -> Result<(), FormatterError> { self.register.format(formatted_code, formatter)?; - if let Some((colon_token, expr)) = &self.value_opt { - write!(formatted_code, "{} ", colon_token.span().as_str())?; + if let Some((_colon_token, expr)) = &self.value_opt { + write!(formatted_code, "{} ", ColonToken::AS_STR)?; expr.format(formatted_code, formatter)?; } @@ -189,7 +190,7 @@ impl Format for Instruction { formatted_code: &mut FormattedCode, _formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{}", &self.op_code_ident().as_str())?; + write!(formatted_code, "{}", self.op_code_as_str())?; for arg in self.register_arg_idents() { write!(formatted_code, " {}", arg.as_str())? } @@ -206,10 +207,10 @@ impl Format for AsmBlockContents { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - for (instruction, semicolon_token) in self.instructions.iter() { + for (instruction, _semicolon_token) in self.instructions.iter() { write!(formatted_code, "{}", formatter.indent_to_str()?)?; instruction.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", semicolon_token.span().as_str())? + writeln!(formatted_code, "{}", SemicolonToken::AS_STR)? } if let Some(final_expr) = &self.final_expr_opt { if formatter.shape.code_line.line_style == LineStyle::Multiline { @@ -232,8 +233,8 @@ impl Format for AsmFinalExpr { formatter: &mut Formatter, ) -> Result<(), FormatterError> { self.register.format(formatted_code, formatter)?; - if let Some((colon_token, ty)) = &self.ty_opt { - write!(formatted_code, "{} ", colon_token.span().as_str())?; + if let Some((_colon_token, ty)) = &self.ty_opt { + write!(formatted_code, "{} ", ColonToken::AS_STR)?; ty.format(formatted_code, formatter)?; } diff --git a/swayfmt/src/utils/language/expr/assignable.rs b/swayfmt/src/utils/language/expr/assignable.rs index b5e278464dd..1df99ad70cf 100644 --- a/swayfmt/src/utils/language/expr/assignable.rs +++ b/swayfmt/src/utils/language/expr/assignable.rs @@ -6,7 +6,12 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{assignable::ElementAccess, expr::ReassignmentOp, Assignable, Expr}; +use sway_ast::{ + assignable::ElementAccess, + expr::ReassignmentOp, + keywords::{DotToken, StarToken, Token}, + Assignable, Expr, +}; use sway_types::Spanned; impl Format for ElementAccess { @@ -27,26 +32,21 @@ impl Format for ElementAccess { } ElementAccess::FieldProjection { target, - dot_token, + dot_token: _, name, } => { target.format(formatted_code, formatter)?; - write!(formatted_code, "{}", dot_token.span().as_str())?; + write!(formatted_code, "{}", DotToken::AS_STR)?; name.format(formatted_code, formatter)?; } ElementAccess::TupleFieldProjection { target, - dot_token, - field: _, - field_span, + dot_token: _, + field, + field_span: _, } => { target.format(formatted_code, formatter)?; - write!( - formatted_code, - "{}{}", - dot_token.span().as_str(), - field_span.as_str() - )?; + write!(formatted_code, "{}{}", DotToken::AS_STR, field)?; } } Ok(()) @@ -63,8 +63,11 @@ impl Format for Assignable { Assignable::ElementAccess(element_access) => { element_access.format(formatted_code, formatter)? } - Assignable::Deref { star_token, expr } => { - write!(formatted_code, "{}", star_token.span().as_str())?; + Assignable::Deref { + star_token: _, + expr, + } => { + write!(formatted_code, "{}", StarToken::AS_STR)?; expr.format(formatted_code, formatter)?; } } @@ -78,7 +81,7 @@ impl Format for ReassignmentOp { formatted_code: &mut FormattedCode, _formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, " {} ", self.span.as_str())?; + write!(formatted_code, " {} ", self.variant.as_str())?; Ok(()) } } diff --git a/swayfmt/src/utils/language/expr/collections.rs b/swayfmt/src/utils/language/expr/collections.rs index f175329bbd9..af34214f21c 100644 --- a/swayfmt/src/utils/language/expr/collections.rs +++ b/swayfmt/src/utils/language/expr/collections.rs @@ -6,7 +6,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::{ExprArrayDescriptor, ExprTupleDescriptor}; +use sway_ast::{ + keywords::{SemicolonToken, Token}, + CommaToken, ExprArrayDescriptor, ExprTupleDescriptor, +}; use sway_types::{ast::Delimiter, Spanned}; impl Format for ExprTupleDescriptor { @@ -20,18 +23,18 @@ impl Format for ExprTupleDescriptor { Self::Nil => {} Self::Cons { head, - comma_token, + comma_token: _, tail, } => match formatter.shape.code_line.line_style { LineStyle::Multiline => { write!(formatted_code, "{}", formatter.indent_to_str()?)?; head.format(formatted_code, formatter)?; - write!(formatted_code, "{}", comma_token.span().as_str())?; + write!(formatted_code, "{}", CommaToken::AS_STR)?; tail.format(formatted_code, formatter)?; } _ => { head.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", comma_token.span().as_str())?; + write!(formatted_code, "{} ", CommaToken::AS_STR)?; tail.format(formatted_code, formatter)?; } }, @@ -91,11 +94,11 @@ impl Format for ExprArrayDescriptor { } Self::Repeat { value, - semicolon_token, + semicolon_token: _, length, } => { value.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", semicolon_token.span().as_str())?; + write!(formatted_code, "{} ", SemicolonToken::AS_STR)?; length.format(formatted_code, formatter)?; } } diff --git a/swayfmt/src/utils/language/expr/conditional.rs b/swayfmt/src/utils/language/expr/conditional.rs index f21e71c133d..b97d5896c47 100644 --- a/swayfmt/src/utils/language/expr/conditional.rs +++ b/swayfmt/src/utils/language/expr/conditional.rs @@ -10,7 +10,11 @@ use crate::{ }, }; use std::{fmt::Write, ops::Range}; -use sway_ast::{expr::LoopControlFlow, IfCondition, IfExpr, MatchBranch, MatchBranchKind}; +use sway_ast::{ + expr::LoopControlFlow, + keywords::{ElseToken, EqToken, FatRightArrowToken, IfToken, Keyword, LetToken, Token}, + CommaToken, IfCondition, IfExpr, MatchBranch, MatchBranchKind, +}; use sway_types::{ast::Delimiter, Spanned}; impl Format for IfExpr { @@ -125,7 +129,7 @@ fn format_if_condition( formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{} ", if_expr.if_token.span().as_str())?; + write!(formatted_code, "{} ", IfToken::AS_STR)?; if formatter.shape.code_line.line_style == LineStyle::Multiline { formatter.indent(); if_expr.condition.format(formatted_code, formatter)?; @@ -181,11 +185,11 @@ fn format_else_opt( )?; if comments_written { - write!(else_if_str, "{}", formatter.indent_to_str()?,)?; + write!(else_if_str, "{}", formatter.indent_to_str()?)?; } else { write!(else_if_str, " ")?; } - write!(else_if_str, "{}", else_token.span().as_str())?; + write!(else_if_str, "{}", ElseToken::AS_STR)?; match &control_flow { LoopControlFlow::Continue(if_expr) => { write!(else_if_str, " ")?; @@ -263,14 +267,14 @@ impl Format for IfCondition { expr.format(formatted_code, formatter)?; } Self::Let { - let_token, + let_token: _, lhs, - eq_token, + eq_token: _, rhs, } => { - write!(formatted_code, "{} ", let_token.span().as_str())?; + write!(formatted_code, "{} ", LetToken::AS_STR)?; lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", eq_token.span().as_str())?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } } @@ -286,11 +290,7 @@ impl Format for MatchBranch { formatter: &mut Formatter, ) -> Result<(), FormatterError> { self.pattern.format(formatted_code, formatter)?; - write!( - formatted_code, - " {} ", - self.fat_right_arrow_token.span().as_str() - )?; + write!(formatted_code, " {} ", FatRightArrowToken::AS_STR)?; self.kind.format(formatted_code, formatter)?; Ok(()) @@ -348,13 +348,16 @@ impl Format for MatchBranchKind { write!(formatted_code, "{}", formatter.indent_to_str()?)?; } Self::close_curly_brace(formatted_code, formatter)?; - if let Some(comma_token) = comma_token_opt { - write!(formatted_code, "{}", comma_token.span().as_str())?; + if comma_token_opt.is_some() { + write!(formatted_code, "{}", CommaToken::AS_STR)?; } } - Self::Expr { expr, comma_token } => { + Self::Expr { + expr, + comma_token: _, + } => { expr.format(formatted_code, formatter)?; - write!(formatted_code, "{}", comma_token.span().as_str())?; + write!(formatted_code, "{}", CommaToken::AS_STR)?; } } diff --git a/swayfmt/src/utils/language/expr/mod.rs b/swayfmt/src/utils/language/expr/mod.rs index a65eb3bc527..95a53c01c30 100644 --- a/swayfmt/src/utils/language/expr/mod.rs +++ b/swayfmt/src/utils/language/expr/mod.rs @@ -10,11 +10,8 @@ use crate::{ }; use std::fmt::Write; use sway_ast::{ - brackets::Parens, - keywords::{CommaToken, DotToken}, - punctuated::Punctuated, - Braces, CodeBlockContents, Expr, ExprStructField, IfExpr, MatchBranch, PathExpr, - PathExprSegment, + brackets::Parens, keywords::*, punctuated::Punctuated, Braces, CodeBlockContents, Expr, + ExprStructField, IfExpr, MatchBranch, PathExpr, PathExprSegment, }; use sway_types::{ast::Delimiter, Spanned}; @@ -79,7 +76,7 @@ fn two_parts_expr( )?; } _ => { - write!(formatted_code, " {} ", operator,)?; + write!(formatted_code, " {} ", operator)?; } } write!(formatted_code, "{}", rhs_code)?; @@ -99,8 +96,8 @@ impl Format for Expr { } Self::Path(path) => path.format(formatted_code, formatter)?, Self::Literal(lit) => lit.format(formatted_code, formatter)?, - Self::AbiCast { abi_token, args } => { - write!(formatted_code, "{}", abi_token.span().as_str())?; + Self::AbiCast { abi_token: _, args } => { + write!(formatted_code, "{}", AbiToken::AS_STR)?; args.get().format(formatted_code, formatter)?; } Self::Struct { path, fields } => { @@ -205,7 +202,7 @@ impl Format for Expr { .get_line_style(None, Some(body_width), &formatter.config); if formatter.shape.code_line.line_style == LineStyle::Multiline { - // Expr needs to be splitten into multiple lines + // Expr needs to be split into multiple lines array_descriptor.format(formatted_code, formatter)?; } else { // Expr fits in a single line @@ -218,10 +215,10 @@ impl Format for Expr { } Self::Asm(asm_block) => asm_block.format(formatted_code, formatter)?, Self::Return { - return_token, + return_token: _, expr_opt, } => { - write!(formatted_code, "{}", return_token.span().as_str())?; + write!(formatted_code, "{}", ReturnToken::AS_STR)?; if let Some(expr) = &expr_opt { write!(formatted_code, " ")?; expr.format(formatted_code, formatter)?; @@ -229,11 +226,11 @@ impl Format for Expr { } Self::If(if_expr) => if_expr.format(formatted_code, formatter)?, Self::Match { - match_token, + match_token: _, value, branches, } => { - write!(formatted_code, "{} ", match_token.span().as_str())?; + write!(formatted_code, "{} ", MatchToken::AS_STR)?; value.format(formatted_code, formatter)?; write!(formatted_code, " ")?; if !branches.get().is_empty() { @@ -250,7 +247,7 @@ impl Format for Expr { } } Self::While { - while_token, + while_token: _, condition, block, } => { @@ -259,7 +256,7 @@ impl Format for Expr { .shape .with_code_line_from(LineStyle::Normal, ExprKind::Function), |formatter| -> Result<(), FormatterError> { - write!(formatted_code, "{} ", while_token.span().as_str())?; + write!(formatted_code, "{} ", WhileToken::AS_STR)?; condition.format(formatted_code, formatter)?; IfExpr::open_curly_brace(formatted_code, formatter)?; block.get().format(formatted_code, formatter)?; @@ -269,8 +266,8 @@ impl Format for Expr { )?; } Self::For { - for_token, - in_token, + for_token: _, + in_token: _, value_pattern, iterator, block, @@ -280,9 +277,9 @@ impl Format for Expr { .shape .with_code_line_from(LineStyle::Normal, ExprKind::Function), |formatter| -> Result<(), FormatterError> { - write!(formatted_code, "{} ", for_token.span().as_str())?; + write!(formatted_code, "{} ", ForToken::AS_STR)?; value_pattern.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", in_token.span().as_str())?; + write!(formatted_code, " {} ", InToken::AS_STR)?; iterator.format(formatted_code, formatter)?; IfExpr::open_curly_brace(formatted_code, formatter)?; block.get().format(formatted_code, formatter)?; @@ -305,7 +302,7 @@ impl Format for Expr { Self::open_parenthesis(formatted_code, formatter)?; let (_, args_str) = write_function_call_arguments(args.get(), formatter)?; - write!(formatted_code, "{}", args_str,)?; + write!(formatted_code, "{}", args_str)?; Self::close_parenthesis(formatted_code, formatter)?; Ok(()) @@ -381,7 +378,7 @@ impl Format for Expr { } Self::FieldProjection { target, - dot_token, + dot_token: _, name, } => { let prev_length = formatted_code.len(); @@ -398,124 +395,124 @@ impl Format for Expr { formatted_code, "\n{}{}", formatter.indent_to_str()?, - dot_token.span().as_str() + DotToken::AS_STR, )?; name.format(formatted_code, formatter)?; formatter.unindent(); } else { - write!(formatted_code, "{}", dot_token.span().as_str())?; + write!(formatted_code, "{}", DotToken::AS_STR)?; name.format(formatted_code, formatter)?; } } Self::TupleFieldProjection { target, - dot_token, - field: _, - field_span, + dot_token: _, + field, + field_span: _, } => { target.format(formatted_code, formatter)?; - write!( - formatted_code, - "{}{}", - dot_token.span().as_str(), - field_span.as_str(), - )?; + write!(formatted_code, "{}{}", DotToken::AS_STR, field)?; } Self::Ref { - ampersand_token, + ampersand_token: _, mut_token, expr, } => { - // TODO-IG: Add unit tests. - write!(formatted_code, "{}", ampersand_token.span().as_str())?; - if let Some(mut_token) = mut_token { - write!(formatted_code, "{} ", mut_token.span().as_str())?; + write!(formatted_code, "{}", AmpersandToken::AS_STR)?; + if mut_token.is_some() { + write!(formatted_code, "{} ", MutToken::AS_STR)?; } expr.format(formatted_code, formatter)?; } - Self::Deref { star_token, expr } => { - write!(formatted_code, "{}", star_token.span().as_str())?; + Self::Deref { + star_token: _, + expr, + } => { + write!(formatted_code, "{}", StarToken::AS_STR)?; expr.format(formatted_code, formatter)?; } - Self::Not { bang_token, expr } => { - write!(formatted_code, "{}", bang_token.span().as_str())?; + Self::Not { + bang_token: _, + expr, + } => { + write!(formatted_code, "{}", BangToken::AS_STR)?; expr.format(formatted_code, formatter)?; } Self::Pow { lhs, - double_star_token, + double_star_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", double_star_token.span().as_str())?; + write!(formatted_code, " {} ", DoubleStarToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Mul { lhs, - star_token, + star_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", star_token.span().as_str())?; + write!(formatted_code, " {} ", StarToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Div { lhs, - forward_slash_token, + forward_slash_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", forward_slash_token.span().as_str())?; + write!(formatted_code, " {} ", ForwardSlashToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Modulo { lhs, - percent_token, + percent_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", percent_token.span().as_str())?; + write!(formatted_code, " {} ", PercentToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Add { lhs, - add_token, + add_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", add_token.span().as_str())?; + write!(formatted_code, " {} ", AddToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Sub { lhs, - sub_token, + sub_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", sub_token.span().as_str())?; + write!(formatted_code, " {} ", SubToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Shl { lhs, - shl_token, + shl_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", shl_token.span().as_str())?; + write!(formatted_code, " {} ", ShlToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::Shr { lhs, - shr_token, + shr_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", shr_token.span().as_str())?; + write!(formatted_code, " {} ", ShrToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::BitAnd { lhs, - ampersand_token, + ampersand_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; @@ -525,18 +522,18 @@ impl Format for Expr { formatted_code, "\n{}{} ", formatter.indent_to_str()?, - ampersand_token.span().as_str() + AmpersandToken::AS_STR, )?; } _ => { - write!(formatted_code, " {} ", ampersand_token.span().as_str())?; + write!(formatted_code, " {} ", AmpersandToken::AS_STR)?; } } rhs.format(formatted_code, formatter)?; } Self::BitXor { lhs, - caret_token, + caret_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; @@ -546,18 +543,18 @@ impl Format for Expr { formatted_code, "\n{}{} ", formatter.indent_to_str()?, - caret_token.span().as_str() + CaretToken::AS_STR, )?; } _ => { - write!(formatted_code, " {} ", caret_token.span().as_str())?; + write!(formatted_code, " {} ", CaretToken::AS_STR)?; } } rhs.format(formatted_code, formatter)?; } Self::BitOr { lhs, - pipe_token, + pipe_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; @@ -567,81 +564,77 @@ impl Format for Expr { formatted_code, "\n{}{} ", formatter.indent_to_str()?, - pipe_token.span().as_str() + PipeToken::AS_STR, )?; } _ => { - write!(formatted_code, " {} ", pipe_token.span().as_str())?; + write!(formatted_code, " {} ", PipeToken::AS_STR)?; } } rhs.format(formatted_code, formatter)?; } Self::Equal { lhs, - double_eq_token, + double_eq_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", double_eq_token.span().as_str())?; + write!(formatted_code, " {} ", DoubleEqToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::NotEqual { lhs, - bang_eq_token, + bang_eq_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", bang_eq_token.span().as_str())?; + write!(formatted_code, " {} ", BangEqToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::LessThan { lhs, - less_than_token, + less_than_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", less_than_token.span().as_str())?; + write!(formatted_code, " {} ", LessThanToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::GreaterThan { lhs, - greater_than_token, + greater_than_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", greater_than_token.span().as_str())?; + write!(formatted_code, " {} ", GreaterThanToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::LessThanEq { lhs, - less_than_eq_token, + less_than_eq_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", less_than_eq_token.span().as_str())?; + write!(formatted_code, " {} ", LessThanEqToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::GreaterThanEq { lhs, - greater_than_eq_token, + greater_than_eq_token: _, rhs, } => { lhs.format(formatted_code, formatter)?; - write!( - formatted_code, - " {} ", - greater_than_eq_token.span().as_str() - )?; + write!(formatted_code, " {} ", GreaterThanEqToken::AS_STR)?; rhs.format(formatted_code, formatter)?; } Self::LogicalAnd { lhs, - double_ampersand_token, + double_ampersand_token: _, rhs, } => { two_parts_expr( lhs, - double_ampersand_token.span().as_str(), + DoubleAmpersandToken::AS_STR, rhs, formatted_code, formatter, @@ -649,16 +642,10 @@ impl Format for Expr { } Self::LogicalOr { lhs, - double_pipe_token, + double_pipe_token: _, rhs, } => { - two_parts_expr( - lhs, - double_pipe_token.span().as_str(), - rhs, - formatted_code, - formatter, - )?; + two_parts_expr(lhs, DoublePipeToken::AS_STR, rhs, formatted_code, formatter)?; } Self::Reassignment { assignable, @@ -669,11 +656,11 @@ impl Format for Expr { reassignment_op.format(formatted_code, formatter)?; expr.format(formatted_code, formatter)?; } - Self::Break { break_token } => { - write!(formatted_code, "{}", break_token.span().as_str())?; + Self::Break { break_token: _ } => { + write!(formatted_code, "{}", BreakToken::AS_STR)?; } - Self::Continue { continue_token } => { - write!(formatted_code, "{}", continue_token.span().as_str())?; + Self::Continue { continue_token: _ } => { + write!(formatted_code, "{}", ContinueToken::AS_STR)?; } } @@ -888,7 +875,7 @@ where fn format_method_call( target: &Expr, - dot_token: &DotToken, + _dot_token: &DotToken, path_seg: &PathExprSegment, contract_args_opt: &Option>>, args: &Parens>, @@ -906,7 +893,7 @@ fn format_method_call( write!(formatted_code, "\n{}", formatter.indent_to_str()?)?; } - write!(formatted_code, "{}", dot_token.span().as_str())?; + write!(formatted_code, "{}", DotToken::AS_STR)?; path_seg.format(formatted_code, formatter)?; if let Some(contract_args) = &contract_args_opt { @@ -927,7 +914,7 @@ fn format_method_call( Expr::open_parenthesis(formatted_code, formatter)?; let (args_inline, args_str) = write_function_call_arguments(args.get(), formatter)?; - write!(formatted_code, "{}", args_str,)?; + write!(formatted_code, "{}", args_str)?; Expr::close_parenthesis(formatted_code, formatter)?; if formatter.shape.code_line.expr_new_line { @@ -942,15 +929,15 @@ fn get_field_width( ) -> Result<(usize, usize), FormatterError> { let mut largest_field: usize = 0; let mut body_width: usize = 3; // this is taking into account the opening brace, the following space and the ending brace. - for (field, comma_token) in &fields.value_separator_pairs { + for (field, _comma_token) in &fields.value_separator_pairs { let mut field_length = field.field_name.as_str().chars().count(); - if let Some((colon_token, expr)) = &field.expr_opt { + if let Some((_colon_token, expr)) = &field.expr_opt { let mut buf = String::new(); - write!(buf, "{} ", colon_token.span().as_str())?; + write!(buf, "{} ", ColonToken::AS_STR)?; expr.format(&mut buf, formatter)?; field_length += buf.chars().count(); } - field_length += comma_token.span().as_str().chars().count(); + field_length += CommaToken::AS_STR.chars().count(); body_width += &field_length + 1; // accounting for the following space if field_length > largest_field { @@ -959,9 +946,9 @@ fn get_field_width( } if let Some(final_value) = &fields.final_value_opt { let mut field_length = final_value.field_name.as_str().chars().count(); - if let Some((colon_token, expr)) = &final_value.expr_opt { + if let Some((_colon_token, expr)) = &final_value.expr_opt { let mut buf = String::new(); - write!(buf, "{} ", colon_token.span().as_str())?; + write!(buf, "{} ", ColonToken::AS_STR)?; expr.format(&mut buf, formatter)?; field_length += buf.chars().count(); } diff --git a/swayfmt/src/utils/language/expr/struct_field.rs b/swayfmt/src/utils/language/expr/struct_field.rs index 0a51864cc6c..99764c34226 100644 --- a/swayfmt/src/utils/language/expr/struct_field.rs +++ b/swayfmt/src/utils/language/expr/struct_field.rs @@ -10,7 +10,10 @@ use crate::{ }, }; use std::fmt::Write; -use sway_ast::ExprStructField; +use sway_ast::{ + keywords::{ColonToken, Token}, + ExprStructField, +}; use sway_types::{ast::Delimiter, Spanned}; impl Format for ExprStructField { @@ -19,8 +22,8 @@ impl Format for ExprStructField { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{}", self.field_name.span().as_str())?; - if let Some((colon_token, expr)) = &self.expr_opt { + write!(formatted_code, "{}", self.field_name.as_str())?; + if let Some((_colon_token, expr)) = &self.expr_opt { formatter.with_shape( formatter .shape @@ -37,12 +40,7 @@ impl Format for ExprStructField { } else { expr_str }; - write!( - formatted_code, - "{} {}", - colon_token.span().as_str(), - expr_str - )?; + write!(formatted_code, "{} {}", ColonToken::AS_STR, expr_str)?; Ok(()) }, )?; diff --git a/swayfmt/src/utils/language/expr/tests.rs b/swayfmt/src/utils/language/expr/tests.rs index ef84d8b0c61..6472940e346 100644 --- a/swayfmt/src/utils/language/expr/tests.rs +++ b/swayfmt/src/utils/language/expr/tests.rs @@ -264,3 +264,13 @@ intermediate_whitespace "{ let i = 42; }"); + +fmt_test_expr!(basic_for_loop +"for iter in [0, 1, 7, 8, 15] { + let i = 42; +}", +intermediate_whitespace +"for iter in [0, 1, 7, 8, 15]{ +let i = 42; +}" +); diff --git a/swayfmt/src/utils/language/literal.rs b/swayfmt/src/utils/language/literal.rs index 3918f4de7f6..8fecedae9eb 100644 --- a/swayfmt/src/utils/language/literal.rs +++ b/swayfmt/src/utils/language/literal.rs @@ -3,7 +3,7 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::Literal; +use sway_ast::{literal::LitBoolType, LitIntType, Literal}; impl Format for Literal { fn format( @@ -12,16 +12,56 @@ impl Format for Literal { _formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self { - // TODO: do more digging into `Literal` and see if there is more formatting to do. - Self::String(lit_string) => write!(formatted_code, "{}", lit_string.span.as_str())?, - Self::Char(lit_char) => write!(formatted_code, "{}", lit_char.span.as_str())?, + Self::String(lit_string) => write!(formatted_code, "\"{}\"", lit_string.parsed)?, + Self::Char(lit_char) => write!(formatted_code, "\'{}\'", lit_char.parsed)?, Self::Int(lit_int) => { - write!(formatted_code, "{}", lit_int.span.as_str())?; - if let Some((_, ty_span)) = &lit_int.ty_opt { - write!(formatted_code, "{}", ty_span.as_str())?; + // It is tricky to support formatting of `LitInt` for an arbitrary `LitInt` + // that is potentially not backed by source code, but constructed in-memory. + // + // E.g., a constructed `LitInt` representing 1000 can have only the numeric value + // (LitInt::parsed) specified, in which case we can simply output the value. + // If it has the type specified (LitInt::ty_opt), we can output the type next to the + // value, e.g., 1000u16. + // But a `LitInt` backed by code can have an arbitrary representation that includes + // underscores. E.g., 1_00_00__u16. In that case we need to preserve the original + // representation. + // + // The taken approach is the following. If the length of the `LitInt::span` is zero, + // we assume that it is not backed by source code and render the canonical representation, + // 1000u16 in the above example. Otherwise, we assume that it is backed by source code + // and use the actual spans to obtain the strings. + + if lit_int.span.is_empty() { + write!(formatted_code, "{}", lit_int.parsed)?; + if let Some((int_type, _)) = &lit_int.ty_opt { + let int_type = match int_type { + LitIntType::U8 => "u8", + LitIntType::U16 => "u16", + LitIntType::U32 => "u32", + LitIntType::U64 => "u64", + LitIntType::U256 => "u256", + LitIntType::I8 => "i8", + LitIntType::I16 => "i16", + LitIntType::I32 => "i32", + LitIntType::I64 => "i64", + }; + write!(formatted_code, "{}", int_type)?; + } + } else { + write!(formatted_code, "{}", lit_int.span.as_str())?; + if let Some((_, ty_span)) = &lit_int.ty_opt { + write!(formatted_code, "{}", ty_span.as_str())?; + } } } - Self::Bool(lit_bool) => write!(formatted_code, "{}", lit_bool.span.as_str())?, + Self::Bool(lit_bool) => write!( + formatted_code, + "{}", + match lit_bool.kind { + LitBoolType::True => "true", + LitBoolType::False => "false", + } + )?, } Ok(()) } diff --git a/swayfmt/src/utils/language/path.rs b/swayfmt/src/utils/language/path.rs index 78711542b7f..6d5a84ded1d 100644 --- a/swayfmt/src/utils/language/path.rs +++ b/swayfmt/src/utils/language/path.rs @@ -7,7 +7,8 @@ use crate::{ }; use std::{fmt::Write, vec}; use sway_ast::{ - keywords::Token, PathExpr, PathExprSegment, PathType, PathTypeSegment, QualifiedPathRoot, + keywords::{AsToken, Keyword, Token}, + DoubleColonToken, PathExpr, PathExprSegment, PathType, PathTypeSegment, QualifiedPathRoot, }; use sway_types::Spanned; @@ -17,7 +18,7 @@ impl Format for PathExpr { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - if let Some((qualified_path_root, double_colon_token)) = &self.root_opt { + if let Some((qualified_path_root, _double_colon_token)) = &self.root_opt { if let Some(root) = &qualified_path_root { open_angle_bracket(formatted_code)?; root.clone() @@ -25,11 +26,11 @@ impl Format for PathExpr { .format(formatted_code, formatter)?; close_angle_bracket(formatted_code)?; } - write!(formatted_code, "{}", double_colon_token.ident().as_str())?; + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; } self.prefix.format(formatted_code, formatter)?; - for (double_colon_token, path_expr_segment) in self.suffix.iter() { - write!(formatted_code, "{}", double_colon_token.span().as_str())?; + for (_double_colon_token, path_expr_segment) in self.suffix.iter() { + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; path_expr_segment.format(formatted_code, formatter)?; } @@ -46,8 +47,8 @@ impl Format for PathExprSegment { // name self.name.format(formatted_code, formatter)?; // generics `::` - if let Some((double_colon_token, generic_args)) = &self.generics_opt { - write!(formatted_code, "{}", double_colon_token.span().as_str())?; + if let Some((_double_colon_token, generic_args)) = &self.generics_opt { + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; generic_args.format(formatted_code, formatter)?; } @@ -62,10 +63,9 @@ impl Format for QualifiedPathRoot { formatter: &mut Formatter, ) -> Result<(), FormatterError> { self.ty.format(formatted_code, formatter)?; - if let Some((as_token, path_type)) = &self.as_trait { - write!(formatted_code, " {} ", as_token.span().as_str())?; - path_type.format(formatted_code, formatter)?; - } + let (_as_token, path_type) = &self.as_trait; + write!(formatted_code, " {} ", AsToken::AS_STR)?; + path_type.format(formatted_code, formatter)?; Ok(()) } @@ -77,7 +77,7 @@ impl Format for PathType { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - if let Some((root_opt, double_colon_token)) = &self.root_opt { + if let Some((root_opt, _double_colon_token)) = &self.root_opt { if let Some(qualified_path_root) = &root_opt { open_angle_bracket(formatted_code)?; qualified_path_root @@ -86,11 +86,11 @@ impl Format for PathType { .format(formatted_code, formatter)?; close_angle_bracket(formatted_code)?; } - write!(formatted_code, "{}", double_colon_token.span().as_str())?; + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; } self.prefix.format(formatted_code, formatter)?; - for (double_colon_token, path_type_segment) in self.suffix.iter() { - write!(formatted_code, "{}", double_colon_token.span().as_str())?; + for (_double_colon_token, path_type_segment) in self.suffix.iter() { + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; path_type_segment.format(formatted_code, formatter)?; } @@ -105,11 +105,11 @@ impl Format for PathTypeSegment { formatter: &mut Formatter, ) -> Result<(), FormatterError> { // name - write!(formatted_code, "{}", self.name.span().as_str())?; + write!(formatted_code, "{}", self.name.as_str())?; // generics `::` if let Some((double_colon_opt, generic_args)) = &self.generics_opt { - if let Some(double_colon_token) = &double_colon_opt { - write!(formatted_code, "{}", double_colon_token.span().as_str())?; + if double_colon_opt.is_some() { + write!(formatted_code, "{}", DoubleColonToken::AS_STR)?; } generic_args.format(formatted_code, formatter)?; } diff --git a/swayfmt/src/utils/language/pattern.rs b/swayfmt/src/utils/language/pattern.rs index 5c95b2e0ba3..655db700939 100644 --- a/swayfmt/src/utils/language/pattern.rs +++ b/swayfmt/src/utils/language/pattern.rs @@ -10,6 +10,7 @@ use crate::{ }; use std::fmt::Write; use sway_ast::{ + keywords::{ColonToken, DoubleDotToken, Keyword, MutToken, RefToken, Token, UnderscoreToken}, Braces, CommaToken, ExprTupleDescriptor, PathExpr, Pattern, PatternStructField, Punctuated, }; use sway_types::{ast::Delimiter, Spanned}; @@ -30,19 +31,19 @@ impl Format for Pattern { formatted_code.push_str(" | "); rhs.format(formatted_code, formatter)?; } - Self::Wildcard { underscore_token } => { - formatted_code.push_str(underscore_token.span().as_str()) - } + Self::Wildcard { + underscore_token: _, + } => formatted_code.push_str(UnderscoreToken::AS_STR), Self::Var { reference, mutable, name, } => { - if let Some(ref_token) = reference { - write!(formatted_code, "{} ", ref_token.span().as_str())?; + if reference.is_some() { + write!(formatted_code, "{} ", RefToken::AS_STR)?; } - if let Some(mut_token) = mutable { - write!(formatted_code, "{} ", mut_token.span().as_str())?; + if mutable.is_some() { + write!(formatted_code, "{} ", MutToken::AS_STR)?; } name.format(formatted_code, formatter)?; } @@ -188,16 +189,16 @@ impl Format for PatternStructField { formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self { - Self::Rest { token } => { - write!(formatted_code, "{}", token.span().as_str())?; + Self::Rest { token: _ } => { + write!(formatted_code, "{}", DoubleDotToken::AS_STR)?; } Self::Field { field_name, pattern_opt, } => { - write!(formatted_code, "{}", field_name.span().as_str())?; - if let Some((colon_token, pattern)) = pattern_opt { - write!(formatted_code, "{} ", colon_token.span().as_str())?; + write!(formatted_code, "{}", field_name.as_str())?; + if let Some((_colon_token, pattern)) = pattern_opt { + write!(formatted_code, "{} ", ColonToken::AS_STR)?; pattern.format(formatted_code, formatter)?; } } @@ -213,12 +214,12 @@ fn get_field_width( ) -> Result<(usize, usize), FormatterError> { let mut largest_field: usize = 0; let mut body_width: usize = 3; // this is taking into account the opening brace, the following space and the ending brace. - for (field, comma_token) in &fields.value_separator_pairs { + for (field, _comma_token) in &fields.value_separator_pairs { let mut field_str = FormattedCode::new(); field.format(&mut field_str, formatter)?; let mut field_length = field_str.chars().count(); - field_length += comma_token.span().as_str().chars().count(); + field_length += CommaToken::AS_STR.chars().count(); body_width += &field_length + 1; // accounting for the following space if field_length > largest_field { diff --git a/swayfmt/src/utils/language/punctuated.rs b/swayfmt/src/utils/language/punctuated.rs index a06a6c68e55..ff488a60864 100644 --- a/swayfmt/src/utils/language/punctuated.rs +++ b/swayfmt/src/utils/language/punctuated.rs @@ -8,8 +8,9 @@ use crate::{ }; use std::fmt::Write; use sway_ast::{ - keywords::CommaToken, punctuated::Punctuated, ConfigurableField, ItemStorage, StorageEntry, - StorageField, TypeField, + keywords::{ColonToken, CommaToken, EqToken, InToken, Keyword, Token}, + punctuated::Punctuated, + ConfigurableField, ItemStorage, PubToken, StorageEntry, StorageField, TypeField, }; use sway_types::{ast::PunctKind, Ident, Spanned}; @@ -27,7 +28,7 @@ where formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - if !self.value_separator_pairs.is_empty() || self.final_value_opt.is_some() { + if !self.is_empty() { match formatter.shape.code_line.line_style { LineStyle::Normal => { write!( @@ -55,7 +56,7 @@ where if !formatted_code.ends_with('\n') { writeln!(formatted_code)?; } - if !self.value_separator_pairs.is_empty() || self.final_value_opt.is_some() { + if !self.is_empty() { formatter.write_indent_into_buffer(formatted_code)?; } @@ -191,8 +192,8 @@ impl Format for Ident { _formatter: &mut Formatter, ) -> Result<(), FormatterError> { match self.is_raw_ident() { - true => write!(formatted_code, "{}{}", RAW_MODIFIER, self.span().as_str())?, - false => write!(formatted_code, "{}", self.span().as_str())?, + true => write!(formatted_code, "{}{}", RAW_MODIFIER, self.as_str())?, + false => write!(formatted_code, "{}", self.as_str())?, } Ok(()) @@ -206,14 +207,14 @@ impl Format for TypeField { formatter: &mut Formatter, ) -> Result<(), FormatterError> { // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &self.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; + if self.visibility.is_some() { + write!(formatted_code, "{} ", PubToken::AS_STR)?; } write!( formatted_code, "{}{} ", - self.name.span().as_str(), - self.colon_token.span().as_str(), + self.name.as_str(), + ColonToken::AS_STR, )?; self.ty.format(formatted_code, formatter)?; @@ -233,11 +234,11 @@ impl Format for ConfigurableField { write!( formatted_code, "{}{} ", - self.name.span().as_str(), - self.colon_token.span().as_str(), + self.name.as_str(), + ColonToken::AS_STR, )?; self.ty.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", self.eq_token.span().as_str())?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; Ok(()) }, @@ -258,17 +259,17 @@ impl Format for StorageField { formatter.with_shape( formatter.shape.with_default_code_line(), |formatter| -> Result<(), FormatterError> { - write!(formatted_code, "{}", self.name.span().as_str())?; - if let Some(in_token) = &self.in_token { - write!(formatted_code, " {}", in_token.span().as_str())?; + write!(formatted_code, "{}", self.name.as_str())?; + if self.in_token.is_some() { + write!(formatted_code, " {} ", InToken::AS_STR)?; } if let Some(key_expr) = &self.key_expr { - write!(formatted_code, " {}", key_expr.span().as_str())?; + key_expr.format(formatted_code, formatter)?; } - write!(formatted_code, "{} ", self.colon_token.span().as_str())?; + write!(formatted_code, "{} ", ColonToken::AS_STR)?; self.ty.format(formatted_code, formatter)?; - write!(formatted_code, " {} ", self.eq_token.span().as_str())?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; Ok(()) }, @@ -328,7 +329,7 @@ impl Format for CommaToken { formatted_code: &mut FormattedCode, _formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{}", self.span().as_str())?; + write!(formatted_code, "{}", CommaToken::AS_STR)?; Ok(()) } diff --git a/swayfmt/src/utils/language/statement.rs b/swayfmt/src/utils/language/statement.rs index ffbcd443f66..1359161bf2c 100644 --- a/swayfmt/src/utils/language/statement.rs +++ b/swayfmt/src/utils/language/statement.rs @@ -3,7 +3,10 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{Expr, Parens, Punctuated, Statement, StatementLet}; +use sway_ast::{ + keywords::{ColonToken, EqToken, Keyword, LetToken, SemicolonToken, Token}, + Expr, Parens, Punctuated, Statement, StatementLet, +}; use sway_types::{Span, Spanned}; impl Format for Statement { @@ -20,7 +23,7 @@ impl Format for Statement { } /// Remove arguments from the expression if the expression is a method call if -/// the method is a simple two path call (foo.bar()). This needed because in +/// the method is a simple two path call (foo.bar()). This is needed because in /// method calls of two parts they are never broke into multiple lines. /// Arguments however can be broken into multiple lines, and that is handled /// by `write_function_call_arguments` @@ -117,11 +120,11 @@ fn format_statement( } else { expr.format(formatted_code, formatter)?; } - if let Some(semicolon) = semicolon_token_opt { + if semicolon_token_opt.is_some() { if formatter.shape.code_line.line_style == LineStyle::Inline { - write!(formatted_code, "{}", semicolon.span().as_str())?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; } else { - writeln!(formatted_code, "{}", semicolon.span().as_str())?; + writeln!(formatted_code, "{}", SemicolonToken::AS_STR)?; } } } @@ -140,24 +143,24 @@ impl Format for StatementLet { formatter: &mut Formatter, ) -> Result<(), FormatterError> { // `let ` - write!(formatted_code, "{} ", self.let_token.span().as_str())?; + write!(formatted_code, "{} ", LetToken::AS_STR)?; // pattern self.pattern.format(formatted_code, formatter)?; // `: Ty` - if let Some(ty) = &self.ty_opt { - write!(formatted_code, "{} ", ty.0.span().as_str())?; - ty.1.format(formatted_code, formatter)?; + if let Some((_colon_token, ty)) = &self.ty_opt { + write!(formatted_code, "{} ", ColonToken::AS_STR)?; + ty.format(formatted_code, formatter)?; } // ` = ` - write!(formatted_code, " {} ", self.eq_token.span().as_str())?; + write!(formatted_code, " {} ", EqToken::AS_STR)?; // expr self.expr.format(formatted_code, formatter)?; if formatter.shape.code_line.line_style == LineStyle::Inline { // `;` - write!(formatted_code, "{}", self.semicolon_token.span().as_str())?; + write!(formatted_code, "{}", SemicolonToken::AS_STR)?; } else { // `;\n` - writeln!(formatted_code, "{}", self.semicolon_token.span().as_str())?; + writeln!(formatted_code, "{}", SemicolonToken::AS_STR)?; } Ok(()) diff --git a/swayfmt/src/utils/language/ty.rs b/swayfmt/src/utils/language/ty.rs index a509cca1567..327ed8165cc 100644 --- a/swayfmt/src/utils/language/ty.rs +++ b/swayfmt/src/utils/language/ty.rs @@ -6,8 +6,12 @@ use std::fmt::Write; use sway_ast::{ brackets::SquareBrackets, expr::Expr, - keywords::{AmpersandToken, MutToken, PtrToken, SliceToken, StrToken, Token, UnderscoreToken}, + keywords::{ + AmpersandToken, BangToken, Keyword, MutToken, PtrToken, SemicolonToken, SliceToken, + StrToken, Token, UnderscoreToken, + }, ty::{Ty, TyArrayDescriptor, TyTupleDescriptor}, + CommaToken, }; use sway_types::{ast::Delimiter, Spanned}; @@ -24,39 +28,35 @@ impl Format for Ty { write!(formatted_code, "{}", Delimiter::Bracket.as_close_char())?; Ok(()) } - Self::Infer { underscore_token } => format_infer(formatted_code, underscore_token), + Self::Infer { + underscore_token: _, + } => format_infer(formatted_code), Self::Path(path_ty) => path_ty.format(formatted_code, formatter), Self::StringSlice(_) => { - write!(formatted_code, "str")?; + write!(formatted_code, "{}", StrToken::AS_STR)?; Ok(()) } - Self::StringArray { str_token, length } => { - format_str(formatted_code, str_token.clone(), length.clone()) - } + Self::StringArray { + str_token: _, + length, + } => format_str(formatted_code, formatter, length.clone()), Self::Tuple(tup_descriptor) => { write!(formatted_code, "{}", Delimiter::Parenthesis.as_open_char())?; tup_descriptor.get().format(formatted_code, formatter)?; write!(formatted_code, "{}", Delimiter::Parenthesis.as_close_char())?; Ok(()) } - Self::Ptr { ptr_token, ty } => { - format_ptr(formatted_code, ptr_token.clone(), ty.clone()) - } + Self::Ptr { ptr_token: _, ty } => format_ptr(formatted_code, formatter, ty.clone()), Self::Slice { slice_token, ty } => { - format_slice(formatted_code, slice_token.clone(), ty.clone()) + format_slice(formatted_code, formatter, slice_token, ty.clone()) } Self::Ref { - ampersand_token, + ampersand_token: _, mut_token, ty, - } => format_ref( - formatted_code, - ampersand_token.clone(), - mut_token.clone(), - ty.clone(), - ), - Self::Never { bang_token } => { - write!(formatted_code, "{}", bang_token.span().as_str(),)?; + } => format_ref(formatted_code, formatter, mut_token, ty), + Self::Never { bang_token: _ } => { + write!(formatted_code, "{}", BangToken::AS_STR)?; Ok(()) } } @@ -64,11 +64,9 @@ impl Format for Ty { } /// Simply inserts a `_` token to the `formatted_code`. -fn format_infer( - formatted_code: &mut FormattedCode, - underscore_token: &UnderscoreToken, -) -> Result<(), FormatterError> { - formatted_code.push_str(underscore_token.ident().as_str()); +fn format_infer(formatted_code: &mut FormattedCode) -> Result<(), FormatterError> { + formatted_code.push_str(UnderscoreToken::AS_STR); + Ok(()) } @@ -79,78 +77,73 @@ impl Format for TyArrayDescriptor { formatter: &mut Formatter, ) -> Result<(), FormatterError> { self.ty.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", self.semicolon_token.span().as_str())?; + write!(formatted_code, "{} ", SemicolonToken::AS_STR)?; self.length.format(formatted_code, formatter)?; + Ok(()) } } fn format_str( formatted_code: &mut FormattedCode, - str_token: StrToken, + formatter: &mut Formatter, length: SquareBrackets>, ) -> Result<(), FormatterError> { - write!( - formatted_code, - "{}{}{}{}", - str_token.span().as_str(), - Delimiter::Bracket.as_open_char(), - length.into_inner().span().as_str(), - Delimiter::Bracket.as_close_char() - )?; + write!(formatted_code, "{}", StrToken::AS_STR)?; + write!(formatted_code, "{}", Delimiter::Bracket.as_open_char())?; + length.into_inner().format(formatted_code, formatter)?; + write!(formatted_code, "{}", Delimiter::Bracket.as_close_char())?; + Ok(()) } fn format_ptr( formatted_code: &mut FormattedCode, - ptr_token: PtrToken, + formatter: &mut Formatter, ty: SquareBrackets>, ) -> Result<(), FormatterError> { - write!( - formatted_code, - "{}[{}]", - ptr_token.span().as_str(), - ty.into_inner().span().as_str() - )?; + write!(formatted_code, "{}", PtrToken::AS_STR)?; + write!(formatted_code, "{}", Delimiter::Bracket.as_open_char())?; + ty.into_inner().format(formatted_code, formatter)?; + write!(formatted_code, "{}", Delimiter::Bracket.as_close_char())?; + Ok(()) } fn format_slice( formatted_code: &mut FormattedCode, - slice_token: Option, + formatter: &mut Formatter, + slice_token: &Option, ty: SquareBrackets>, ) -> Result<(), FormatterError> { - if let Some(slice_token) = slice_token { - write!( - formatted_code, - "{}[{}]", - slice_token.span().as_str(), - ty.into_inner().span().as_str() - )?; - } else { - write!(formatted_code, "[{}]", ty.into_inner().span().as_str())?; + if slice_token.is_some() { + write!(formatted_code, "{}", SliceToken::AS_STR)?; } + write!(formatted_code, "{}", Delimiter::Bracket.as_open_char())?; + ty.into_inner().format(formatted_code, formatter)?; + write!(formatted_code, "{}", Delimiter::Bracket.as_close_char())?; Ok(()) } fn format_ref( formatted_code: &mut FormattedCode, - ampersand_token: AmpersandToken, - mut_token: Option, - ty: Box, + formatter: &mut Formatter, + mut_token: &Option, + ty: &Ty, ) -> Result<(), FormatterError> { write!( formatted_code, - "{}{}{}", - ampersand_token.span().as_str(), - if let Some(mut_token) = mut_token { - format!("{} ", mut_token.span().as_str()) + "{}{}", + AmpersandToken::AS_STR, + if mut_token.is_some() { + format!("{} ", MutToken::AS_STR) } else { "".to_string() }, - ty.span().as_str() )?; + ty.format(formatted_code, formatter)?; + Ok(()) } @@ -162,7 +155,7 @@ impl Format for TyTupleDescriptor { ) -> Result<(), FormatterError> { if let TyTupleDescriptor::Cons { head, - comma_token, + comma_token: _, tail, } = self { @@ -170,7 +163,7 @@ impl Format for TyTupleDescriptor { formatter.shape.with_default_code_line(), |formatter| -> Result<(), FormatterError> { head.format(formatted_code, formatter)?; - write!(formatted_code, "{} ", comma_token.ident().as_str())?; + write!(formatted_code, "{} ", CommaToken::AS_STR)?; tail.format(formatted_code, formatter)?; Ok(()) diff --git a/swayfmt/src/utils/language/where_clause.rs b/swayfmt/src/utils/language/where_clause.rs index 6a365370572..805bac01b86 100644 --- a/swayfmt/src/utils/language/where_clause.rs +++ b/swayfmt/src/utils/language/where_clause.rs @@ -3,8 +3,11 @@ use crate::{ utils::map::byte_span::{ByteSpan, LeafSpans}, }; use std::fmt::Write; -use sway_ast::{WhereBound, WhereClause}; -use sway_types::{ast::PunctKind, Spanned}; +use sway_ast::{ + keywords::{ColonToken, Keyword, Token, WhereToken}, + CommaToken, WhereBound, WhereClause, +}; +use sway_types::Spanned; impl Format for WhereClause { fn format( @@ -15,8 +18,8 @@ impl Format for WhereClause { writeln!( formatted_code, "{}{}", - &formatter.indent_to_str()?, - self.where_token.span().as_str(), + formatter.indent_to_str()?, + WhereToken::AS_STR, )?; formatter.indent(); // We should add a multiline field to `Shape` @@ -27,18 +30,19 @@ impl Format for WhereClause { // ``` // let value_pairs = self.bounds.value_separator_pairs.clone(); - for (bound, comma_token) in value_pairs.iter() { + for (bound, _comma_token) in value_pairs.iter() { // `WhereBound` bound.format(formatted_code, formatter)?; // `CommaToken` - writeln!(formatted_code, "{}", comma_token.span().as_str())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } if let Some(final_value) = &self.bounds.final_value_opt { final_value.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; + writeln!(formatted_code, "{}", CommaToken::AS_STR)?; } // reset indent formatter.unindent(); + Ok(()) } } @@ -52,11 +56,12 @@ impl Format for WhereBound { write!( formatted_code, "{}{}{} ", - &formatter.indent_to_str()?, // `Indent` - self.ty_name.span().as_str(), // `Ident` - self.colon_token.span().as_str(), // `ColonToken` + formatter.indent_to_str()?, + self.ty_name.as_str(), + ColonToken::AS_STR, )?; self.bounds.format(formatted_code, formatter)?; + Ok(()) } } diff --git a/swayfmt/src/utils/map/byte_span.rs b/swayfmt/src/utils/map/byte_span.rs index 36578739dcf..fec51afdc1c 100644 --- a/swayfmt/src/utils/map/byte_span.rs +++ b/swayfmt/src/utils/map/byte_span.rs @@ -49,8 +49,8 @@ impl ByteSpan { impl Ord for ByteSpan { fn cmp(&self, other: &Self) -> Ordering { - // If the starting position is the same encapsulatig span (i.e, wider one) should come - // first + // If the starting position is the same encapsulating span (i.e, wider one) should come + // first. match self.start.cmp(&other.start) { Ordering::Equal => other.end.cmp(&self.end), ord => ord, diff --git a/swayfmt/src/utils/map/newline.rs b/swayfmt/src/utils/map/newline.rs index 769b05e7ad3..9600248beee 100644 --- a/swayfmt/src/utils/map/newline.rs +++ b/swayfmt/src/utils/map/newline.rs @@ -1,8 +1,7 @@ use anyhow::Result; use ropey::{str_utils::byte_to_char_idx, Rope}; -use std::{collections::BTreeMap, fmt::Display, path::PathBuf, sync::Arc}; +use std::{collections::BTreeMap, fmt::Display, sync::Arc}; use sway_ast::Module; -use sway_types::SourceEngine; use crate::{ constants::NEW_LINE, @@ -106,11 +105,9 @@ fn newline_map_from_src(unformatted_input: &str) -> Result, unformatted_module: &Module, formatted_input: Arc, - path: Option>, formatted_code: &mut FormattedCode, formatter: &Formatter, ) -> Result<(), FormatterError> { @@ -123,7 +120,7 @@ pub fn handle_newlines( // formatting the code a second time will still produce the same result. let newline_map = newline_map_from_src(&unformatted_input)?; // After the formatting existing items should be the same (type of the item) but their spans will be changed since we applied formatting to them. - let formatted_module = parse_file(source_engine, formatted_input, path)?.value; + let formatted_module = parse_file(formatted_input)?.value; // Actually find & insert the newline sequences add_newlines( newline_map, diff --git a/swayfmt/tests/mod.rs b/swayfmt/tests/mod.rs index 890995ef338..75f46632b23 100644 --- a/swayfmt/tests/mod.rs +++ b/swayfmt/tests/mod.rs @@ -6,11 +6,11 @@ use test_macros::assert_eq_pretty; /// Takes a configured formatter as input and formats a given input and checks the actual output against an /// expected output. There are two format passes to ensure that the received output does not change on a second pass. fn check_with_formatter(unformatted: &str, expected: &str, formatter: &mut Formatter) { - let first_formatted = Formatter::format(formatter, Arc::from(unformatted), None).unwrap(); + let first_formatted = Formatter::format(formatter, Arc::from(unformatted)).unwrap(); assert_eq_pretty!(first_formatted, expected); let second_formatted = - Formatter::format(formatter, Arc::from(first_formatted.clone()), None).unwrap(); + Formatter::format(formatter, Arc::from(first_formatted.clone())).unwrap(); assert_eq_pretty!(second_formatted, first_formatted); } @@ -74,7 +74,33 @@ fn struct_alignment() { contract; pub struct Foo { barbazfoo: u64, - baz : bool, + baz : bool, + } + "#}, + indoc! {r#" + contract; + pub struct Foo { + barbazfoo : u64, + baz : bool, + } + "#}, + &mut formatter, + ); +} + +#[test] +#[ignore = "Bug in `swayfmt`. Activate this test once https://github.com/FuelLabs/sway/issues/6805 is fixed."] +fn struct_alignment_without_trailing_comma() { + // The last struct field does not have trailing comma. + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::AlignFields(40); + + check_with_formatter( + indoc! {r#" + contract; + pub struct Foo { + barbazfoo: u64, + baz : bool } "#}, indoc! {r#" @@ -98,7 +124,51 @@ fn struct_alignment_with_public_fields() { contract; pub struct Foo { barbazfoo: u64, - pub baz : bool, + pub baz : bool, + } + "#}, + indoc! {r#" + contract; + pub struct Foo { + barbazfoo : u64, + pub baz : bool, + } + "#}, + &mut formatter, + ); + + check_with_formatter( + indoc! {r#" + contract; + pub struct Foo { + pub barbazfoo: u64, + baz : bool, + } + "#}, + indoc! {r#" + contract; + pub struct Foo { + pub barbazfoo : u64, + baz : bool, + } + "#}, + &mut formatter, + ); +} + +#[test] +#[ignore = "Bug in `swayfmt`. Activate this test once https://github.com/FuelLabs/sway/issues/6805 is fixed."] +fn struct_alignment_with_public_fields_without_trailing_comma() { + // The last struct field does not have trailing comma. + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::AlignFields(40); + + check_with_formatter( + indoc! {r#" + contract; + pub struct Foo { + barbazfoo: u64, + pub baz : bool } "#}, indoc! {r#" @@ -116,7 +186,7 @@ fn struct_alignment_with_public_fields() { contract; pub struct Foo { pub barbazfoo: u64, - baz : bool, + baz : bool } "#}, indoc! {r#" @@ -157,7 +227,34 @@ fn struct_public_fields() { } #[test] -fn struct_ending_comma() { +fn struct_public_fields_without_trailing_comma() { + // The last struct field does not have trailing comma. + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::Off; + + check_with_formatter( + indoc! {r#" + contract; + pub struct Foo { + pub barbaz: T, + foo: u64, + pub baz : bool + } + "#}, + indoc! {r#" + contract; + pub struct Foo { + pub barbaz: T, + foo: u64, + pub baz: bool, + } + "#}, + &mut formatter, + ); +} + +#[test] +fn struct_add_ending_comma() { check( indoc! {r#" contract; @@ -183,10 +280,10 @@ fn enum_without_variant_alignment() { contract; enum Color { - Blue: (), Green: (), - Red: (), - Silver: (), - Grey: () } + Blue: (), Green: (), + Red: (), + Silver: () , + Grey: () , } "#}, indoc! {r#" contract; @@ -204,7 +301,6 @@ fn enum_without_variant_alignment() { #[test] fn enum_with_variant_alignment() { - // Creating a config with enum_variant_align_threshold that exceeds longest variant length let mut formatter = Formatter::default(); formatter.config.structures.field_alignment = FieldAlignment::AlignFields(20); check_with_formatter( @@ -212,10 +308,68 @@ fn enum_with_variant_alignment() { contract; enum Color { - Blue: (), Green: (), - Red: (), + Blue: (), Green: (), + Red: (), + Silver: () , + Grey: () , } + "#}, + indoc! {r#" + contract; + + enum Color { + Blue : (), + Green : (), + Red : (), + Silver : (), + Grey : (), + } + "#}, + &mut formatter, + ); +} + +#[test] +fn enum_without_variant_alignment_without_trailing_comma() { + // The last enum variant does not have trailing comma. + check( + indoc! {r#" + contract; + + enum Color { + Blue: (), Green : (), + Red : (), + Silver: () , + Grey: () } + "#}, + indoc! {r#" + contract; + + enum Color { + Blue: (), + Green: (), + Red: (), Silver: (), - Grey: (), } + Grey: (), + } + "#}, + ); +} + +#[test] +#[ignore = "Bug in `swayfmt`. Activate this test once https://github.com/FuelLabs/sway/issues/6805 is fixed."] +fn enum_with_variant_alignment_without_trailing_comma() { + // The last enum variant does not have trailing comma. + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::AlignFields(20); + check_with_formatter( + indoc! {r#" + contract; + + enum Color { + Blue: (), Green : (), + Red : (), + Silver: () , + Grey: () } "#}, indoc! {r#" contract; @@ -232,6 +386,119 @@ fn enum_with_variant_alignment() { ); } +#[test] +fn configurable_without_alignment() { + check( + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, Green: u64 = 0, + Red: u64=0, + Silver: u64= 0, + Grey: u64 =0, } + "#}, + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, + Green: u64 = 0, + Red: u64 = 0, + Silver: u64 = 0, + Grey: u64 = 0, + } + "#}, + ); +} + +#[test] +fn configurable_with_alignment() { + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::AlignFields(20); + check_with_formatter( + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, Green: u64 = 0, + Red: u64=0, + Silver: u64= 0, + Grey: u64 =0, } + "#}, + indoc! {r#" + contract; + + configurable { + Blue : u64 = 0, + Green : u64 = 0, + Red : u64 = 0, + Silver : u64 = 0, + Grey : u64 = 0, + } + "#}, + &mut formatter, + ); +} + +#[test] +fn configurable_without_alignment_without_trailing_comma() { + // The last configurable does not have trailing comma. + check( + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, Green: u64 = 0, + Red: u64=0, + Silver: u64= 0, + Grey: u64 =0 } + "#}, + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, + Green: u64 = 0, + Red: u64 = 0, + Silver: u64 = 0, + Grey: u64 = 0, + } + "#}, + ); +} + +#[test] +#[ignore = "Bug in `swayfmt`. Activate this test once https://github.com/FuelLabs/sway/issues/6805 is fixed."] +fn configurable_with_alignment_without_trailing_comma() { + // The last configurable does not have trailing comma. + let mut formatter = Formatter::default(); + formatter.config.structures.field_alignment = FieldAlignment::AlignFields(20); + check_with_formatter( + indoc! {r#" + contract; + + configurable { + Blue: u64 = 0, Green: u64 = 0, + Red: u64=0, + Silver: u64= 0, + Grey: u64 =0 } + "#}, + indoc! {r#" + contract; + + configurable { + Blue : u64 = 0, + Green : u64 = 0, + Red : u64 = 0, + Silver : u64 = 0, + Grey : u64 = 0, + } + "#}, + &mut formatter, + ); +} + #[test] fn item_abi_with_generics_and_attributes() { check( @@ -3155,3 +3422,70 @@ fn retain_in_keyword() { "#}, ); } + +#[test] +fn tuple_field_access() { + check( + indoc! {r#" + contract; + + fn fun() { + let t = (1, 1); + let a = t . 0; + let b = t + . + 1 + ; + } + "#}, + indoc! {r#" + contract; + + fn fun() { + let t = (1, 1); + let a = t.0; + let b = t.1; + } + "#}, + ); +} + +#[test] +fn contract_for_loop() { + check( + indoc! {r#" + contract; + + abi MyContract { + fn test_function() -> bool; + } + + impl MyContract for Contract { + fn test_function() -> bool { + let mut my_vec: Vec = Vec::new(); + for iter in my_vec.iter() { + + } + + true + } + } + "#}, + indoc! {r#" + contract; + + abi MyContract { + fn test_function() -> bool; + } + + impl MyContract for Contract { + fn test_function() -> bool { + let mut my_vec: Vec = Vec::new(); + for iter in my_vec.iter() { } + + true + } + } + "#}, + ); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.lock new file mode 100644 index 00000000000..7f8a3338563 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "array_oob_reassignment" +source = "member" +dependencies = ["core"] + +[[package]] +name = "core" +source = "path+from-root-CC73096846C1E083" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.toml new file mode 100644 index 00000000000..cc38134eea8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "array_oob_reassignment" +entry = "main.sw" +implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/src/main.sw new file mode 100644 index 00000000000..cd5d7979225 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/src/main.sw @@ -0,0 +1,16 @@ +script; + +fn main() { + let mut a = [u64; 0]; + a[0] = 1; + + + let mut b = [[u64; 1]; 1]; + b[0][1] = 1; + + + b[1][0] = 1; + + + a[0] = return; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/test.toml new file mode 100644 index 00000000000..e3c8288e736 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_oob_reassignment/test.toml @@ -0,0 +1,19 @@ +category = "fail" + +# check: $()let mut a = [u64; 0]; +# nextln: $()This declaration is never used. + +# check: $()let mut b = [[u64; 1]; 1]; +# nextln: $()This declaration is never used. + +# check: $()a[0] = 1; +# nextln: $()Index out of bounds; the length is 0 but the index is 0. + +# check: $()b[0][1] = 1; +# nextln: $()Index out of bounds; the length is 1 but the index is 1. + +# check: $()b[1][0] = 1; +# nextln: $()Index out of bounds; the length is 1 but the index is 1. + +# check: $()a[0] = return; +# nextln: $()Index out of bounds; the length is 0 but the index is 0. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.lock new file mode 100644 index 00000000000..cc167580e1e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-FF35018BB7DF2595" + +[[package]] +name = "for_loop_error" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-FF35018BB7DF2595" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.toml new file mode 100644 index 00000000000..700b97ff4e3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "for_loop_error" +entry = "main.sw" + +[dependencies] +std = { path = "../../../reduced_std_libs/sway-lib-std-vec" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/src/main.sw new file mode 100644 index 00000000000..5c3f883c10d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/src/main.sw @@ -0,0 +1,12 @@ +script; + +fn main() { + let mut v : Vec = Vec::new(); + v.push(1); + v.push(2); + v.push(3); + for elem in v.iter() { + log(elem); + 3 + }; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/test.toml new file mode 100644 index 00000000000..e738a23e98e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/for_loop_error/test.toml @@ -0,0 +1,13 @@ +category = "fail" + +# check: $()3 +# nextln: $()Mismatched types. +# nextln: $()expected: () +# nextln: $()found: numeric. +# nextln: $()Implicit return must match up with block's type. + +# check: $()3 +# nextln: $()Mismatched types. +# nextln: $()expected: () +# nextln: $()found: numeric. +# nextln: $()A for loop's loop body cannot implicitly return a value. Try assigning it to a mutable variable declared outside of the loop instead. \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.lock new file mode 100644 index 00000000000..e319a387961 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-3A3AB9F18BDA9100" + +[[package]] +name = "parser_generic_turbo_fish_prefix" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-3A3AB9F18BDA9100" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.toml new file mode 100644 index 00000000000..22143b9b5eb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "parser_generic_turbo_fish_prefix" + +[dependencies] +std = { path = "../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/src/main.sw new file mode 100644 index 00000000000..af437cc603e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/src/main.sw @@ -0,0 +1,9 @@ +script; + +struct S { + x: T, +} + +fn main() { + let x: ::S:: = S::{x: 8}; +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/test.toml new file mode 100644 index 00000000000..cfb2e0f1a93 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/parser_generic_turbo_fish_prefix/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $()let x: ::S:: = S::{x: 8}; +# nextln: $()Expected `as`. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.lock new file mode 100644 index 00000000000..58699a84e7e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "core" +source = "path+from-root-722DFCAB6A209427" + +[[package]] +name = "self_return_type" +source = "member" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.toml new file mode 100644 index 00000000000..40a91e8b7b5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/Forc.toml @@ -0,0 +1,9 @@ +[project] +name = "self_return_type" +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +implicit-std = false + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/src/main.sw new file mode 100644 index 00000000000..659207244d6 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/src/main.sw @@ -0,0 +1,13 @@ +script; +trait MyTrait { + fn foo(self, other: Self) -> Self; +} +impl MyTrait for u8 { + fn foo(self, other: Self) -> self { + self + } +} +fn main() -> () { + let a = 1u8; + let _ = a.foo(2u8); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/test.toml new file mode 100644 index 00000000000..3bd3d799fb2 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_return_type/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $()fn foo(self, other: Self) -> self { +# nextln: $()Unknown type name "self". A self type with a similar name exists (notice the capitalization): `Self` \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_abi/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_abi/src/main.sw index 4486078f32b..ec8dab4f17b 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_abi/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/associated_const_abi/src/main.sw @@ -2,15 +2,12 @@ contract; abi A { const A_WITH_DEFAULT: u32 = 3; - // TODO: Uncomment this case when https://github.com/FuelLabs/sway/issues/6348 is fixed. - // const A_NO_DEFAULT: u32; + const A_NO_DEFAULT: u32; const COMMON_1: u32 = 5; - // TODO: Uncomment this version (without default) when https://github.com/FuelLabs/sway/issues/6348 is fixed. - // const COMMON_2: u32; - const COMMON_2: u32 = 111; // TODO: Remove this one in favor of the one without default. + const COMMON_2: u32; fn a_with_default() -> u32; - // fn a_no_default() -> u32; + fn a_no_default() -> u32; fn common_1() -> u32; fn common_2() -> u32; @@ -34,9 +31,7 @@ abi A { } abi B { - // TODO: Uncomment this version (without default) when https://github.com/FuelLabs/sway/issues/6348 is fixed. - // const COMMON_1: u32; - const COMMON_1: u32 = 111; // TODO: Remove this one in favor of the one without default. + const COMMON_1: u32; const COMMON_2: u32 = 7; fn common_1() -> u32; @@ -54,7 +49,7 @@ abi B { impl A for Contract { const A_WITH_DEFAULT: u32 = 13; - // const A_NO_DEFAULT: u32 = 133; + const A_NO_DEFAULT: u32 = 133; const COMMON_1: u32 = 15; const COMMON_2: u32 = 155; @@ -62,9 +57,9 @@ impl A for Contract { Self::A_WITH_DEFAULT } - // fn a_no_default() -> u32 { - // Self::A_NO_DEFAULT - // } + fn a_no_default() -> u32 { + Self::A_NO_DEFAULT + } fn common_1() -> u32 { Self::COMMON_1 @@ -92,14 +87,12 @@ impl B for Contract { fn test() { let a = abi(A, CONTRACT_ID); // TODO: Enable these asserts once these bugs are fixed: - // https://github.com/FuelLabs/sway/issues/6310 // https://github.com/FuelLabs/sway/issues/6306 - // https://github.com/FuelLabs/sway/issues/6348 - // assert_eq(13, a.a_with_default()); + assert_eq(13, a.a_with_default()); // assert_eq(13, a.a_implemented_with_default()); - // assert_eq(133, a.a_no_default()); + assert_eq(133, a.a_no_default()); // assert_eq(133, a.a_implemented_no_default()); // assert_eq(15, a.common_1()); @@ -110,9 +103,9 @@ fn test() { let b = abi(B, CONTRACT_ID); - // assert_eq(177, b.common_1()); + assert_eq(177, b.common_1()); // assert_eq(177, b.b_implemented_common_1()); - // assert_eq(17, b.common_2()); + assert_eq(17, b.common_2()); // assert_eq(17, b.b_implemented_common_2()); } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json index 2daeaffdc6a..71cf1f22ff7 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json @@ -62,82 +62,82 @@ { "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", "name": "BOOL", - "offset": 6896 + "offset": 7048 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "U8", - "offset": 7088 + "offset": 7240 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "ANOTHER_U8", - "offset": 6824 + "offset": 6976 }, { "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", "name": "U16", - "offset": 7032 + "offset": 7184 }, { "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", "name": "U32", - "offset": 7072 + "offset": 7224 }, { "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", "name": "U64", - "offset": 7080 + "offset": 7232 }, { "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", "name": "U256", - "offset": 7040 + "offset": 7192 }, { "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", "name": "B256", - "offset": 6864 + "offset": 7016 }, { "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", "name": "CONFIGURABLE_STRUCT", - "offset": 6984 + "offset": 7136 }, { "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", "name": "CONFIGURABLE_ENUM_A", - "offset": 6904 + "offset": 7056 }, { "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", "name": "CONFIGURABLE_ENUM_B", - "offset": 6944 + "offset": 7096 }, { "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", "name": "ARRAY_BOOL", - "offset": 6832 + "offset": 6984 }, { "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", "name": "ARRAY_U64", - "offset": 6840 + "offset": 6992 }, { "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", "name": "TUPLE_BOOL_U64", - "offset": 7016 + "offset": 7168 }, { "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", "name": "STR_4", - "offset": 7008 + "offset": 7160 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "NOT_USED", - "offset": 7000 + "offset": 7152 } ], "encodingVersion": "1", diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/snapshot.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/snapshot.toml index 784ff71e177..adfffceffad 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/snapshot.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/snapshot.toml @@ -1 +1 @@ -cmds = ["forc build --path {root} --release --ir final"] +cmds = ["forc build --path {root} --release --ir final --asm final"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/stdout.snap index 8e825421dd7..6af5fcc2f13 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode/stdout.snap @@ -2,7 +2,7 @@ source: test/tests/tests.rs snapshot_kind: text --- -> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode --release --ir final +> forc build --path test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode --release --ir final --asm final exit status: 0 output: Building test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_dedup_decode @@ -361,4 +361,194 @@ script { !128 = fn_call_path_span !0 235 236 !129 = (!127 !128) - Finished release [optimized + fuel] target(s) [744 B] in ??? +;; ASM: Final program +;; Program kind: Script +.program: +move $$tmp $pc +jmpf $zero i4 +DATA_SECTION_OFFSET[0..32] +DATA_SECTION_OFFSET[32..64] +CONFIGURABLES_OFFSET[0..32] +CONFIGURABLES_OFFSET[32..64] +lw $$ds $$tmp i1 +add $$ds $$ds $$tmp +cfei i16 ; allocate stack space for globals +addr $$arg0 data_Configurable_0; get pointer to configurable TUPLE default value +addi $$arg1 $zero i8 ; get length of configurable TUPLE default value +addi $$arg2 $ssp i0 ; get pointer to configurable TUPLE stack address +sub $$reta $pc $is ; get current instruction offset from instructions start ($is) +srli $$reta $$reta i2 ; get current instruction offset in 32-bit words +addi $$reta $$reta i4 ; set new return address +jmpf $zero i93 ; decode configurable TUPLE +addr $$arg0 data_Configurable_1; get pointer to configurable WRAPPED default value +addi $$arg1 $zero i8 ; get length of configurable WRAPPED default value +addi $$arg2 $ssp i8 ; get pointer to configurable WRAPPED stack address +sub $$reta $pc $is ; get current instruction offset from instructions start ($is) +srli $$reta $$reta i2 ; get current instruction offset in 32-bit words +addi $$reta $$reta i4 ; set new return address +jmpf $zero i85 ; decode configurable WRAPPED +move $$locbase $sp ; save locals base register for function __entry +cfei i360 ; allocate 360 bytes for locals and 0 slots for call arguments +addi $r6 $$locbase i320 ; get offset to local +sub $$reta $pc $is ; get current instruction offset from instructions start ($is) +srli $$reta $$reta i2 ; get current instruction offset in 32-bit words +addi $$reta $$reta i4 ; [call]: set new return address +jmpf $zero i140 ; [call]: call main_8 +move $r0 $$retv ; [call]: copy the return value +movi $r1 i1024 ; initialize constant into register +aloc $r1 +move $r1 $hp ; return value from ASM block with return register hp +addi $r2 $$locbase i112 ; get offset to local +sw $$locbase $r1 i14 ; store word +movi $r1 i1024 ; initialize constant into register +sw $$locbase $r1 i15 ; store word +sw $$locbase $zero i16 ; store word +movi $r1 i24 ; get data length for memory copy +mcp $$locbase $r2 $r1 ; copy memory +addi $r1 $$locbase i184 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r1 $$locbase $r2 ; copy memory +addi $r2 $$locbase i272 ; get offset to local +movi $r3 i24 ; get data length for memory copy +mcp $r2 $r1 $r3 ; copy memory +addi $r1 $$locbase i24 ; get offset to local +movi $r3 i24 ; get data length for memory copy +mcp $r1 $r2 $r3 ; copy memory +addi $r2 $$locbase i136 ; get offset to local +movi $r3 i24 ; get data length for memory copy +mcp $r2 $r1 $r3 ; copy memory +lw $r5 $$locbase i17 ; load word +lw $r4 $$locbase i18 ; load word +lw $r1 $$locbase i19 ; load word +movi $r2 i8 ; initialize constant into register +add $r2 $r1 $r2 +gt $r3 $r2 $r4 +jnzf $r3 $zero i1 +jmpf $zero i7 +movi $r3 i2 ; initialize constant into register +mul $r3 $r4 $r3 +movi $r4 i8 ; initialize constant into register +add $r4 $r3 $r4 +aloc $r4 +mcp $hp $r5 $r1 +move $r5 $hp ; return value from ASM block with return register hp +add $r1 $r5 $r1 +sw $r1 $r0 i0 ; store word +addi $r0 $$locbase i208 ; get offset to local +sw $$locbase $r5 i26 ; store word +sw $$locbase $r4 i27 ; store word +sw $$locbase $r2 i28 ; store word +addi $r1 $$locbase i48 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r1 $r0 $r2 ; copy memory +addi $r0 $$locbase i248 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r0 $r1 $r2 ; copy memory +addi $r1 $$locbase i336 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r1 $r0 $r2 ; copy memory +addi $r0 $$locbase i336 ; get offset to local +addi $r1 $$locbase i296 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r1 $r0 $r2 ; copy memory +addi $r0 $$locbase i72 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r0 $r1 $r2 ; copy memory +addi $r1 $$locbase i160 ; get offset to local +movi $r2 i24 ; get data length for memory copy +mcp $r1 $r0 $r2 ; copy memory +lw $r0 $$locbase i20 ; load word +addi $r1 $r1 i16 ; get offset to aggregate element +addi $r2 $$locbase i232 ; get offset to local +sw $$locbase $r0 i29 ; store word +addi $r0 $r2 i8 ; get offset to aggregate element +movi $r3 i8 ; get data length for memory copy +mcp $r0 $r1 $r3 ; copy memory +addi $r0 $$locbase i96 ; get offset to local +movi $r1 i16 ; get data length for memory copy +mcp $r0 $r2 $r1 ; copy memory +movi $r1 i16 ; get data length for memory copy +mcp $r6 $r0 $r1 ; copy memory +lw $r0 $r6 i1 ; load size of returned slice +lw $r6 $r6 i0 ; load pointer to returned slice +retd $r6 $r0 +pshl i15 ; save registers 16..40 +pshh i524288 ; save registers 40..64 +move $$locbase $sp ; save locals base register for function abi_decode_in_place_0 +cfei i24 ; allocate 24 bytes for locals and 0 slots for call arguments +move $r0 $$arg0 ; save argument 0 (ptr) +move $r1 $$arg1 ; save argument 1 (len) +move $r2 $$arg2 ; save argument 2 (target) +move $r3 $$reta ; save return address +move $$arg0 $r0 ; [call]: pass argument 0 +move $$arg1 $r1 ; [call]: pass argument 1 +move $$arg2 $$locbase ; [call]: pass argument 2 +sub $$reta $pc $is ; get current instruction offset from instructions start ($is) +srli $$reta $$reta i2 ; get current instruction offset in 32-bit words +addi $$reta $$reta i4 ; [call]: set new return address +jmpf $zero i19 ; [call]: call from_parts_1 +lw $r0 $$retv i0 ; load word +sw $$locbase $r0 i1 ; store word +addi $r0 $$locbase i8 ; get offset to local +move $$arg0 $r0 ; [call]: pass argument 0 +sub $$reta $pc $is ; get current instruction offset from instructions start ($is) +srli $$reta $$reta i2 ; get current instruction offset in 32-bit words +addi $$reta $$reta i4 ; [call]: set new return address +jmpf $zero i23 ; [call]: call abi_decode_3 +move $r0 $$retv ; [call]: copy the return value +sw $$locbase $r0 i2 ; store word +addi $r0 $$locbase i16 ; get offset to local +movi $r1 i8 ; initialize constant into register +mcp $r2 $r0 $r1 ; mcp target temp size +move $$retv $zero ; set return value +cfsi i24 ; free 24 bytes for locals and 0 slots for extra call arguments +move $$reta $r3 ; restore return address +poph i524288 ; restore registers 40..64 +popl i15 ; restore registers 16..40 +jmp $$reta ; return from call +pshl i7 ; save registers 16..40 +pshh i524288 ; save registers 40..64 +move $$locbase $sp ; save locals base register for function from_parts_1 +move $r0 $$arg0 ; save argument 0 (ptr) +move $r1 $$arg2 ; save argument 2 (__ret_value) +move $r2 $$reta ; save return address +sw $r1 $r0 i0 ; store word +move $$retv $r1 ; set return value +move $$reta $r2 ; restore return address +poph i524288 ; restore registers 40..64 +popl i7 ; restore registers 16..40 +jmp $$reta ; return from call +pshl i31 ; save registers 16..40 +pshh i524288 ; save registers 40..64 +move $$locbase $sp ; save locals base register for function abi_decode_3 +move $r0 $$arg0 ; save argument 0 (buffer) +move $r1 $$reta ; save return address +lw $r2 $r0 i0 ; load word +lw $r2 $r2 i0 ; lw val ptr i0 +lw $r3 $r0 i0 ; load word +movi $r4 i8 ; initialize constant into register +add $r3 $r3 $r4 +sw $r0 $r3 i0 ; store word +move $$retv $r2 ; set return value +move $$reta $r1 ; restore return address +poph i524288 ; restore registers 40..64 +popl i31 ; restore registers 16..40 +jmp $$reta ; return from call +pshl i7 ; save registers 16..40 +pshh i524288 ; save registers 40..64 +move $$locbase $sp ; save locals base register for function main_8 +move $r2 $$reta ; save return address +lw $r0 $ssp i1 ; load word +lw $r1 $ssp i0 ; load word +add $r0 $r0 $r1 +move $$retv $r0 ; set return value +move $$reta $r2 ; restore return address +poph i524288 ; restore registers 40..64 +popl i7 ; restore registers 16..40 +jmp $$reta ; return from call +.data: +data__0 .bytes[8] 00 00 00 00 00 00 00 02 ........ +data__1 .bytes[8] 00 00 00 00 00 00 00 01 ........ + + + Finished release [optimized + fuel] target(s) [752 B] in ??? diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock new file mode 100644 index 00000000000..688eb152964 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "configurable_tests" +source = "member" +dependencies = ["std"] + +[[package]] +name = "core" +source = "path+from-root-CEAD1EF3DC39BB76" + +[[package]] +name = "std" +source = "path+from-root-CEAD1EF3DC39BB76" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml new file mode 100644 index 00000000000..7fff3f8b575 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "configurable_tests" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json new file mode 100644 index 00000000000..096b1d487c9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json @@ -0,0 +1,284 @@ +{ + "configurables": [ + { + "configurableType": { + "name": "", + "type": 5, + "typeArguments": null + }, + "name": "BOOL", + "offset": 3392 + }, + { + "configurableType": { + "name": "", + "type": 13, + "typeArguments": null + }, + "name": "U8", + "offset": 3528 + }, + { + "configurableType": { + "name": "", + "type": 13, + "typeArguments": null + }, + "name": "ANOTHER_U8", + "offset": 3320 + }, + { + "configurableType": { + "name": "", + "type": 9, + "typeArguments": null + }, + "name": "U16", + "offset": 3472 + }, + { + "configurableType": { + "name": "", + "type": 11, + "typeArguments": null + }, + "name": "U32", + "offset": 3512 + }, + { + "configurableType": { + "name": "", + "type": 11, + "typeArguments": null + }, + "name": "U64", + "offset": 3520 + }, + { + "configurableType": { + "name": "", + "type": 10, + "typeArguments": null + }, + "name": "U256", + "offset": 3480 + }, + { + "configurableType": { + "name": "", + "type": 4, + "typeArguments": null + }, + "name": "B256", + "offset": 3360 + }, + { + "configurableType": { + "name": "", + "type": 8, + "typeArguments": [] + }, + "name": "CONFIGURABLE_STRUCT", + "offset": 3432 + }, + { + "configurableType": { + "name": "", + "type": 6, + "typeArguments": [] + }, + "name": "CONFIGURABLE_ENUM_A", + "offset": 3400 + }, + { + "configurableType": { + "name": "", + "type": 6, + "typeArguments": [] + }, + "name": "CONFIGURABLE_ENUM_B", + "offset": 3416 + }, + { + "configurableType": { + "name": "", + "type": 2, + "typeArguments": null + }, + "name": "ARRAY_BOOL", + "offset": 3328 + }, + { + "configurableType": { + "name": "", + "type": 3, + "typeArguments": null + }, + "name": "ARRAY_U64", + "offset": 3336 + }, + { + "configurableType": { + "name": "", + "type": 1, + "typeArguments": null + }, + "name": "TUPLE_BOOL_U64", + "offset": 3456 + }, + { + "configurableType": { + "name": "", + "type": 7, + "typeArguments": null + }, + "name": "STR_4", + "offset": 3448 + } + ], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + }, + { + "components": [ + { + "name": "__tuple_element", + "type": 5, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 12, + "typeArguments": null + } + ], + "type": "(_, _)", + "typeId": 1, + "typeParameters": null + }, + { + "components": [ + { + "name": "__array_element", + "type": 5, + "typeArguments": null + } + ], + "type": "[_; 3]", + "typeId": 2, + "typeParameters": null + }, + { + "components": [ + { + "name": "__array_element", + "type": 12, + "typeArguments": null + } + ], + "type": "[_; 3]", + "typeId": 3, + "typeParameters": null + }, + { + "components": null, + "type": "b256", + "typeId": 4, + "typeParameters": null + }, + { + "components": null, + "type": "bool", + "typeId": 5, + "typeParameters": null + }, + { + "components": [ + { + "name": "A", + "type": 5, + "typeArguments": null + }, + { + "name": "B", + "type": 12, + "typeArguments": null + } + ], + "type": "enum ConfigurableEnum", + "typeId": 6, + "typeParameters": null + }, + { + "components": null, + "type": "str[4]", + "typeId": 7, + "typeParameters": null + }, + { + "components": [ + { + "name": "a", + "type": 5, + "typeArguments": null + }, + { + "name": "b", + "type": 12, + "typeArguments": null + } + ], + "type": "struct ConfigurableStruct", + "typeId": 8, + "typeParameters": null + }, + { + "components": null, + "type": "u16", + "typeId": 9, + "typeParameters": null + }, + { + "components": null, + "type": "u256", + "typeId": 10, + "typeParameters": null + }, + { + "components": null, + "type": "u32", + "typeId": 11, + "typeParameters": null + }, + { + "components": null, + "type": "u64", + "typeId": 12, + "typeParameters": null + }, + { + "components": null, + "type": "u8", + "typeId": 13, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json new file mode 100644 index 00000000000..2daeaffdc6a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json @@ -0,0 +1,228 @@ +{ + "concreteTypes": [ + { + "concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "type": "()" + }, + { + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "metadataTypeId": 0, + "type": "(bool, u64)" + }, + { + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "metadataTypeId": 1, + "type": "[bool; 3]" + }, + { + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "metadataTypeId": 2, + "type": "[u64; 3]" + }, + { + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "type": "b256" + }, + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "type": "bool" + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "metadataTypeId": 3, + "type": "enum ConfigurableEnum" + }, + { + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "type": "str[4]" + }, + { + "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", + "metadataTypeId": 4, + "type": "struct ConfigurableStruct" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "type": "u16" + }, + { + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "type": "u256" + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "type": "u32" + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "type": "u8" + } + ], + "configurables": [ + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "name": "BOOL", + "offset": 6896 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "U8", + "offset": 7088 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "ANOTHER_U8", + "offset": 6824 + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "name": "U16", + "offset": 7032 + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "name": "U32", + "offset": 7072 + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "name": "U64", + "offset": 7080 + }, + { + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "name": "U256", + "offset": 7040 + }, + { + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "name": "B256", + "offset": 6864 + }, + { + "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", + "name": "CONFIGURABLE_STRUCT", + "offset": 6984 + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "name": "CONFIGURABLE_ENUM_A", + "offset": 6904 + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "name": "CONFIGURABLE_ENUM_B", + "offset": 6944 + }, + { + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "name": "ARRAY_BOOL", + "offset": 6832 + }, + { + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "name": "ARRAY_U64", + "offset": 6840 + }, + { + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "name": "TUPLE_BOOL_U64", + "offset": 7016 + }, + { + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "name": "STR_4", + "offset": 7008 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "NOT_USED", + "offset": 7000 + } + ], + "encodingVersion": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ], + "loggedTypes": [], + "messagesTypes": [], + "metadataTypes": [ + { + "components": [ + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "__tuple_element", + "typeId": 5 + } + ], + "metadataTypeId": 0, + "type": "(_, _)" + }, + { + "components": [ + { + "name": "__array_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + ], + "metadataTypeId": 1, + "type": "[_; 3]" + }, + { + "components": [ + { + "name": "__array_element", + "typeId": 5 + } + ], + "metadataTypeId": 2, + "type": "[_; 3]" + }, + { + "components": [ + { + "name": "A", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "B", + "typeId": 5 + }, + { + "name": "C", + "typeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + } + ], + "metadataTypeId": 3, + "type": "enum ConfigurableEnum" + }, + { + "components": [ + { + "name": "a", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "b", + "typeId": 5 + } + ], + "metadataTypeId": 4, + "type": "struct ConfigurableStruct" + }, + { + "metadataTypeId": 5, + "type": "u64" + } + ], + "programType": "script", + "specVersion": "1" +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw new file mode 100644 index 00000000000..a0e5d11a345 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw @@ -0,0 +1,83 @@ +script; + +use std::hash::*; + +struct ConfigurableStruct { + a: bool, + b: u64, +} + +enum ConfigurableEnum { + A: bool, + B: u64, + C: b256 +} + +impl core::ops::Eq for ConfigurableEnum { + fn eq(self, other: ConfigurableEnum) -> bool { + match (self, other) { + (ConfigurableEnum::A(inner1), ConfigurableEnum::A(inner2)) => inner1 == inner2, + (ConfigurableEnum::B(inner1), ConfigurableEnum::B(inner2)) => inner1 == inner2, + _ => false, + } + } +} + +type AnotherU8 = u8; + +configurable { + BOOL: bool = true, + U8: u8 = 1, + ANOTHER_U8: AnotherU8 = 3, + U16: u16 = 2, + U32: u32 = 3, + U64: u32 = 4, + U256: u256 = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256, + B256: b256 = 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + CONFIGURABLE_STRUCT: ConfigurableStruct = ConfigurableStruct { a: true, b: 5 }, + CONFIGURABLE_ENUM_A: ConfigurableEnum = ConfigurableEnum::A(true), + CONFIGURABLE_ENUM_B: ConfigurableEnum = ConfigurableEnum::B(12), + ARRAY_BOOL: [bool; 3] = [true, false, true], + ARRAY_U64: [u64; 3] = [9, 8, 7], + TUPLE_BOOL_U64: (bool, u64) = (true, 11), + STR_4: str[4] = __to_str_array("abcd"), + + NOT_USED: u8 = 1 +} + +fn main() { +} + +#[test] +fn t() { + assert(BOOL == true); + assert(U8 == 1); + assert(ANOTHER_U8 == 3); + assert(U16 == 2); + assert(U32 == 3); + assert(U64 == 4); + assert(U256 == 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256); + assert(B256 == 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB); + assert(CONFIGURABLE_STRUCT.a == true); + assert(CONFIGURABLE_STRUCT.b == 5); + assert(CONFIGURABLE_ENUM_A == ConfigurableEnum::A(true)); + assert(CONFIGURABLE_ENUM_B == ConfigurableEnum::B(12)); + assert(ARRAY_BOOL[0] == true); + assert(ARRAY_BOOL[1] == false); + assert(ARRAY_BOOL[2] == true); + assert(ARRAY_U64[0] == 9); + assert(ARRAY_U64[1] == 8); + assert(ARRAY_U64[2] == 7); + assert(TUPLE_BOOL_U64.0 == true); + assert(TUPLE_BOOL_U64.1 == 11); + assert(sha256_str_array(STR_4) == sha256("abcd")); + + // Assert address do not change + let addr_1 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + let addr_2 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + assert(addr_1 == addr_2); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml new file mode 100644 index 00000000000..0f3f6d7e866 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml @@ -0,0 +1 @@ +category = "unit_tests_pass" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/Forc.toml index a06f647ef58..79e3031d8bd 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/Forc.toml @@ -5,4 +5,4 @@ license = "Apache-2.0" name = "main_args_various_types" [dependencies] -std = { path = "../../../../../reduced_std_libs/sway-lib-std-assert" } +std = { path = "../../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/json_abi_oracle_new_encoding.json index 6d95b04580f..ee7cb119623 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/json_abi_oracle_new_encoding.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/json_abi_oracle_new_encoding.json @@ -6,8 +6,8 @@ "type": "[(struct OpName, enum SignedNum); 2]" }, { - "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0", - "type": "u64" + "concreteTypeId": "d489af6a272dc02bb98c58446e5250345c65721ecfef3f43b26c263819725668", + "type": "generic AliasedS" } ], "configurables": [], @@ -22,7 +22,7 @@ } ], "name": "main", - "output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + "output": "d489af6a272dc02bb98c58446e5250345c65721ecfef3f43b26c263819725668" } ], "loggedTypes": [ @@ -61,11 +61,11 @@ "components": [ { "name": "Positive", - "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + "typeId": 5 }, { "name": "Negative", - "typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + "typeId": 5 } ], "metadataTypeId": 2, @@ -84,6 +84,10 @@ ], "metadataTypeId": 4, "type": "struct OpName" + }, + { + "metadataTypeId": 5, + "type": "u64" } ], "programType": "script", diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/main.sw index cf8cb142ee1..55cf6bae4d3 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/main.sw @@ -1,5 +1,9 @@ script; +mod types; + +use types::{S as AliasedS}; + fn eq_str_3(a: str[3], b: str) -> bool { let ptr_b = b.as_ptr(); asm(a: a, b: ptr_b, len: 3, r) { @@ -17,7 +21,7 @@ struct OpName { val: str[3] } -fn main(ops: [(OpName, SignedNum); 2]) -> u64 { +fn main(ops: [(OpName, SignedNum); 2]) -> AliasedS { __log(ops); assert(eq_str_3(ops[0].0.val, "set")); assert(match ops[0].1 { @@ -31,5 +35,7 @@ fn main(ops: [(OpName, SignedNum); 2]) -> u64 { _ => revert(2), } == 1); - 1 + AliasedS{ + v: 1 + } } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/types.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/types.sw new file mode 100644 index 00000000000..8e5e9741521 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/main_args/main_args_various_types/src/types.sw @@ -0,0 +1,5 @@ +library; + +pub struct S { + pub v: u64 +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.lock new file mode 100644 index 00000000000..e52d486e352 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-1F6D5E67DD060824" + +[[package]] +name = "mutability_of_references_memcpy_bug" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-1F6D5E67DD060824" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.toml new file mode 100644 index 00000000000..66e2ad87de3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "mutability_of_references_memcpy_bug" + +[dependencies] +std = { path = "../../../../../reduced_std_libs/sway-lib-std-assert" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/lib.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/lib.sw new file mode 100644 index 00000000000..88c1bfa547f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/lib.sw @@ -0,0 +1,19 @@ +library; + +#[inline(never)] +pub fn unit(b: u256) -> u256 { + b +} + +#[inline(never)] +pub fn weird(_b: u256) { + let mut b = _b; // b = _b = 2 + + log(b); + + let mut b_tmp = b; // b_tmp = b = 2 + + b = 0x03u256; // b = a = 1 + + assert(unit(b_tmp) == 0x02u256); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/main.sw new file mode 100644 index 00000000000..91454432859 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/src/main.sw @@ -0,0 +1,27 @@ +contract; + +pub mod lib; + +use ::lib::weird; + +abi MyContract { + fn test_function(b: u256) -> u256; +} + +impl MyContract for Contract { + + fn test_function(_b: u256) -> u256 { + weird(_b); + 0x00u256 + } +} + + + +#[test] +fn test() { + let caller = abi(MyContract, CONTRACT_ID); + let b = 0x02u256; + + let _ = caller.test_function(b); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/test.toml new file mode 100644 index 00000000000..ad1782559e7 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/mutability_of_references_memcpy_bug/test.toml @@ -0,0 +1,3 @@ +category = "unit_tests_pass" +validate_abi = false +expected_warnings = 0 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json index 49a75d8d5c1..c58838960ad 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json @@ -9,7 +9,7 @@ { "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", "name": "SOME_U256", - "offset": 816 + "offset": 872 } ], "encodingVersion": "1", diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw index ffc9dcbe016..8d0a9e93e2c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/array_of_structs_caller/src/main.sw @@ -6,7 +6,7 @@ use std::hash::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x14ed3cd06c2947248f69d54bfa681fe40d26267be84df7e19e253622b7921bbe; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xdf53a7533a12c5ee3df459fc424f51807fc9740f13080a725f5f66408ede1186; // AUTO-CONTRACT-ID ../../test_contracts/array_of_structs_contract --release +const CONTRACT_ID = 0xe0c85555f3d94e58cc23830deb6dc193e1f173761a4fc5d56d3e07a5a1db0378; // AUTO-CONTRACT-ID ../../test_contracts/array_of_structs_contract --release fn get_address() -> Option { Some(CONTRACT_ID.into()) diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/src/main.sw index 5459da78f0c..6da541af0b8 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/src/main.sw @@ -9,12 +9,12 @@ use test_fuel_coin_abi::*; #[cfg(experimental_new_encoding = false)] const FUEL_COIN_CONTRACT_ID = 0xec2277ebe007ade87e3d797c3b1e070dcd542d5ef8f038b471f262ef9cebc87c; #[cfg(experimental_new_encoding = true)] -const FUEL_COIN_CONTRACT_ID = 0xf2fecff29038dab2ef571397ea5507359265c9154608e7de36ccbea20ed5c8aa; +const FUEL_COIN_CONTRACT_ID = 0xcdb572031a0779e09beea1e93e21630d104766c90cc8ce90c8dfd0abdd0ce5b2; // AUTO-CONTRACT-ID ../../test_contracts/test_fuel_coin_contract --release #[cfg(experimental_new_encoding = false)] const BALANCE_CONTRACT_ID = 0xf6cd545152ac83225e8e7df2efb5c6fa6e37bc9b9e977b5ea8103d28668925df; #[cfg(experimental_new_encoding = true)] -const BALANCE_CONTRACT_ID = 0xaa4e3d9c953790384f76dcad07e21d6973ae8df79432e48bbed76ccb6437a9a7; // AUTO-CONTRACT-ID ../../test_contracts/balance_test_contract --release +const BALANCE_CONTRACT_ID = 0xb770fa56f665d6fbdbdceecce21b7b61878f650981ac7f21c052613015937034; // AUTO-CONTRACT-ID ../../test_contracts/balance_test_contract --release fn main() -> bool { let default_gas = 1_000_000_000_000; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/test.toml index a653725436b..ebf2e18c854 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/asset_ops_test/test.toml @@ -1,5 +1,8 @@ category = "run_on_node" expected_result = { action = "result", value = 1 } expected_result_new_encoding = { action = "return_data", value = "01" } -contracts = ["should_pass/test_contracts/balance_test_contract", "should_pass/test_contracts/test_fuel_coin_contract"] -unsupported_profiles = ["debug"] \ No newline at end of file +contracts = [ + "should_pass/test_contracts/balance_test_contract", + "should_pass/test_contracts/test_fuel_coin_contract", +] +unsupported_profiles = ["debug"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/bal_opcode/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/bal_opcode/src/main.sw index 365a489a3dd..638541084b4 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/bal_opcode/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/bal_opcode/src/main.sw @@ -5,7 +5,7 @@ use balance_test_abi::BalanceTest; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xf6cd545152ac83225e8e7df2efb5c6fa6e37bc9b9e977b5ea8103d28668925df; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xaa4e3d9c953790384f76dcad07e21d6973ae8df79432e48bbed76ccb6437a9a7; // AUTO-CONTRACT-ID ../../test_contracts/balance_test_contract --release +const CONTRACT_ID = 0xb770fa56f665d6fbdbdceecce21b7b61878f650981ac7f21c052613015937034; // AUTO-CONTRACT-ID ../../test_contracts/balance_test_contract --release fn main() -> bool { let balance_test_contract = abi(BalanceTest, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_abi_with_tuples/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_abi_with_tuples/src/main.sw index b0a18868637..9a078ca2bbc 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_abi_with_tuples/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_abi_with_tuples/src/main.sw @@ -6,7 +6,7 @@ use abi_with_tuples::{MyContract, Location, Person}; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xfdc14550c8aee742cd556d0ab7f378b7be0d3b1e6e086c097352e94590d4ed02; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xe21bdb5e019c073978f5bd6eee1339a0e68ab38b913d8a00ff1286e9e5eb894d; // AUTO-CONTRACT-ID ../../test_contracts/abi_with_tuples_contract --release +const CONTRACT_ID = 0xcc679c89d2950879d4f8fb3b99770f93dfde3335fff574cbf0588691fbcefde3; // AUTO-CONTRACT-ID ../../test_contracts/abi_with_tuples_contract --release fn main() -> bool { let the_abi = abi(MyContract, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw index d7218234e92..d6a56b1b07e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw @@ -4,7 +4,7 @@ use basic_storage_abi::{BasicStorage, Quad}; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x94db39f409a31b9f2ebcadeea44378e419208c20de90f5d8e1e33dc1523754cb; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x27857d650234acd05b5fa9a9bc34abf76fa66d2e6ce8c8b24416805b75005bd0; // AUTO-CONTRACT-ID ../../test_contracts/basic_storage --release +const CONTRACT_ID = 0x52954afa6e6020d78b4daa89bc2bfceb31a7b85a97f55a49946f67d4e03b66c0; // AUTO-CONTRACT-ID ../../test_contracts/basic_storage --release fn main() -> u64 { let addr = abi(BasicStorage, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw index eab00cc7b7c..7dc873bf458 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_contract_with_type_aliases/src/main.sw @@ -5,7 +5,7 @@ use contract_with_type_aliases_abi::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x0cbeb6efe3104b460be769bdc4ea101ebf16ccc16f2d7b667ec3e1c7f5ce35b5; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x8b400005d6178d7ceaccac502a021abad28a899ab8692099c0bfa5e70853d573; // AUTO-CONTRACT-ID ../../test_contracts/contract_with_type_aliases --release +const CONTRACT_ID = 0x5f2d9be937ca4a0de3404d1165d0faed6f8c440ab9ba7c6c046904e28761f7f7; // AUTO-CONTRACT-ID ../../test_contracts/contract_with_type_aliases --release fn main() { let caller = abi(MyContract, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw index 28089f44fa3..28218e97054 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw @@ -6,7 +6,7 @@ use dynamic_contract_call::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xd1b4047af7ef111c023ab71069e01dc2abfde487c0a0ce1268e4f447e6c6e4c2; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xe1dbd25c5d2ccb547ce1f592d04a407dec80cc715255c358dfb32ccf9ca0f926; // AUTO-CONTRACT-ID ../../test_contracts/increment_contract --release +const CONTRACT_ID = 0xc2694e6a397883ae59ea2e807c0ba3714865d0c4bf16571893d632b3bd7ec6f7; // AUTO-CONTRACT-ID ../../test_contracts/increment_contract --release fn main() -> bool { let the_abi = abi(Incrementor, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw index 06673118a96..b8060557890 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_storage_enum/src/main.sw @@ -5,7 +5,7 @@ use storage_enum_abi::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xc601d11767195485a6654d566c67774134668863d8c797a8c69e8778fb1f89e9; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x47084a8c1eb453b655842e1b3067ee1f0d9081052bd74ac606efa6ae9a56d901; // AUTO-CONTRACT-ID ../../test_contracts/storage_enum_contract --release +const CONTRACT_ID = 0xd9247267148944e3f541347c65705af8798b257e8acda1e89e220c4bdf9298fc; // AUTO-CONTRACT-ID ../../test_contracts/storage_enum_contract --release fn main() -> u64 { let caller = abi(StorageEnum, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_auth_test/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_auth_test/src/main.sw index ae8dc25bd57..ea5fb5c74e5 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_auth_test/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_auth_test/src/main.sw @@ -5,7 +5,7 @@ use auth_testing_abi::AuthTesting; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xc2eec20491b53aab7232cbd27c31d15417b4e9daf0b89c74cc242ef1295f681f; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0x507e5e609fdb2a085da8d681519b6bb6b738f2beb0321600c1e3497db3114a37; // AUTO-CONTRACT-ID ../../test_contracts/auth_testing_contract --release +const CONTRACT_ID = 0xcdc67c314d13752e5c3e10aa1de6615e1ec6864e0461317b5f26eef3320a9a8e; // AUTO-CONTRACT-ID ../../test_contracts/auth_testing_contract --release // should be false in the case of a script fn main() -> bool { diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_context_test/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_context_test/src/main.sw index 4a0c2e86a63..64dec655d14 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_context_test/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/caller_context_test/src/main.sw @@ -6,7 +6,7 @@ use context_testing_abi::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x6054c11cda000f5990373a4d61929396165be4dfdd61d5b7bd26da60ab0d8577; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xb80ada1b7018cd6cb45dac7e0dfc1b17fc2931439facc55bd784523410e31449; // AUTO-CONTRACT-ID ../../test_contracts/context_testing_contract --release +const CONTRACT_ID = 0xecf511a0e2f4022c17413625d69c390d51d0740424d6886cbbe9ca60acd0606b; // AUTO-CONTRACT-ID ../../test_contracts/context_testing_contract --release fn main() -> bool { let gas: u64 = u64::max(); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/nested_struct_args_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/nested_struct_args_caller/src/main.sw index 69da3b33161..1364b7aa4b4 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/nested_struct_args_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/nested_struct_args_caller/src/main.sw @@ -5,7 +5,7 @@ use nested_struct_args_abi::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0xe63d33a1b3a6903808b379f6a41a72fa8a370e8b76626775e7d9d2f9c4c5da40; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xc4f40cab476f1e705db3cdb22367eb20ebb180213801918db9b2030db56a0542; // AUTO-CONTRACT-ID ../../test_contracts/nested_struct_args_contract --release +const CONTRACT_ID = 0x27e6a749a1773f65f850b36e4e61afdbfe72ba4189cd518c16e06cf0c04a1fa9; // AUTO-CONTRACT-ID ../../test_contracts/nested_struct_args_contract --release fn main() -> bool { let caller = abi(NestedStructArgs, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw index 7850cbec2ad..181a7fd980a 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw @@ -6,7 +6,7 @@ use std::hash::*; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x3bc28acd66d327b8c1b9624c1fabfc07e9ffa1b5d71c2832c3bfaaf8f4b805e9; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xa8eb4bf19964abcc5fdeac8daa1e5816326f728e9883f0287cf4c404527291a8; // AUTO-CONTRACT-ID ../../test_contracts/storage_access_contract --release +const CONTRACT_ID = 0x03ad85f37b515247a0481ba4d1448a5592b8684312e7f2626d4403ab9fec2acc; // AUTO-CONTRACT-ID ../../test_contracts/storage_access_contract --release fn main() -> bool { let caller = abi(StorageAccess, CONTRACT_ID); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.lock new file mode 100644 index 00000000000..efaec2f6f2a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-2A7B279266ACFF97" + +[[package]] +name = "issue_6335_repro" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-2A7B279266ACFF97" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.toml new file mode 100644 index 00000000000..5f7f8ed6230 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/Forc.toml @@ -0,0 +1,9 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +implicit-std = false +license = "Apache-2.0" +name = "issue_6335_repro" + +[dependencies] +std = { path = "../../../../reduced_std_libs/sway-lib-std-conversions" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/src/main.sw new file mode 100644 index 00000000000..36c95885603 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/src/main.sw @@ -0,0 +1,25 @@ +contract; + +use std::bytes::*; + +abi MyAbi { + fn test() -> u64; +} + +abi FakeAbi { + fn test() -> Bytes; +} + +impl MyAbi for Contract { + fn test() -> u64 { + 64 + } +} + +#[test] +fn test() { + let caller = abi(FakeAbi, CONTRACT_ID); + let res = caller.test(); + assert(res.len() == 64); + let s: str[30] = abi_decode(res.as_raw_slice()); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/test.toml new file mode 100644 index 00000000000..526e7df8fdb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_6335_repro/test.toml @@ -0,0 +1,4 @@ +category = "compile" +validate_abi = false +validate_storage_slots = false +expected_warnings = 0 diff --git a/test/src/in_language_tests/test_programs/bytes_inline_tests/src/main.sw b/test/src/in_language_tests/test_programs/bytes_inline_tests/src/main.sw index 65597943ef7..e32d25da40b 100644 --- a/test/src/in_language_tests/test_programs/bytes_inline_tests/src/main.sw +++ b/test/src/in_language_tests/test_programs/bytes_inline_tests/src/main.sw @@ -779,6 +779,7 @@ fn bytes_append_to_empty() { }; } +#[test()] fn bytes_append_self() { let (mut bytes, a, b, c) = setup(); assert(bytes.len() == 3); @@ -797,6 +798,7 @@ fn bytes_append_self() { assert(bytes.get(5).unwrap() == c); } +#[test()] fn bytes_append_empty_self() { let mut empty_bytes = Bytes::new(); diff --git a/test/src/in_language_tests/test_programs/math_inline_tests/src/main.sw b/test/src/in_language_tests/test_programs/math_inline_tests/src/main.sw index c2641d71272..7a320860c83 100644 --- a/test/src/in_language_tests/test_programs/math_inline_tests/src/main.sw +++ b/test/src/in_language_tests/test_programs/math_inline_tests/src/main.sw @@ -1,5 +1,7 @@ library; +mod modulo; + use std::{flags::{disable_panic_on_overflow, disable_panic_on_unsafe_math}, registers::flags}; #[test] diff --git a/test/src/in_language_tests/test_programs/math_inline_tests/src/modulo.sw b/test/src/in_language_tests/test_programs/math_inline_tests/src/modulo.sw new file mode 100644 index 00000000000..43187e0b69d --- /dev/null +++ b/test/src/in_language_tests/test_programs/math_inline_tests/src/modulo.sw @@ -0,0 +1,147 @@ +library; + +use std::flags::disable_panic_on_unsafe_math; + +// u8 +#[test] +pub fn u8_modulo() { + let u8_max = u8::max(); + + assert(0u8 % 1u8 == 0u8); + assert(0u8 % 2u8 == 0u8); + assert(1u8 % 1u8 == 0u8); + assert(1u8 % 2u8 == 1u8); + + assert(u8_max % 1u8 == 0u8); + assert(u8_max % 2u8 == 1u8); + assert(u8_max % u8_max == 0u8); + assert(254u8 % u8_max == 254u8); +} + +#[test(should_revert)] +pub fn u8_modulo_panic_on_undefined_math() { + log(1u8 % 0u8); +} + +#[test] +pub fn u8_modulo_unsafe_math() { + disable_panic_on_unsafe_math(); + assert(0u8 % 0u8 == 0u8); + assert(1u8 % 0u8 == 0u8); + assert(u8::max() % 0u8 == 0u8); +} + +// u16 +#[test] +pub fn u16_modulo() { + let u16_max = u16::max(); + + assert(0u16 % 1u16 == 0u16); + assert(0u16 % 2u16 == 0u16); + assert(1u16 % 1u16 == 0u16); + assert(1u16 % 2u16 == 1u16); + + assert(u16_max % 1u16 == 0u16); + assert(u16_max % 2u16 == 1u16); + assert(u16_max % u16_max == 0u16); + assert((u16_max - 1u16) % u16_max == (u16_max - 1u16)); +} + +#[test(should_revert)] +pub fn u16_modulo_panic_on_undefined_math() { + log(1u16 % 0u16); +} + +#[test] +pub fn u16_modulo_unsafe_math() { + disable_panic_on_unsafe_math(); + assert(0u16 % 0u16 == 0u16); + assert(1u16 % 0u16 == 0u16); + assert(u16::max() % 0u16 == 0u16); +} + +// u32 +#[test] +pub fn u32_modulo() { + let u32_max = u32::max(); + + assert(0u32 % 1u32 == 0u32); + assert(0u32 % 2u32 == 0u32); + assert(1u32 % 1u32 == 0u32); + assert(1u32 % 2u32 == 1u32); + + assert(u32_max % 1u32 == 0u32); + assert(u32_max % 2u32 == 1u32); + assert(u32_max % u32_max == 0u32); + assert((u32_max - 1u32) % u32_max == (u32_max - 1u32)); +} + +#[test(should_revert)] +pub fn u32_modulo_panic_on_undefined_math() { + log(1u32 % 0u32); +} + +#[test] +pub fn u32_modulo_unsafe_math() { + disable_panic_on_unsafe_math(); + assert(0u32 % 0u32 == 0u32); + assert(1u32 % 0u32 == 0u32); + assert(u32::max() % 0u32 == 0u32); +} + +// u64 +#[test] +pub fn u64_modulo() { + let u64_max = u64::max(); + + assert(0u64 % 1u64 == 0u64); + assert(0u64 % 2u64 == 0u64); + assert(1u64 % 1u64 == 0u64); + assert(1u64 % 2u64 == 1u64); + + assert(u64_max % 1u64 == 0u64); + assert(u64_max % 2u64 == 1u64); + assert(u64_max % u64_max == 0u64); + assert((u64_max - 1u64) % u64_max == (u64_max - 1u64)); +} + +#[test(should_revert)] +pub fn u64_modulo_panic_on_undefined_math() { + log(1u64 % 0u64); +} + +#[test] +pub fn u64_modulo_unsafe_math() { + disable_panic_on_unsafe_math(); + assert(0u64 % 0u64 == 0u64); + assert(1u64 % 0u64 == 0u64); + assert(u64::max() % 0u64 == 0u64); +} + +// u256 +#[test] +pub fn u256_modulo() { + let u256_max = u256::max(); + + assert(0x0u256 % 0x1u256 == 0x0u256); + assert(0x1u256 % 0x1u256 == 0x0u256); + assert(0x1u256 % 0x2u256 == 0x1u256); + + assert(u256_max % 0x1u256 == 0x0u256); + assert(u256_max % 0x2u256 == 0x1u256); + assert(u256_max % u256_max == 0x0u256); + assert((u256_max - 0x1u256) % u256_max == (u256_max - 0x1u256)); +} + +#[test(should_revert)] +pub fn u256_modulo_panic_on_undefined_math() { + log(0x1u256 % 0x0u256); +} + +#[test] +pub fn u256_modulo_unsafe_math() { + disable_panic_on_unsafe_math(); + assert(0x0u256 % 0x0u256 == 0x0u256); + assert(0x1u256 % 0x0u256 == 0x0u256); + assert(u256::max() % 0x0u256 == 0x0u256); +} diff --git a/test/src/in_language_tests/test_programs/revert_inline_tests/src/main.sw b/test/src/in_language_tests/test_programs/revert_inline_tests/src/main.sw index 64c74e30b63..e11a6f413cd 100644 --- a/test/src/in_language_tests/test_programs/revert_inline_tests/src/main.sw +++ b/test/src/in_language_tests/test_programs/revert_inline_tests/src/main.sw @@ -18,4 +18,4 @@ fn pass_revert_require() { #[test(should_revert)] fn revert_revert_with_log() { revert_with_log("error") -} \ No newline at end of file +} diff --git a/test/src/in_language_tests/test_programs/u128_inline_tests/src/main.sw b/test/src/in_language_tests/test_programs/u128_inline_tests/src/main.sw index 4d7acda31fb..584a1716ad4 100644 --- a/test/src/in_language_tests/test_programs/u128_inline_tests/src/main.sw +++ b/test/src/in_language_tests/test_programs/u128_inline_tests/src/main.sw @@ -1,6 +1,14 @@ library; -use std::{u128::U128, registers::flags, flags::{set_flags, disable_panic_on_unsafe_math, disable_panic_on_overflow}}; +use std::{ + flags::{ + disable_panic_on_overflow, + disable_panic_on_unsafe_math, + set_flags, + }, + registers::flags, + u128::U128, +}; #[test] fn u128_from_u8() { @@ -686,6 +694,60 @@ fn u128_root() { assert(root_of_u_128 == U128::from((0, 1))); } +#[test] +fn u128_mod() { + let u128_zero = U128::zero(); + let u128_1 = U128::from((0, 1)); + let u128_2 = U128::from((0, 2)); + let u128_3 = U128::from((0, 3)); + let u128_max = U128::max(); + + assert(u128_zero % u128_1 == u128_zero); + assert(u128_zero % u128_2 == u128_zero); + assert(u128_zero % u128_3 == u128_zero); + assert(u128_zero % u128_max == u128_zero); + + assert(u128_1 % u128_1 == u128_zero); + assert(U128::from((0, 10)) % u128_1 == u128_zero); + assert(U128::from((0, 10000)) % u128_1 == u128_zero); + assert(u128_max % u128_1 == u128_zero); + + assert(u128_2 % u128_2 == u128_zero); + assert(u128_3 % u128_2 == u128_1); + assert(U128::from((0, 10)) % u128_2 == u128_zero); + assert(U128::from((0, 10000)) % u128_2 == u128_zero); + assert(U128::from((0, 10001)) % u128_2 == u128_1); + assert(U128::from((0, u64::max())) % u128_2 == u128_1); + assert(U128::from((u64::max(), 0)) % u128_2 == u128_zero); + assert(U128::from((u64::max(), 1)) % u128_2 == u128_1); + assert(u128_max % u128_2 == u128_1); + + assert(u128_3 % u128_3 == u128_zero); + assert(u128_2 % u128_3 == u128_2); + assert(u128_1 % u128_3 == u128_1); + assert(U128::from((0, 30000)) % u128_3 == u128_zero); + assert(U128::from((0, 30001)) % u128_3 == u128_1); + assert(U128::from((0, 30002)) % u128_3 == u128_2); + assert(U128::from((u64::max(), 0)) % u128_3 == u128_zero); + assert(U128::from((u64::max(), 1)) % u128_3 == u128_1); + assert(U128::from((u64::max(), 2)) % u128_3 == u128_2); + assert(u128_max % u128_3 == u128_zero); + + assert(U128::from((u64::max(), 0)) % U128::from((u64::max(), 0)) == u128_zero); + assert(U128::from((u64::max(), 1)) % U128::from((u64::max(), 0)) == u128_1); + assert(U128::from((u64::max(), 2)) % U128::from((u64::max(), 0)) == u128_2); + assert(U128::from((u64::max(), 3)) % U128::from((u64::max(), 0)) == u128_3); + assert(u128_max % U128::from((u64::max(), 0)) == U128::from((0, u64::max()))); +} + +#[test(should_revert)] +fn revert_u128_mod_zero() { + let a = U128::from((0, 1)); + let b = U128::zero(); + + let result = a % b; +} + #[test] fn u128_log() { let prior_flags = flags(); @@ -781,228 +843,272 @@ fn parity_u128_log_with_ruint() { // Failure cases found by comparing parity with ruint implementation of U128 // https://docs.rs/ruint/latest/src/ruint/log.rs.html#45-89 let a = [ - 2, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, - 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 15, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, - 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, - 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 29, - 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, - 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, - 33, 33, 33, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, - 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, - 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, - 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, - 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, - 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, - 41, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, - 42, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, - 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, - 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, - 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, - 47, 47, 47, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, - 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, - 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, - 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 60, 60, - 60, 60, 61, 61, 61, 62, 62, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, - 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, - 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, - 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, - 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, - 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, - 67, 67, 67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, - 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, - 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 70, - 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, - 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, - 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, - 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, - 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, - 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, - 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, - 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, - 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, - 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, - 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, - 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, - 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, - 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, - 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 85, - 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, - 85, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, - 86, 86, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, - 87, 87, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, - 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 90, - 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, - 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, - 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, - 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, - 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, - 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, - 100, 100, 100, 100, + 2, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, + 14, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, + 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 29, 29, 29, + 29, 29, 30, 30, 30, 30, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, + 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, + 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, + 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, + 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, + 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 60, 60, 60, + 60, 61, 61, 61, 62, 62, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, + 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, + 69, 69, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, + 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, + 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 79, + 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, + 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, + 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, + 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, + 84, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, + 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87, 87, 87, 87, 87, + 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 88, 88, 88, + 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, + 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, + 89, 89, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, + 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, + 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, + 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, + 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, + 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, ]; let b = [ - 3, 3, 5, 6, 7, 3, 6, 7, 3, 7, 3, 3, 9, 10, 11, 12, 13, 14, 15, 3, 10, 11, 12, 13, 14, 15, - 3, 11, 12, 13, 14, 15, 3, 12, 13, 14, 15, 3, 13, 14, 15, 3, 14, 15, 3, 15, 3, 3, 5, 6, 7, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 3, 5, 6, 7, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, - 6, 7, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, - 6, 7, 25, 26, 27, 28, 29, 30, 31, 3, 6, 7, 26, 27, 28, 29, 30, 31, 3, 6, 7, 27, 28, 29, 30, - 31, 3, 6, 7, 28, 29, 30, 31, 3, 6, 7, 29, 30, 31, 3, 6, 7, 30, 31, 3, 6, 7, 31, 3, 6, 7, 3, - 6, 7, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 6, 7, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 6, 7, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 3, 6, 7, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 38, - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, - 62, 63, 3, 7, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 3, 7, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 44, 45, 46, 47, 48, 49, 50, 51, 52, - 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 3, 7, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, - 62, 63, 3, 7, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 50, 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 3, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 54, - 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 56, 57, 58, - 59, 60, 61, 62, 63, 3, 57, 58, 59, 60, 61, 62, 63, 3, 58, 59, 60, 61, 62, 63, 3, 59, 60, - 61, 62, 63, 3, 60, 61, 62, 63, 3, 61, 62, 63, 3, 62, 63, 3, 63, 3, 3, 5, 6, 7, 9, 10, 11, - 12, 13, 14, 15, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, - 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, - 12, 13, 14, 15, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, - 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, - 13, 14, 15, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, - 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, - 15, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 69, 70, - 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, - 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 70, 71, 72, 73, 74, 75, - 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, - 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, - 10, 11, 12, 13, 14, 15, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, - 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 73, - 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, - 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 74, 75, 76, 77, 78, 79, 80, 81, 82, - 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, - 11, 12, 13, 14, 15, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, - 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, - 7, 9, 10, 11, 12, 13, 14, 15, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, - 10, 11, 12, 13, 14, 15, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, - 7, 10, 11, 12, 13, 14, 15, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, - 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, - 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 84, 85, 86, 87, 88, - 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 85, - 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, - 14, 15, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, - 12, 13, 14, 15, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, - 11, 12, 13, 14, 15, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, - 11, 12, 13, 14, 15, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, - 12, 13, 14, 15, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, - 14, 15, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 92, - 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 93, 94, 95, 96, 97, - 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, - 10, 11, 12, 13, 14, 15, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 96, - 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, - 12, 13, 14, 15, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 99, 100, 3, 5, 6, 7, 10, - 11, 12, 13, 14, 15, 100, 3, 5, 6, 7, 11, 12, 13, 14, 15, + 3, 3, 5, 6, 7, 3, 6, 7, 3, 7, 3, 3, 9, 10, 11, 12, 13, 14, 15, 3, 10, 11, + 12, 13, 14, 15, 3, 11, 12, 13, 14, 15, 3, 12, 13, 14, 15, 3, 13, 14, 15, 3, + 14, 15, 3, 15, 3, 3, 5, 6, 7, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 3, 5, 6, 7, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 3, 5, 6, 7, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, + 5, 6, 7, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 3, 5, 6, 7, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 24, + 25, 26, 27, 28, 29, 30, 31, 3, 5, 6, 7, 25, 26, 27, 28, 29, 30, 31, 3, 6, + 7, 26, 27, 28, 29, 30, 31, 3, 6, 7, 27, 28, 29, 30, 31, 3, 6, 7, 28, 29, 30, + 31, 3, 6, 7, 29, 30, 31, 3, 6, 7, 30, 31, 3, 6, 7, 31, 3, 6, 7, 3, 6, 7, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 6, 7, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 3, 6, 7, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 6, + 7, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 3, 7, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, + 7, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 3, 7, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 3, 7, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 3, 7, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 3, 7, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 3, 7, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 3, 7, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 3, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 3, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 3, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 3, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 3, 56, 57, 58, 59, 60, 61, 62, 63, 3, 57, 58, 59, 60, 61, + 62, 63, 3, 58, 59, 60, 61, 62, 63, 3, 59, 60, 61, 62, 63, 3, 60, 61, 62, 63, + 3, 61, 62, 63, 3, 62, 63, 3, 63, 3, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, + 6, 7, 9, 10, 11, 12, 13, 14, 15, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, + 14, 15, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, + 7, 9, 10, 11, 12, 13, 14, 15, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 75, 76, + 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, + 6, 7, 9, 10, 11, 12, 13, 14, 15, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, + 13, 14, 15, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, + 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, + 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, + 11, 12, 13, 14, 15, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, + 15, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, + 12, 13, 14, 15, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, + 7, 10, 11, 12, 13, 14, 15, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, + 5, 6, 7, 10, 11, 12, 13, 14, 15, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 92, 93, 94, 95, 96, 97, 98, 99, 100, 3, + 5, 6, 7, 10, 11, 12, 13, 14, 15, 93, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, + 7, 10, 11, 12, 13, 14, 15, 94, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, + 12, 13, 14, 15, 95, 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, + 96, 97, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 97, 98, 99, 100, + 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 98, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, + 14, 15, 99, 100, 3, 5, 6, 7, 10, 11, 12, 13, 14, 15, 100, 3, 5, 6, 7, 11, + 12, 13, 14, 15, ]; let expected = [ - 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, - 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 3, 1, 1, 0, - 0, 0, 3, 1, 1, 0, 0, 3, 1, 1, 0, 3, 1, 1, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, - 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 3, 0, 3, 3, 2, 2, 2, 1, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, - 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, - 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, - 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 4, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, + 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 3, + 1, 1, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 3, 1, 1, 0, 0, 3, 1, 1, 0, 3, 1, 1, 3, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 3, 0, 3, 3, 2, + 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, + 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, + 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, + 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 4, 2, 2, 2, 1, 1, 1, 1, 1, ]; @@ -1065,7 +1171,7 @@ fn u128_overflowing_pow() { let a = U128::max(); let res = a.pow(2); - + assert(res == U128::from((0, 0))); set_flags(prior_flags); @@ -1081,4 +1187,4 @@ fn u128_unsafemath_log2() { assert(res == U128::zero()); set_flags(prior_flags); -} \ No newline at end of file +} diff --git a/test/src/sdk-harness/Forc.lock b/test/src/sdk-harness/Forc.lock index b05cb399826..ee70e29936a 100644 --- a/test/src/sdk-harness/Forc.lock +++ b/test/src/sdk-harness/Forc.lock @@ -251,11 +251,21 @@ name = "run_external_proxy" source = "member" dependencies = ["std"] +[[package]] +name = "run_external_proxy_with_storage" +source = "member" +dependencies = ["std"] + [[package]] name = "run_external_target" source = "member" dependencies = ["std"] +[[package]] +name = "run_external_target_with_storage" +source = "member" +dependencies = ["std"] + [[package]] name = "script_bytecode" source = "member" @@ -410,7 +420,7 @@ source = "member" dependencies = ["std"] [[package]] -name = "tx_output_change_contract" +name = "tx_output_contract" source = "member" dependencies = ["std"] diff --git a/test/src/sdk-harness/Forc.toml b/test/src/sdk-harness/Forc.toml index 97a7001d7da..2f4dd422291 100644 --- a/test/src/sdk-harness/Forc.toml +++ b/test/src/sdk-harness/Forc.toml @@ -29,6 +29,8 @@ members = [ "test_projects/result_option_expect", "test_projects/run_external_proxy", "test_projects/run_external_target", + "test_projects/run_external_proxy_with_storage", + "test_projects/run_external_target_with_storage", "test_projects/script_bytecode", "test_projects/script_data", "test_projects/storage", @@ -76,7 +78,7 @@ members = [ "test_artifacts/storage_vec/svec_u64", "test_artifacts/tx_contract", "test_artifacts/tx_input_count_predicate", - "test_artifacts/tx_output_change_contract", + "test_artifacts/tx_output_contract", "test_artifacts/tx_output_contract_creation_predicate", "test_artifacts/tx_output_count_predicate", "test_artifacts/tx_output_predicate", diff --git a/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw index ed80700620c..bce0730c604 100644 --- a/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw @@ -135,7 +135,8 @@ impl TxContractTest for Contract { let mut iter = 0; while iter < expected_data_bytes.len() { - if data.get(iter).unwrap() != expected_data_bytes.get(iter).unwrap() { + if data.get(iter).unwrap() != expected_data_bytes.get(iter).unwrap() + { return false } iter += 1; diff --git a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw deleted file mode 100644 index 32aeeb5e7c9..00000000000 --- a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw +++ /dev/null @@ -1,13 +0,0 @@ -contract; - -use std::asset::transfer; - -abi TxOutputChangeContract { - fn send_assets(to: Address, asset: AssetId, amount: u64); -} - -impl TxOutputChangeContract for Contract { - fn send_assets(to: Address, asset: AssetId, amount: u64) { - transfer(Identity::Address(to), asset, amount); - } -} diff --git a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml b/test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml similarity index 81% rename from test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml rename to test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml index 5a053f78b77..824dbc61360 100644 --- a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml +++ b/test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml @@ -2,7 +2,7 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" -name = "tx_output_change_contract" +name = "tx_output_contract" [dependencies] std = { path = "../../../../../sway-lib-std" } diff --git a/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw new file mode 100644 index 00000000000..3682db6ea9b --- /dev/null +++ b/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw @@ -0,0 +1,35 @@ +contract; + +use std::asset::transfer; +use std::outputs::*; + +abi TxOutputContract { + fn send_assets_change(to: Address, asset: AssetId, amount: u64); + fn send_assets_variable(to: Address, asset: AssetId, index: u64) -> (Address, AssetId, u64); +} + +impl TxOutputContract for Contract { + fn send_assets_change(to: Address, asset: AssetId, amount: u64) { + transfer(Identity::Address(to), asset, amount); + } + + fn send_assets_variable(to: Address, asset: AssetId, index: u64) -> (Address, AssetId, u64) { + transfer(Identity::Address(to), asset, 1); + + get_variable_tx_params(index) + } +} + +fn get_variable_tx_params(index: u64) -> (Address, AssetId, u64) { + let tx_asset_id = output_asset_id(index); + let tx_to = output_asset_to(index); + let tx_amount = output_amount(index); + + let tx_output_type = output_type(index); + assert(tx_output_type.is_some() && tx_output_type.unwrap() == Output::Variable); + ( + tx_to.unwrap_or(Address::zero()), + tx_asset_id.unwrap_or(AssetId::zero()), + tx_amount.unwrap_or(0), + ) +} diff --git a/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw index e639bd11833..6f6495c9a42 100644 --- a/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw @@ -1,6 +1,6 @@ predicate; -use std::outputs::{output_asset_id, output_asset_to, output_type, Output}; +use std::outputs::{Output, output_asset_id, output_asset_to, output_type}; fn main(index: u64, asset_id: b256, to: b256, expected_type: Output) -> bool { let tx_asset_id = output_asset_id(index); diff --git a/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw index 59bd4f10714..44938b3c833 100644 --- a/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw @@ -1,6 +1,6 @@ predicate; -use std::tx::{tx_type, Transaction}; +use std::tx::{Transaction, tx_type}; fn main(expected_type: Transaction) -> bool { tx_type() == expected_type diff --git a/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw index adb0ea9dbc9..294051eb0cb 100644 --- a/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw @@ -1,8 +1,13 @@ predicate; -use std::tx::{tx_witnesses_count, tx_witness_data_length, tx_witness_data}; +use std::tx::{tx_witness_data, tx_witness_data_length, tx_witnesses_count}; -fn main(index: u64, expected_count: u64, expected_length: u64, expected_data: [u8; 64]) -> bool { +fn main( + index: u64, + expected_count: u64, + expected_length: u64, + expected_data: [u8; 64], +) -> bool { let count: u64 = tx_witnesses_count(); let length: Option = tx_witness_data_length(index); let data: Option<[u8; 64]> = tx_witness_data(index); @@ -11,7 +16,7 @@ fn main(index: u64, expected_count: u64, expected_length: u64, expected_data: [u assert(length.is_some() && length.unwrap() == expected_length); assert(data.is_some()); - let data = data.unwrap(); + let data = data.unwrap(); let mut iter = 0; while iter < 64 { assert(data[iter] == expected_data[iter]); diff --git a/test/src/sdk-harness/test_projects/auth/mod.rs b/test/src/sdk-harness/test_projects/auth/mod.rs index 2fefd28f8c8..5c167a1d927 100644 --- a/test/src/sdk-harness/test_projects/auth/mod.rs +++ b/test/src/sdk-harness/test_projects/auth/mod.rs @@ -624,7 +624,7 @@ async fn can_get_predicate_address() { // Setup predicate. let hex_predicate_address: &str = - "0x5dcc82a88eebb07fb628db93d11ec38f085cbf36453a7135fea41b93cc44e118"; + "0x8b300a68337368654e71c65ae93c3d9eb3b9837d0c11d770cbf8740a6a5a8631"; let predicate_address = Address::from_str(hex_predicate_address).expect("failed to create Address from string"); let predicate_bech32_address = Bech32Address::from(predicate_address); @@ -750,7 +750,7 @@ async fn when_incorrect_predicate_address_passed() { async fn can_get_predicate_address_in_message() { // Setup predicate address. let hex_predicate_address: &str = - "0x5dcc82a88eebb07fb628db93d11ec38f085cbf36453a7135fea41b93cc44e118"; + "0x8b300a68337368654e71c65ae93c3d9eb3b9837d0c11d770cbf8740a6a5a8631"; let predicate_address = Address::from_str(hex_predicate_address).expect("failed to create Address from string"); let predicate_bech32_address = Bech32Address::from(predicate_address); diff --git a/test/src/sdk-harness/test_projects/harness.rs b/test/src/sdk-harness/test_projects/harness.rs index bc01e3cbc5b..6f297b67ad9 100644 --- a/test/src/sdk-harness/test_projects/harness.rs +++ b/test/src/sdk-harness/test_projects/harness.rs @@ -31,6 +31,7 @@ mod registers; mod result_in_abi; mod result_option_expect; mod run_external_proxy; +mod run_external_proxy_with_storage; mod script_data; mod storage; mod storage_access; diff --git a/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.lock b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.lock new file mode 100644 index 00000000000..c0e7ac7bd1a --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-37E4B1588712FA61' + +[[package]] +name = 'std' +source = 'path+from-root-37E4B1588712FA61' +dependencies = ['core'] + +[[package]] +name = 'run_external_proxy' +source = 'member' +dependencies = ['std'] diff --git a/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.toml b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.toml new file mode 100644 index 00000000000..77108416623 --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "run_external_proxy_with_storage" + +[dependencies] +std = { path = "../../../../../sway-lib-std" } diff --git a/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/mod.rs b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/mod.rs new file mode 100644 index 00000000000..39e70a5fcc4 --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/mod.rs @@ -0,0 +1,106 @@ +use fuels::{prelude::*, types::Bits256}; + +abigen!(Contract( + name = "RunExternalProxyContract", + abi = "test_projects/run_external_proxy_with_storage/out/release/run_external_proxy_with_storage-abi.json", +)); + +#[tokio::test] +async fn run_external_can_proxy_call() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + + let storage_configuration = + StorageConfiguration::default().add_slot_overrides_from_file("test_projects/run_external_target_with_storage/out/release/run_external_target_with_storage-storage_slots.json").unwrap(); + + let target_id = Contract::load_from( + "test_projects/run_external_target_with_storage/out/release/run_external_target_with_storage.bin", + LoadConfiguration::default() + .with_storage_configuration(storage_configuration.clone()), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let configurables = RunExternalProxyContractConfigurables::default() + .with_TARGET(target_id.clone().into()) + .unwrap(); + let id = Contract::load_from( + "test_projects/run_external_proxy_with_storage/out/release/run_external_proxy_with_storage.bin", + LoadConfiguration::default().with_configurables(configurables).with_storage_configuration(storage_configuration), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + let instance = RunExternalProxyContract::new(id.clone(), wallet); + // Call "large_value" + // Will call run_external_proxy::large_value + // that will call run_external_target::large_value + // and return the value doubled. + let result = instance + .methods() + .large_value() + .with_contract_ids(&[target_id.clone().into()]) + .call() + .await + .unwrap(); + for r in result.receipts.iter() { + match r { + Receipt::LogData { data, .. } => { + if let Some(data) = data { + if data.len() > 8 { + if let Ok(s) = std::str::from_utf8(&data[8..]) { + print!("{:?} ", s); + } + } + println!("{:?}", data); + } + } + _ => {} + } + } + let expected_large = + Bits256::from_hex_str("0x00000000000000000000000059F2f1fCfE2474fD5F0b9BA1E73ca90b143Eb8d0") + .unwrap(); + assert_eq!(result.value, expected_large); + // Call "double_value" + // Will call run_external_proxy::double_value + // that will call run_external_target::double_value + // and return the value doubled. + let result = instance + .methods() + .double_value(42) + .with_contract_ids(&[target_id.clone().into()]) + .call() + .await + .unwrap(); + for r in result.receipts.iter() { + match r { + Receipt::LogData { data, .. } => { + if let Some(data) = data { + if data.len() > 8 { + if let Ok(s) = std::str::from_utf8(&data[8..]) { + print!("{:?} ", s); + } + } + println!("{:?}", data); + } + } + _ => {} + } + } + assert_eq!(result.value, 84); + // Call "does_not_exist_in_the_target" + // Will call run_external_proxy::does_not_exist_in_the_target + // it will proxy the call to run_external_target, + // and endup in the fallback, fn that will triple the input value + let result = instance + .methods() + .does_not_exist_in_the_target(42) + .with_contract_ids(&[target_id.into()]) + .call() + .await + .unwrap(); + assert_eq!(result.value, 126); +} diff --git a/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/src/main.sw b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/src/main.sw new file mode 100644 index 00000000000..05dc44f0473 --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_proxy_with_storage/src/main.sw @@ -0,0 +1,29 @@ +contract; + +use std::execution::run_external; + +configurable { + TARGET: ContractId = ContractId::zero(), +} + +abi RunExternalTest { + fn double_value(foo: u64) -> u64; + fn large_value() -> b256; + fn does_not_exist_in_the_target(foo: u64) -> u64; +} +impl RunExternalTest for Contract { + fn double_value(_foo: u64) -> u64 { + __log(1); + run_external(TARGET) + } + + fn large_value() -> b256 { + run_external(TARGET) + } + + // ANCHOR: does_not_exist_in_the_target + fn does_not_exist_in_the_target(_foo: u64) -> u64 { + run_external(TARGET) + } + // ANCHOR_END: does_not_exist_in_the_target +} \ No newline at end of file diff --git a/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.lock b/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.lock new file mode 100644 index 00000000000..05131a01155 --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = 'core' +source = 'path+from-root-37E4B1588712FA61' + +[[package]] +name = 'std' +source = 'path+from-root-37E4B1588712FA61' +dependencies = ['core'] + +[[package]] +name = 'run_external_target' +source = 'member' +dependencies = ['std'] diff --git a/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.toml b/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.toml new file mode 100644 index 00000000000..130715dae8c --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_target_with_storage/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "run_external_target_with_storage" + +[dependencies] +std = { path = "../../../../../sway-lib-std" } diff --git a/test/src/sdk-harness/test_projects/run_external_target_with_storage/src/main.sw b/test/src/sdk-harness/test_projects/run_external_target_with_storage/src/main.sw new file mode 100644 index 00000000000..ab7d770ccef --- /dev/null +++ b/test/src/sdk-harness/test_projects/run_external_target_with_storage/src/main.sw @@ -0,0 +1,38 @@ +contract; + +use std::constants::ZERO_B256; + +storage { + owner: Identity = Identity::Address(Address::from(ZERO_B256)), + simple_value: u64 = 0, +} + +abi RunExternalTest { + fn double_value(foo: u64) -> u64; + fn large_value() -> b256; +} + +impl RunExternalTest for Contract { + fn double_value(foo: u64) -> u64 { + __log(2); + foo * 2 + } + fn large_value() -> b256 { + 0x00000000000000000000000059F2f1fCfE2474fD5F0b9BA1E73ca90b143Eb8d0 + } +} + +// ANCHOR: fallback +#[fallback, storage(read, write)] +fn fallback() -> u64 { + let iden = storage.owner.read(); + + use std::call_frames::*; + __log(3); + __log(called_method()); + __log("double_value"); + __log(called_method() == "double_value"); + let foo = called_args::(); + foo * 3 +} +// ANCHOR_END: fallback diff --git a/test/src/sdk-harness/test_projects/tx_fields/mod.rs b/test/src/sdk-harness/test_projects/tx_fields/mod.rs index 0ac934999c7..f7e7308737d 100644 --- a/test/src/sdk-harness/test_projects/tx_fields/mod.rs +++ b/test/src/sdk-harness/test_projects/tx_fields/mod.rs @@ -14,8 +14,8 @@ const MESSAGE_DATA: [u8; 3] = [1u8, 2u8, 3u8]; const TX_CONTRACT_BYTECODE_PATH: &str = "test_artifacts/tx_contract/out/release/tx_contract.bin"; const TX_OUTPUT_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_predicate/out/release/tx_output_predicate.bin"; -const TX_OUTPUT_CHANGE_CONTRACT_BYTECODE_PATH: &str = - "test_artifacts/tx_output_change_contract/out/release/tx_output_change_contract.bin"; +const TX_OUTPUT_CONTRACT_BYTECODE_PATH: &str = + "test_artifacts/tx_output_contract/out/release/tx_output_contract.bin"; const TX_FIELDS_PREDICATE_BYTECODE_PATH: &str = "test_projects/tx_fields/out/release/tx_fields.bin"; const TX_CONTRACT_CREATION_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_contract_creation_predicate/out/release/tx_output_contract_creation_predicate.bin"; @@ -28,8 +28,8 @@ const TX_INPUT_COUNT_PREDICATE_BYTECODE_PATH: &str = const TX_OUTPUT_COUNT_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_count_predicate/out/release/tx_output_count_predicate.bin"; -use crate::tx_fields::Transaction as SwayTransaction; use crate::tx_fields::Output as SwayOutput; +use crate::tx_fields::Transaction as SwayTransaction; abigen!( Contract( @@ -37,8 +37,8 @@ abigen!( abi = "test_artifacts/tx_contract/out/release/tx_contract-abi.json", ), Contract( - name = "TxOutputChangeContract", - abi = "test_artifacts/tx_output_change_contract/out/release/tx_output_change_contract-abi.json", + name = "TxOutputContract", + abi = "test_artifacts/tx_output_contract/out/release/tx_output_contract-abi.json", ), Predicate( name = "TestPredicate", @@ -172,7 +172,10 @@ async fn generate_predicate_inputs( (predicate_code, predicate_input, predicate_message) } -async fn setup_output_predicate(index: u64, expected_output_type: SwayOutput) -> (WalletUnlocked, WalletUnlocked, Predicate, AssetId, AssetId) { +async fn setup_output_predicate( + index: u64, + expected_output_type: SwayOutput, +) -> (WalletUnlocked, WalletUnlocked, Predicate, AssetId, AssetId) { let asset_id1 = AssetId::default(); let asset_id2 = AssetId::new([2u8; 32]); let wallets_config = WalletsConfig::new_multiple_assets( @@ -201,7 +204,12 @@ async fn setup_output_predicate(index: u64, expected_output_type: SwayOutput) -> let wallet2 = wallets.pop().unwrap(); let predicate_data = TestOutputPredicateEncoder::default() - .encode_data(index, Bits256([0u8; 32]), Bits256(*wallet1.address().hash()), expected_output_type) + .encode_data( + index, + Bits256([0u8; 32]), + Bits256(*wallet1.address().hash()), + expected_output_type, + ) .unwrap(); let predicate = Predicate::load_from(TX_OUTPUT_PREDICATE_BYTECODE_PATH) @@ -1547,7 +1555,8 @@ mod outputs { #[tokio::test] async fn can_get_tx_output_details() { - let (wallet, _, predicate, asset_id, _) = setup_output_predicate(0, SwayOutput::Coin).await; + let (wallet, _, predicate, asset_id, _) = + setup_output_predicate(0, SwayOutput::Coin).await; let balance = predicate.get_asset_balance(&asset_id).await.unwrap(); @@ -1678,32 +1687,39 @@ mod outputs { assert_eq!(predicate_balance, 0); } } - + #[tokio::test] async fn can_get_tx_output_change_details() { // Prepare predicate - let (wallet, _, predicate, asset_id, _) = setup_output_predicate(2, SwayOutput::Change).await; + let (wallet, _, predicate, asset_id, _) = + setup_output_predicate(2, SwayOutput::Change).await; let provider = wallet.try_provider().unwrap().clone(); let balance = predicate.get_asset_balance(&asset_id).await.unwrap(); // Deploy contract - let contract_id = Contract::load_from(TX_OUTPUT_CHANGE_CONTRACT_BYTECODE_PATH, LoadConfiguration::default()) - .unwrap() - .deploy(&wallet, TxPolicies::default()) - .await - .unwrap(); - - let instance = TxOutputChangeContract::new(contract_id.clone(), wallet.clone()); + let contract_id = Contract::load_from( + TX_OUTPUT_CONTRACT_BYTECODE_PATH, + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let instance = TxOutputContract::new(contract_id.clone(), wallet.clone()); // Send tokens to the contract let _ = wallet .force_transfer_to_contract(&contract_id, 10, asset_id, TxPolicies::default()) - .await - .unwrap(); + .await + .unwrap(); // Build transaction - let call_handler = instance.methods().send_assets(wallet.clone().address(), asset_id, 10); + let call_handler = + instance + .methods() + .send_assets_change(wallet.clone().address(), asset_id, 10); let mut tb = call_handler.transaction_builder().await.unwrap(); // Inputs for predicate @@ -1714,20 +1730,21 @@ mod outputs { .unwrap(); // Outputs for predicate - let predicate_output = wallet.get_asset_outputs_for_amount( - &wallet.address(), - asset_id, - transfer_amount, - ); + let predicate_output = + wallet.get_asset_outputs_for_amount(&wallet.address(), asset_id, transfer_amount); // Append the inputs and outputs to the transaction tb.inputs.push(predicate_input.get(0).unwrap().clone()); tb.outputs.push(predicate_output.get(0).unwrap().clone()); - tb.outputs.push(SdkOutput::Change{to: wallet.address().into(), amount: 0, asset_id}); + tb.outputs.push(SdkOutput::Change { + to: wallet.address().into(), + amount: 0, + asset_id, + }); wallet.adjust_for_fee(&mut tb, 0).await.unwrap(); tb.add_signer(wallet.clone()).unwrap(); - + let tx = tb.build(provider.clone()).await.unwrap(); let _tx_id = provider.send_transaction(tx).await.unwrap(); @@ -1735,6 +1752,44 @@ mod outputs { let new_balance = predicate.get_asset_balance(&asset_id).await.unwrap(); assert!(balance - transfer_amount == new_balance); } + + #[tokio::test] + async fn can_get_tx_output_variable_details() { + // Prepare wallet + let (wallet, _, _, asset_id, _) = setup_output_predicate(1, SwayOutput::Variable).await; + + // Deploy contract + let contract_id = Contract::load_from( + TX_OUTPUT_CONTRACT_BYTECODE_PATH, + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let instance = TxOutputContract::new(contract_id.clone(), wallet.clone()); + + // Send tokens to the contract + let _ = wallet + .force_transfer_to_contract(&contract_id, 10, asset_id, TxPolicies::default()) + .await + .unwrap(); + + // Run transaction with variable output + let (tx_to, tx_asset_id, tx_amount) = instance + .methods() + .send_assets_variable(wallet.clone().address(), asset_id, 2) + .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) + .call() + .await + .unwrap() + .value; + + assert_eq!(tx_to, wallet.clone().address().into()); + assert_eq!(tx_asset_id, asset_id); + assert_eq!(tx_amount, 1); + } } mod revert { @@ -1743,7 +1798,8 @@ mod outputs { #[tokio::test] #[should_panic] async fn fails_output_predicate_when_incorrect_asset() { - let (wallet1, _, predicate, _, asset_id2) = setup_output_predicate(0, SwayOutput::Coin).await; + let (wallet1, _, predicate, _, asset_id2) = + setup_output_predicate(0, SwayOutput::Coin).await; let transfer_amount = 10; predicate @@ -1760,7 +1816,8 @@ mod outputs { #[tokio::test] #[should_panic] async fn fails_output_predicate_when_incorrect_to() { - let (_, wallet2, predicate, asset_id1, _) = setup_output_predicate(0, SwayOutput::Coin).await; + let (_, wallet2, predicate, asset_id1, _) = + setup_output_predicate(0, SwayOutput::Coin).await; let transfer_amount = 10; predicate