diff --git a/.editorconfig b/.editorconfig index ef8ed24c52a5..1b137cf4ebef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,9 +7,18 @@ root = true [*] end_of_line = lf charset = utf-8 -trim_trailing_whitespace = true insert_final_newline = true +# some tests need trailing whitespace in output snapshots +[!tests/] +trim_trailing_whitespace = true +# for actual source code files of test, we still don't want trailing whitespace +[tests/**.{rs,js}] +trim_trailing_whitespace = true +# these specific source files need to have trailing whitespace. +[tests/ui/{frontmatter/frontmatter-whitespace-3.rs,parser/shebang/shebang-space.rs}] +trim_trailing_whitespace = false + [!src/llvm-project] indent_style = space indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml index 712b32759ae0..4a79cd4ba97c 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yaml +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -1,5 +1,5 @@ name: Documentation problem -description: Create a report for a documentation problem. +description: Report an issue with documentation content. labels: ["A-docs"] body: - type: markdown @@ -19,20 +19,20 @@ body: - [The Rustonomicon](https://github.com/rust-lang/nomicon/issues) - [The Embedded Book](https://github.com/rust-embedded/book/issues) - All other documentation issues should be filed here. + Or, if you find an issue related to rustdoc (e.g. doctest, rustdoc UI), please use the rustdoc issue template instead. - Or, if you find an issue related to rustdoc (e.g. doctest, rustdoc UI), please use the bug report or blank issue template instead. + All other documentation issues should be filed here. - type: textarea id: location attributes: - label: Location + label: Location (URL) validations: - required: true + required: true - type: textarea id: summary attributes: label: Summary validations: - required: true \ No newline at end of file + required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ce543071d84..f539b64d8c82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: run_type: ${{ steps.jobs.outputs.run_type }} steps: - name: Checkout the source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Test citool # Only test citool on the auto branch, to reduce latency of the calculate matrix job # on PR/try builds. @@ -113,16 +113,16 @@ jobs: run: git config --global core.autocrlf false - name: checkout the source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 - # Free up disk space on Linux and Windows by removing preinstalled components that + # Free up disk space on Linux by removing preinstalled components that # we do not need. We do this to enable some of the less resource # intensive jobs to run on free runners, which however also have # less disk space. - name: free up disk space - run: src/ci/scripts/free-disk-space.sh + run: src/ci/scripts/free-disk-space-linux.sh if: matrix.free_disk # If we don't need to free up disk space then just report how much space we have @@ -313,7 +313,7 @@ jobs: if: ${{ !cancelled() && contains(fromJSON('["auto", "try"]'), needs.calculate_matrix.outputs.run_type) }} steps: - name: checkout the source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 2 # Calculate the exit status of the whole CI workflow. diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 9d4b6192d6ea..80ffd67e04e1 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: checkout the source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive - name: install the bootstrap toolchain @@ -101,7 +101,7 @@ jobs: pull-requests: write steps: - name: checkout the source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: download Cargo.lock from update job uses: actions/download-artifact@v4 diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 6d050d98cb2e..a89867efe666 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -29,7 +29,7 @@ jobs: # Needed to write to the ghcr.io registry packages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index ca088ba31fdf..12ff4be4f1e8 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -15,7 +15,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # Make sure that we have enough commits to find the parent merge commit. # Since all merges should be through merge commits, fetching two commits diff --git a/.gitmodules b/.gitmodules index 439fde6d7660..8617643a1202 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,7 +25,7 @@ [submodule "src/llvm-project"] path = src/llvm-project url = https://github.com/rust-lang/llvm-project.git - branch = rustc/20.1-2025-07-13 + branch = rustc/21.1-2025-08-01 shallow = true [submodule "src/doc/embedded-book"] path = src/doc/embedded-book diff --git a/.mailmap b/.mailmap index 90533e81b39f..2b75f5a145f2 100644 --- a/.mailmap +++ b/.mailmap @@ -597,6 +597,7 @@ Sam Radhakrishnan Samuel Tardieu Santiago Pastorino Santiago Pastorino +Sasha Pourcelot Sasha Scott McMurray Scott McMurray Scott Olson Scott Olson diff --git a/Cargo.lock b/Cargo.lock index d9cfda17ad92..91528a4135e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,9 +80,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -119,18 +119,18 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-svg" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c" +checksum = "dc03a770ef506fe1396c0e476120ac0e6523cf14b74218dd5f18cd6833326fa9" dependencies = [ "anstyle", "anstyle-lossy", @@ -141,13 +141,13 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -353,9 +353,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "camino" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" dependencies = [ "serde", ] @@ -518,9 +518,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" dependencies = [ "clap_builder", "clap_derive", @@ -538,9 +538,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" dependencies = [ "anstream", "anstyle", @@ -568,7 +568,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clippy" -version = "0.1.90" +version = "0.1.91" dependencies = [ "anstream", "askama", @@ -595,7 +595,7 @@ dependencies = [ [[package]] name = "clippy_config" -version = "0.1.90" +version = "0.1.91" dependencies = [ "clippy_utils", "itertools", @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "clippy_lints" -version = "0.1.90" +version = "0.1.91" dependencies = [ "arrayvec", "cargo_metadata 0.18.1", @@ -649,7 +649,7 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.90" +version = "0.1.91" dependencies = [ "arrayvec", "itertools", @@ -937,9 +937,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.161" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3523cc02ad831111491dd64b27ad999f1ae189986728e477604e61b81f828df" +checksum = "b5287274dfdf7e7eaa3d97d460eb2a94922539e6af214bda423f292105011ee2" dependencies = [ "cc", "cxxbridge-cmd", @@ -951,9 +951,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.161" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212b754247a6f07b10fa626628c157593f0abf640a3dd04cce2760eca970f909" +checksum = "65f3ce027a744135db10a1ebffa0863dab685aeef48f40a02c201f5e70c667d3" dependencies = [ "cc", "codespan-reporting", @@ -966,9 +966,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.161" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f426a20413ec2e742520ba6837c9324b55ffac24ead47491a6e29f933c5b135a" +checksum = "a07dc23f2eea4774297f4c9a17ae4065fecb63127da556e6c9fadb0216d93595" dependencies = [ "clap", "codespan-reporting", @@ -980,15 +980,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.161" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258b6069020b4e5da6415df94a50ee4f586a6c38b037a180e940a43d06a070d" +checksum = "f7a4dbad6171f763c4066c83dcd27546b6e93c5c5ae2229f9813bda7233f571d" [[package]] name = "cxxbridge-macro" -version = "1.0.161" +version = "1.0.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dec184b52be5008d6eaf7e62fc1802caf1ad1227d11b3b7df2c409c7ffc3f4" +checksum = "a9be4b527950fc42db06163705e78e73eedc8fd723708e942afe3572a9a2c366" dependencies = [ "indexmap", "proc-macro2", @@ -1051,13 +1051,13 @@ dependencies = [ [[package]] name = "declare_clippy_lint" -version = "0.1.90" +version = "0.1.91" [[package]] name = "derive-where" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", @@ -1171,7 +1171,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", + "redox_users 0.5.2", "windows-sys 0.60.2", ] @@ -1567,9 +1567,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1688,7 +1688,7 @@ dependencies = [ "potential_utf", "yoke 0.8.0", "zerofrom", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -1721,7 +1721,7 @@ dependencies = [ "litemap 0.8.0", "tinystr 0.8.1", "writeable 0.6.1", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -1769,7 +1769,7 @@ dependencies = [ "icu_properties", "icu_provider 2.0.0", "smallvec", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -1791,7 +1791,7 @@ dependencies = [ "icu_provider 2.0.0", "potential_utf", "zerotrie", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -1831,7 +1831,7 @@ dependencies = [ "yoke 0.8.0", "zerofrom", "zerotrie", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -1909,9 +1909,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" @@ -2094,9 +2094,9 @@ dependencies = [ [[package]] name = "jsonpath-rust" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d057f8fd19e20c3f14d3663983397155739b6bc1148dc5cd4c4a1a5b3130eb0" +checksum = "633a7320c4bb672863a3782e89b9094ad70285e097ff6832cddd0ec615beadfa" dependencies = [ "pest", "pest_derive", @@ -2190,7 +2190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -2201,9 +2201,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags", "libc", @@ -2643,9 +2643,9 @@ dependencies = [ [[package]] name = "object" -version = "0.37.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fd943161069e1768b4b3d050890ba48730e590f57e56d4aa04e7e090e61b4a" +checksum = "b3e3d0a7419f081f4a808147e845310313a39f322d7ae1f996b7f001d6cbed04" dependencies = [ "crc32fast", "flate2", @@ -2653,7 +2653,7 @@ dependencies = [ "indexmap", "memchr", "ruzstd 0.8.1", - "wasmparser 0.234.0", + "wasmparser 0.236.0", ] [[package]] @@ -2971,7 +2971,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -3167,9 +3167,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -3187,9 +3187,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -3279,11 +3279,11 @@ dependencies = [ "build_helper", "gimli 0.32.0", "libc", - "object 0.37.1", + "object 0.37.2", "regex", "serde_json", "similar", - "wasmparser 0.219.2", + "wasmparser 0.236.0", ] [[package]] @@ -3301,9 +3301,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -3367,6 +3367,7 @@ dependencies = [ "rand 0.9.2", "rand_xoshiro", "rustc_data_structures", + "rustc_error_messages", "rustc_hashes", "rustc_index", "rustc_macros", @@ -3569,7 +3570,7 @@ dependencies = [ "itertools", "libc", "measureme", - "object 0.37.1", + "object 0.37.2", "rustc-demangle", "rustc_abi", "rustc_ast", @@ -3607,7 +3608,7 @@ dependencies = [ "cc", "itertools", "libc", - "object 0.37.1", + "object 0.37.2", "pathdiff", "regex", "rustc_abi", @@ -3622,6 +3623,7 @@ dependencies = [ "rustc_hir", "rustc_incremental", "rustc_index", + "rustc_lint_defs", "rustc_macros", "rustc_metadata", "rustc_middle", @@ -3777,6 +3779,8 @@ dependencies = [ "icu_locid", "icu_provider_adapters", "intl-memoizer", + "rustc_ast", + "rustc_ast_pretty", "rustc_baked_icu_data", "rustc_data_structures", "rustc_macros", @@ -3793,22 +3797,18 @@ dependencies = [ "annotate-snippets 0.11.5", "derive_setters", "rustc_abi", - "rustc_ast", - "rustc_ast_pretty", "rustc_data_structures", "rustc_error_codes", "rustc_error_messages", "rustc_fluent_macro", "rustc_hashes", - "rustc_hir", + "rustc_hir_id", "rustc_index", "rustc_lexer", "rustc_lint_defs", "rustc_macros", "rustc_serialize", "rustc_span", - "rustc_target", - "rustc_type_ir", "serde", "serde_json", "termcolor", @@ -3889,13 +3889,16 @@ dependencies = [ name = "rustc_hir" version = "0.0.0" dependencies = [ + "bitflags", "odht", "rustc_abi", "rustc_arena", "rustc_ast", "rustc_ast_pretty", "rustc_data_structures", + "rustc_error_messages", "rustc_hashes", + "rustc_hir_id", "rustc_index", "rustc_macros", "rustc_serialize", @@ -3933,6 +3936,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "rustc_hir_id" +version = "0.0.0" +dependencies = [ + "rustc_data_structures", + "rustc_index", + "rustc_macros", + "rustc_serialize", + "rustc_span", +] + [[package]] name = "rustc_hir_pretty" version = "0.0.0" @@ -4124,7 +4138,7 @@ dependencies = [ "rustc_ast", "rustc_data_structures", "rustc_error_messages", - "rustc_hir", + "rustc_hir_id", "rustc_macros", "rustc_serialize", "rustc_span", @@ -4265,7 +4279,6 @@ dependencies = [ "rustc_errors", "rustc_fluent_macro", "rustc_graphviz", - "rustc_hir", "rustc_index", "rustc_macros", "rustc_middle", @@ -4307,7 +4320,6 @@ name = "rustc_monomorphize" version = "0.0.0" dependencies = [ "rustc_abi", - "rustc_ast", "rustc_data_structures", "rustc_errors", "rustc_fluent_macro", @@ -4316,7 +4328,6 @@ dependencies = [ "rustc_middle", "rustc_session", "rustc_span", - "rustc_symbol_mangling", "rustc_target", "serde", "serde_json", @@ -4519,6 +4530,7 @@ name = "rustc_resolve" version = "0.0.0" dependencies = [ "bitflags", + "indexmap", "itertools", "pulldown-cmark", "rustc_arena", @@ -4642,9 +4654,10 @@ name = "rustc_target" version = "0.0.0" dependencies = [ "bitflags", - "object 0.37.1", + "object 0.37.2", "rustc_abi", "rustc_data_structures", + "rustc_error_messages", "rustc_fs_util", "rustc_macros", "rustc_serialize", @@ -4708,7 +4721,6 @@ name = "rustc_traits" version = "0.0.0" dependencies = [ "rustc_data_structures", - "rustc_hir", "rustc_infer", "rustc_middle", "rustc_span", @@ -4763,6 +4775,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustc_ast_ir", "rustc_data_structures", + "rustc_error_messages", "rustc_index", "rustc_macros", "rustc_serialize", @@ -4810,6 +4823,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", + "stringdex", "tempfile", "threadpool", "tracing", @@ -4917,9 +4931,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ruzstd" @@ -4977,9 +4991,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" [[package]] name = "self_cell" @@ -5039,9 +5053,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -5223,6 +5237,15 @@ dependencies = [ "quote", ] +[[package]] +name = "stringdex" +version = "0.0.1-alpha4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2841fd43df5b1ff1b042e167068a1fe9b163dc93041eae56ab2296859013a9a0" +dependencies = [ + "stacker", +] + [[package]] name = "strsim" version = "0.11.1" @@ -5283,9 +5306,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.36.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" dependencies = [ "libc", "objc2-core-foundation", @@ -5505,7 +5528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", - "zerovec 0.11.2", + "zerovec 0.11.4", ] [[package]] @@ -6099,12 +6122,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.235.0" +version = "0.236.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a" +checksum = "3108979166ab0d3c7262d2e16a2190ffe784b2a5beb963edef154b5e8e07680b" dependencies = [ "leb128fmt", - "wasmparser 0.235.0", + "wasmparser 0.236.0", ] [[package]] @@ -6144,9 +6167,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.235.0" +version = "0.236.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" +checksum = "16d1eee846a705f6f3cb9d7b9f79b54583810f1fb57a1e3aea76d1742db2e3d2" dependencies = [ "bitflags", "indexmap", @@ -6155,22 +6178,22 @@ dependencies = [ [[package]] name = "wast" -version = "235.0.0" +version = "236.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eda4293f626c99021bb3a6fbe4fbbe90c0e31a5ace89b5f620af8925de72e13" +checksum = "11d6b6faeab519ba6fbf9b26add41617ca6f5553f99ebc33d876e591d2f4f3c6" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.1", - "wasm-encoder 0.235.0", + "wasm-encoder 0.236.0", ] [[package]] name = "wat" -version = "1.235.0" +version = "1.236.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e777e0327115793cb96ab220b98f85327ec3d11f34ec9e8d723264522ef206aa" +checksum = "cc31704322400f461f7f31a5f9190d5488aaeafb63ae69ad2b5888d2704dcb08" dependencies = [ "wast", ] @@ -6426,7 +6449,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6462,10 +6485,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -6850,9 +6874,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke 0.8.0", "zerofrom", diff --git a/RELEASES.md b/RELEASES.md index 1ae221774dc9..33abe45ce462 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,140 @@ +Version 1.89.0 (2025-08-07) +========================== + + + +Language +-------- +- [Stabilize explicitly inferred const arguments (`feature(generic_arg_infer)`)](https://github.com/rust-lang/rust/pull/141610) +- [Add a warn-by-default `mismatched_lifetime_syntaxes` lint.](https://github.com/rust-lang/rust/pull/138677) + This lint detects when the same lifetime is referred to by different syntax categories between function arguments and return values, which can be confusing to read, especially in unsafe code. + This lint supersedes the warn-by-default `elided_named_lifetimes` lint. +- [Expand `unpredictable_function_pointer_comparisons` to also lint on function pointer comparisons in external macros](https://github.com/rust-lang/rust/pull/134536) +- [Make the `dangerous_implicit_autorefs` lint deny-by-default](https://github.com/rust-lang/rust/pull/141661) +- [Stabilize the avx512 target features](https://github.com/rust-lang/rust/pull/138940) +- [Stabilize `kl` and `widekl` target features for x86](https://github.com/rust-lang/rust/pull/140766) +- [Stabilize `sha512`, `sm3` and `sm4` target features for x86](https://github.com/rust-lang/rust/pull/140767) +- [Stabilize LoongArch target features `f`, `d`, `frecipe`, `lasx`, `lbt`, `lsx`, and `lvz`](https://github.com/rust-lang/rust/pull/135015) +- [Remove `i128` and `u128` from `improper_ctypes_definitions`](https://github.com/rust-lang/rust/pull/137306) +- [Stabilize `repr128` (`#[repr(u128)]`, `#[repr(i128)]`)](https://github.com/rust-lang/rust/pull/138285) +- [Allow `#![doc(test(attr(..)))]` everywhere](https://github.com/rust-lang/rust/pull/140560) +- [Extend temporary lifetime extension to also go through tuple struct and tuple variant constructors](https://github.com/rust-lang/rust/pull/140593) +- [`extern "C"` functions on the `wasm32-unknown-unknown` target now have a standards compliant ABI](https://blog.rust-lang.org/2025/04/04/c-abi-changes-for-wasm32-unknown-unknown/) + + + +Compiler +-------- +- [Default to non-leaf frame pointers on aarch64-linux](https://github.com/rust-lang/rust/pull/140832) +- [Enable non-leaf frame pointers for Arm64EC Windows](https://github.com/rust-lang/rust/pull/140862) +- [Set Apple frame pointers by architecture](https://github.com/rust-lang/rust/pull/141797) + + + + +Platform Support +---------------- +- [Add new Tier-3 targets `loongarch32-unknown-none` and `loongarch32-unknown-none-softfloat`](https://github.com/rust-lang/rust/pull/142053) +- [`x86_64-apple-darwin` is in the process of being demoted to Tier 2 with host tools](https://github.com/rust-lang/rfcs/pull/3841) + +Refer to Rust's [platform support page][platform-support-doc] +for more information on Rust's tiered platform support. + +[platform-support-doc]: https://doc.rust-lang.org/rustc/platform-support.html + + + +Libraries +--------- +- [Specify the base path for `file!`](https://github.com/rust-lang/rust/pull/134442) +- [Allow storing `format_args!()` in a variable](https://github.com/rust-lang/rust/pull/140748) +- [Add `#[must_use]` to `[T; N]::map`](https://github.com/rust-lang/rust/pull/140957) +- [Implement `DerefMut` for `Lazy{Cell,Lock}`](https://github.com/rust-lang/rust/pull/129334) +- [Implement `Default` for `array::IntoIter`](https://github.com/rust-lang/rust/pull/141574) +- [Implement `Clone` for `slice::ChunkBy`](https://github.com/rust-lang/rust/pull/138016) +- [Implement `io::Seek` for `io::Take`](https://github.com/rust-lang/rust/pull/138023) + + + + +Stabilized APIs +--------------- + +- [`NonZero`](https://doc.rust-lang.org/stable/std/num/struct.NonZero.html) +- Many intrinsics for x86, not enumerated here + - [AVX512 intrinsics](https://github.com/rust-lang/rust/issues/111137) + - [`SHA512`, `SM3` and `SM4` intrinsics](https://github.com/rust-lang/rust/issues/126624) +- [`File::lock`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.lock) +- [`File::lock_shared`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.lock_shared) +- [`File::try_lock`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.try_lock) +- [`File::try_lock_shared`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.try_lock_shared) +- [`File::unlock`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.unlock) +- [`NonNull::from_ref`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.from_ref) +- [`NonNull::from_mut`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.from_mut) +- [`NonNull::without_provenance`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.without_provenance) +- [`NonNull::with_exposed_provenance`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.with_exposed_provenance) +- [`NonNull::expose_provenance`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.expose_provenance) +- [`OsString::leak`](https://doc.rust-lang.org/stable/std/ffi/struct.OsString.html#method.leak) +- [`PathBuf::leak`](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.leak) +- [`Result::flatten`](https://doc.rust-lang.org/stable/std/result/enum.Result.html#method.flatten) +- [`std::os::linux::net::TcpStreamExt::quickack`](https://doc.rust-lang.org/stable/std/os/linux/net/trait.TcpStreamExt.html#tymethod.quickack) +- [`std::os::linux::net::TcpStreamExt::set_quickack`](https://doc.rust-lang.org/stable/std/os/linux/net/trait.TcpStreamExt.html#tymethod.set_quickack) + +These previously stable APIs are now stable in const contexts: + +- [`<[T; N]>::as_mut_slice`](https://doc.rust-lang.org/stable/std/primitive.array.html#method.as_mut_slice) +- [`<[u8]>::eq_ignore_ascii_case`](https://doc.rust-lang.org/stable/std/primitive.slice.html#impl-%5Bu8%5D/method.eq_ignore_ascii_case) +- [`str::eq_ignore_ascii_case`](https://doc.rust-lang.org/stable/std/primitive.str.html#impl-str/method.eq_ignore_ascii_case) + + + + +Cargo +----- +- [`cargo fix` and `cargo clippy --fix` now default to the same Cargo target selection as other build commands.](https://github.com/rust-lang/cargo/pull/15192/) Previously it would apply to all targets (like binaries, examples, tests, etc.). The `--edition` flag still applies to all targets. +- [Stabilize doctest-xcompile.](https://github.com/rust-lang/cargo/pull/15462/) Doctests are now tested when cross-compiling. Just like other tests, it will use the [`runner` setting](https://doc.rust-lang.org/cargo/reference/config.html#targettriplerunner) to run the tests. If you need to disable tests for a target, you can use the [ignore doctest attribute](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#ignoring-targets) to specify the targets to ignore. + + + + +Rustdoc +----- +- [On mobile, make the sidebar full width and linewrap](https://github.com/rust-lang/rust/pull/139831). This makes long section and item names much easier to deal with on mobile. + + + + +Compatibility Notes +------------------- +- [Make `missing_fragment_specifier` an unconditional error](https://github.com/rust-lang/rust/pull/128425) +- [Enabling the `neon` target feature on `aarch64-unknown-none-softfloat` causes a warning](https://github.com/rust-lang/rust/pull/135160) because mixing code with and without that target feature is not properly supported by LLVM +- [Sized Hierarchy: Part I](https://github.com/rust-lang/rust/pull/137944) + - Introduces a small breaking change affecting `?Sized` bounds on impls on recursive types which contain associated type projections. It is not expected to affect any existing published crates. Can be fixed by refactoring the involved types or opting into the `sized_hierarchy` unstable feature. See the [FCP report](https://github.com/rust-lang/rust/pull/137944#issuecomment-2912207485) for a code example. +- The warn-by-default `elided_named_lifetimes` lint is [superseded by the warn-by-default `mismatched_lifetime_syntaxes` lint.](https://github.com/rust-lang/rust/pull/138677) +- [Error on recursive opaque types earlier in the type checker](https://github.com/rust-lang/rust/pull/139419) +- [Type inference side effects from requiring element types of array repeat expressions are `Copy` are now only available at the end of type checking](https://github.com/rust-lang/rust/pull/139635) +- [The deprecated accidentally-stable `std::intrinsics::{copy,copy_nonoverlapping,write_bytes}` are now proper intrinsics](https://github.com/rust-lang/rust/pull/139916). There are no debug assertions guarding against UB, and they cannot be coerced to function pointers. +- [Remove long-deprecated `std::intrinsics::drop_in_place`](https://github.com/rust-lang/rust/pull/140151) +- [Make well-formedness predicates no longer coinductive](https://github.com/rust-lang/rust/pull/140208) +- [Remove hack when checking impl method compatibility](https://github.com/rust-lang/rust/pull/140557) +- [Remove unnecessary type inference due to built-in trait object impls](https://github.com/rust-lang/rust/pull/141352) +- [Lint against "stdcall", "fastcall", and "cdecl" on non-x86-32 targets](https://github.com/rust-lang/rust/pull/141435) +- [Future incompatibility warnings relating to the never type (`!`) are now reported in dependencies](https://github.com/rust-lang/rust/pull/141937) +- [Ensure `std::ptr::copy_*` intrinsics also perform the static self-init checks](https://github.com/rust-lang/rust/pull/142575) +- [`extern "C"` functions on the `wasm32-unknown-unknown` target now have a standards compliant ABI](https://blog.rust-lang.org/2025/04/04/c-abi-changes-for-wasm32-unknown-unknown/) + + + +Internal Changes +---------------- + +These changes do not affect any public interfaces of Rust, but they represent +significant improvements to the performance or internals of rustc and related +tools. + +- [Correctly un-remap compiler sources paths with the `rustc-dev` component](https://github.com/rust-lang/rust/pull/142377) + + Version 1.88.0 (2025-06-26) ========================== @@ -1641,7 +1778,7 @@ Language - [Undeprecate lint `unstable_features` and make use of it in the compiler.](https://github.com/rust-lang/rust/pull/118639/) - [Make inductive cycles in coherence ambiguous always.](https://github.com/rust-lang/rust/pull/118649/) - [Get rid of type-driven traversal in const-eval interning](https://github.com/rust-lang/rust/pull/119044/), - only as a [future compatiblity lint](https://github.com/rust-lang/rust/pull/122204) for now. + only as a [future compatibility lint](https://github.com/rust-lang/rust/pull/122204) for now. - [Deny braced macro invocations in let-else.](https://github.com/rust-lang/rust/pull/119062/) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 31966af33012..4c18d5f8675d 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -9,7 +9,7 @@ # a custom configuration file can also be specified with `--config` to the build # system. # -# Note that the following are equivelent, for more details see . +# Note that the following are equivalent, for more details see . # # build.verbose = 1 # @@ -345,9 +345,9 @@ # want to use vendoring. See https://forge.rust-lang.org/infra/other-installation-methods.html#source-code. #build.vendor = if "is a tarball source" && "vendor" dir exists && ".cargo/config.toml" file exists { true } else { false } -# Typically the build system will build the Rust compiler twice. The second -# compiler, however, will simply use its own libraries to link against. If you -# would rather to perform a full bootstrap, compiling the compiler three times, +# If you build the compiler more than twice (stage3+) or the standard library more than once +# (stage 2+), the third compiler and second library will get uplifted from stage2 and stage1, +# respectively. If you would like to disable this uplifting, and rather perform a full bootstrap, # then you can set this option to true. # # This is only useful for verifying that rustc generates reproducible builds. @@ -482,7 +482,7 @@ # Use `--extra-checks=''` to temporarily disable all extra checks. # # Automatically enabled in the "tools" profile. -# Set to the empty string to force disable (recommeded for hdd systems). +# Set to the empty string to force disable (recommended for hdd systems). #build.tidy-extra-checks = "" # Indicates whether ccache is used when building certain artifacts (e.g. LLVM). @@ -740,11 +740,19 @@ # result (broken, compiling, testing) into this JSON file. #rust.save-toolstates = (path) -# This is an array of the codegen backends that will be compiled for the rustc -# that's being compiled. The default is to only build the LLVM codegen backend, -# and currently the only standard options supported are `"llvm"`, `"cranelift"` -# and `"gcc"`. The first backend in this list will be used as default by rustc -# when no explicit backend is specified. +# This array serves three distinct purposes: +# - Backends in this list will be automatically compiled and included in the sysroot of each +# rustc compiled by bootstrap. +# - The first backend in this list will be configured as the **default codegen backend** by each +# rustc compiled by bootstrap. In other words, if the first backend is e.g. cranelift, then when +# we build a stage 1 rustc, it will by default compile Rust programs using the Cranelift backend. +# This also means that stage 2 rustc would get built by the Cranelift backend. +# - Running `x dist` (without additional arguments, or with `--include-default-paths`) will produce +# a dist component/tarball for the Cranelift backend if it is included in this array. +# +# Note that the LLVM codegen backend is special and will always be built and distributed. +# +# Currently, the only standard options supported here are `"llvm"`, `"cranelift"` and `"gcc"`. #rust.codegen-backends = ["llvm"] # Indicates whether LLD will be compiled and made available in the sysroot for rustc to execute, and diff --git a/compiler/rustc_abi/Cargo.toml b/compiler/rustc_abi/Cargo.toml index 5f9afc46a1ac..83d96d8d04da 100644 --- a/compiler/rustc_abi/Cargo.toml +++ b/compiler/rustc_abi/Cargo.toml @@ -9,6 +9,7 @@ bitflags = "2.4.1" rand = { version = "0.9.0", default-features = false, optional = true } rand_xoshiro = { version = "0.7.0", optional = true } rustc_data_structures = { path = "../rustc_data_structures", optional = true } +rustc_error_messages = { path = "../rustc_error_messages", optional = true } rustc_hashes = { path = "../rustc_hashes" } rustc_index = { path = "../rustc_index", default-features = false } rustc_macros = { path = "../rustc_macros", optional = true } @@ -24,6 +25,7 @@ default = ["nightly", "randomize"] # without depending on rustc_data_structures, rustc_macros and rustc_serialize nightly = [ "dep:rustc_data_structures", + "dep:rustc_error_messages", "dep:rustc_macros", "dep:rustc_serialize", "dep:rustc_span", diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs index 29a3678abf3f..41d744e1946a 100644 --- a/compiler/rustc_abi/src/extern_abi.rs +++ b/compiler/rustc_abi/src/extern_abi.rs @@ -223,6 +223,9 @@ impl StableOrd for ExternAbi { const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = (); } +#[cfg(feature = "nightly")] +rustc_error_messages::into_diag_arg_using_display!(ExternAbi); + impl ExternAbi { /// An ABI "like Rust" /// diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index fdff18ffd471..de3e0e0c87f5 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -35,7 +35,6 @@ use rustc_span::{ByteSymbol, DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, use thin_vec::{ThinVec, thin_vec}; pub use crate::format::*; -use crate::ptr::P; use crate::token::{self, CommentKind, Delimiter}; use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream}; use crate::util::parser::{ExprPrecedence, Fixity}; @@ -225,7 +224,7 @@ pub struct PathSegment { /// `Some` means that parameter list is supplied (`Path`) /// but it can be empty (`Path<>`). /// `P` is used as a size optimization for the common case with no parameters. - pub args: Option>, + pub args: Option>, } // Succeeds if the path segment is arg-free and matches the given symbol. @@ -286,7 +285,7 @@ pub enum GenericArg { /// `'a` in `Foo<'a>`. Lifetime(#[visitable(extra = LifetimeCtxt::GenericArg)] Lifetime), /// `Bar` in `Foo`. - Type(P), + Type(Box), /// `1` in `Foo<1>`. Const(AnonConst), } @@ -328,15 +327,15 @@ impl AngleBracketedArg { } } -impl From for P { +impl From for Box { fn from(val: AngleBracketedArgs) -> Self { - P(GenericArgs::AngleBracketed(val)) + Box::new(GenericArgs::AngleBracketed(val)) } } -impl From for P { +impl From for Box { fn from(val: ParenthesizedArgs) -> Self { - P(GenericArgs::Parenthesized(val)) + Box::new(GenericArgs::Parenthesized(val)) } } @@ -350,7 +349,7 @@ pub struct ParenthesizedArgs { pub span: Span, /// `(A, B)` - pub inputs: ThinVec>, + pub inputs: ThinVec>, /// ```text /// Foo(A, B) -> C @@ -435,10 +434,10 @@ pub enum GenericParamKind { /// A lifetime definition (e.g., `'a: 'b + 'c + 'd`). Lifetime, Type { - default: Option>, + default: Option>, }, Const { - ty: P, + ty: Box, /// Span of the whole parameter definition, including default. span: Span, /// Optional default value for the const generic param. @@ -526,7 +525,7 @@ pub struct WhereBoundPredicate { /// Any generics from a `for` binding. pub bound_generic_params: ThinVec, /// The type being bounded. - pub bounded_ty: P, + pub bounded_ty: Box, /// Trait and lifetime bounds (`Clone + Send + 'static`). #[visitable(extra = BoundKind::Bound)] pub bounds: GenericBounds, @@ -548,8 +547,8 @@ pub struct WhereRegionPredicate { /// E.g., `T = int`. #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct WhereEqPredicate { - pub lhs_ty: P, - pub rhs_ty: P, + pub lhs_ty: Box, + pub rhs_ty: Box, } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] @@ -558,7 +557,7 @@ pub struct Crate { /// expansion placeholders or an unassigned value (`DUMMY_NODE_ID`) before that. pub id: NodeId, pub attrs: AttrVec, - pub items: ThinVec>, + pub items: ThinVec>, pub spans: ModSpans, pub is_placeholder: bool, } @@ -638,7 +637,7 @@ pub struct Pat { impl Pat { /// Attempt reparsing the pattern as a type. /// This is intended for use by diagnostics. - pub fn to_ty(&self) -> Option> { + pub fn to_ty(&self) -> Option> { let kind = match &self.kind { PatKind::Missing => unreachable!(), // In a type expression `_` is an inference variable. @@ -671,7 +670,7 @@ impl Pat { _ => return None, }; - Some(P(Ty { kind, id: self.id, span: self.span, tokens: None })) + Some(Box::new(Ty { kind, id: self.id, span: self.span, tokens: None })) } /// Walk top-down and call `it` in each place where a pattern occurs @@ -764,8 +763,8 @@ impl Pat { } } -impl From> for Pat { - fn from(value: P) -> Self { +impl From> for Pat { + fn from(value: Box) -> Self { *value } } @@ -780,7 +779,7 @@ pub struct PatField { /// The identifier for the field. pub ident: Ident, /// The pattern the field is destructured to. - pub pat: P, + pub pat: Box, pub is_shorthand: bool, pub attrs: AttrVec, pub id: NodeId, @@ -865,44 +864,44 @@ pub enum PatKind { /// or a unit struct/variant pattern, or a const pattern (in the last two cases the third /// field must be `None`). Disambiguation cannot be done with parser alone, so it happens /// during name resolution. - Ident(BindingMode, Ident, Option>), + Ident(BindingMode, Ident, Option>), /// A struct or struct variant pattern (e.g., `Variant {x, y, ..}`). - Struct(Option>, Path, ThinVec, PatFieldsRest), + Struct(Option>, Path, ThinVec, PatFieldsRest), /// A tuple struct/variant pattern (`Variant(x, y, .., z)`). - TupleStruct(Option>, Path, ThinVec>), + TupleStruct(Option>, Path, ThinVec>), /// An or-pattern `A | B | C`. /// Invariant: `pats.len() >= 2`. - Or(ThinVec>), + Or(ThinVec>), /// A possibly qualified path pattern. /// Unqualified path patterns `A::B::C` can legally refer to variants, structs, constants /// or associated constants. Qualified path patterns `::B::C`/`::B::C` can /// only legally refer to associated constants. - Path(Option>, Path), + Path(Option>, Path), /// A tuple pattern (`(a, b)`). - Tuple(ThinVec>), + Tuple(ThinVec>), /// A `box` pattern. - Box(P), + Box(Box), /// A `deref` pattern (currently `deref!()` macro-based syntax). - Deref(P), + Deref(Box), /// A reference pattern (e.g., `&mut (a, b)`). - Ref(P, Mutability), + Ref(Box, Mutability), /// A literal, const block or path. - Expr(P), + Expr(Box), /// A range pattern (e.g., `1...2`, `1..2`, `1..`, `..2`, `1..=2`, `..=2`). - Range(Option>, Option>, Spanned), + Range(Option>, Option>, Spanned), /// A slice pattern `[a, b, c]`. - Slice(ThinVec>), + Slice(ThinVec>), /// A rest pattern `..`. /// @@ -922,13 +921,13 @@ pub enum PatKind { Never, /// A guard pattern (e.g., `x if guard(x)`). - Guard(P, P), + Guard(Box, Box), /// Parentheses in patterns used for grouping (i.e., `(PAT)`). - Paren(P), + Paren(Box), /// A macro pattern; pre-expansion. - MacCall(P), + MacCall(Box), /// Placeholder for a pattern that wasn't syntactically well formed in some way. Err(ErrorGuaranteed), @@ -1223,22 +1222,22 @@ impl Stmt { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub enum StmtKind { /// A local (let) binding. - Let(P), + Let(Box), /// An item definition. - Item(P), + Item(Box), /// Expr without trailing semi-colon. - Expr(P), + Expr(Box), /// Expr with a trailing semi-colon. - Semi(P), + Semi(Box), /// Just a trailing semi-colon. Empty, /// Macro. - MacCall(P), + MacCall(Box), } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct MacCallStmt { - pub mac: P, + pub mac: Box, pub style: MacStmtStyle, pub attrs: AttrVec, pub tokens: Option, @@ -1262,8 +1261,8 @@ pub enum MacStmtStyle { pub struct Local { pub id: NodeId, pub super_: Option, - pub pat: P, - pub ty: Option>, + pub pat: Box, + pub ty: Option>, pub kind: LocalKind, pub span: Span, pub colon_sp: Option, @@ -1278,10 +1277,10 @@ pub enum LocalKind { Decl, /// Local declaration with an initializer. /// Example: `let x = y;` - Init(P), + Init(Box), /// Local declaration with an initializer and an `else` clause. /// Example: `let Some(x) = y else { return };` - InitElse(P, P), + InitElse(Box, Box), } impl LocalKind { @@ -1315,11 +1314,11 @@ impl LocalKind { pub struct Arm { pub attrs: AttrVec, /// Match arm pattern, e.g. `10` in `match foo { 10 => {}, _ => {} }`. - pub pat: P, + pub pat: Box, /// Match arm guard, e.g. `n > 10` in `match foo { n if n > 10 => {}, _ => {} }`. - pub guard: Option>, + pub guard: Option>, /// Match arm body. Omitted if the pattern is a never pattern. - pub body: Option>, + pub body: Option>, pub span: Span, pub id: NodeId, pub is_placeholder: bool, @@ -1332,7 +1331,7 @@ pub struct ExprField { pub id: NodeId, pub span: Span, pub ident: Ident, - pub expr: P, + pub expr: Box, pub is_shorthand: bool, pub is_placeholder: bool, } @@ -1357,7 +1356,7 @@ pub enum UnsafeSource { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct AnonConst { pub id: NodeId, - pub value: P, + pub value: Box, } /// An expression. @@ -1469,7 +1468,7 @@ impl Expr { } /// Attempts to reparse as `Ty` (for diagnostic purposes). - pub fn to_ty(&self) -> Option> { + pub fn to_ty(&self) -> Option> { let kind = match &self.kind { // Trivial conversions. ExprKind::Path(qself, path) => TyKind::Path(qself.clone(), path.clone()), @@ -1511,7 +1510,7 @@ impl Expr { _ => return None, }; - Some(P(Ty { kind, id: self.id, span: self.span, tokens: None })) + Some(Box::new(Ty { kind, id: self.id, span: self.span, tokens: None })) } pub fn precedence(&self) -> ExprPrecedence { @@ -1632,8 +1631,8 @@ impl Expr { } } -impl From> for Expr { - fn from(value: P) -> Self { +impl From> for Expr { + fn from(value: Box) -> Self { *value } } @@ -1645,8 +1644,8 @@ pub struct Closure { pub constness: Const, pub coroutine_kind: Option, pub movability: Movability, - pub fn_decl: P, - pub body: P, + pub fn_decl: Box, + pub body: Box, /// The span of the declaration block: 'move |...| -> ...' pub fn_decl_span: Span, /// The span of the argument block `|...|` @@ -1677,9 +1676,9 @@ pub struct MethodCall { /// The method name and its generic arguments, e.g. `foo::`. pub seg: PathSegment, /// The receiver, e.g. `x`. - pub receiver: P, + pub receiver: Box, /// The arguments, e.g. `a, b, c`. - pub args: ThinVec>, + pub args: ThinVec>, /// The span of the function, without the dot and receiver e.g. `foo::(a, b, c)`. pub span: Span, @@ -1688,7 +1687,7 @@ pub struct MethodCall { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub enum StructRest { /// `..x`. - Base(P), + Base(Box), /// `..`. Rest(Span), /// No trailing `..` or expression. @@ -1697,7 +1696,7 @@ pub enum StructRest { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct StructExpr { - pub qself: Option>, + pub qself: Option>, pub path: Path, pub fields: ThinVec, pub rest: StructRest, @@ -1707,7 +1706,7 @@ pub struct StructExpr { #[derive(Clone, Encodable, Decodable, Debug)] pub enum ExprKind { /// An array (e.g, `[a, b, c, d]`). - Array(ThinVec>), + Array(ThinVec>), /// Allow anonymous constants from an inline `const` block. ConstBlock(AnonConst), /// A function call. @@ -1716,90 +1715,90 @@ pub enum ExprKind { /// and the second field is the list of arguments. /// This also represents calling the constructor of /// tuple-like ADTs such as tuple structs and enum variants. - Call(P, ThinVec>), + Call(Box, ThinVec>), /// A method call (e.g., `x.foo::(a, b, c)`). MethodCall(Box), /// A tuple (e.g., `(a, b, c, d)`). - Tup(ThinVec>), + Tup(ThinVec>), /// A binary operation (e.g., `a + b`, `a * b`). - Binary(BinOp, P, P), + Binary(BinOp, Box, Box), /// A unary operation (e.g., `!x`, `*x`). - Unary(UnOp, P), + Unary(UnOp, Box), /// A literal (e.g., `1`, `"foo"`). Lit(token::Lit), /// A cast (e.g., `foo as f64`). - Cast(P, P), + Cast(Box, Box), /// A type ascription (e.g., `builtin # type_ascribe(42, usize)`). /// /// Usually not written directly in user code but /// indirectly via the macro `type_ascribe!(...)`. - Type(P, P), + Type(Box, Box), /// A `let pat = expr` expression that is only semantically allowed in the condition /// of `if` / `while` expressions. (e.g., `if let 0 = x { .. }`). /// /// `Span` represents the whole `let pat = expr` statement. - Let(P, P, Span, Recovered), + Let(Box, Box, Span, Recovered), /// An `if` block, with an optional `else` block. /// /// `if expr { block } else { expr }` /// /// If present, the "else" expr is always `ExprKind::Block` (for `else`) or /// `ExprKind::If` (for `else if`). - If(P, P, Option>), + If(Box, Box, Option>), /// A while loop, with an optional label. /// /// `'label: while expr { block }` - While(P, P, Option Foo { .. }` or `impl Trait for Foo { .. }`. - Impl(Box), + Impl(Impl), /// A macro invocation. /// /// E.g., `foo!(..)`. - MacCall(P), + MacCall(Box), /// A macro definition. MacroDef(Ident, MacroDef), /// A single delegation item (`reuse`). @@ -3881,7 +3884,7 @@ impl ItemKind { | Self::Union(_, generics, _) | Self::Trait(box Trait { generics, .. }) | Self::TraitAlias(_, generics, _) - | Self::Impl(box Impl { generics, .. }) => Some(generics), + | Self::Impl(Impl { generics, .. }) => Some(generics), _ => None, } } @@ -3908,7 +3911,7 @@ pub enum AssocItemKind { /// An associated type. Type(Box), /// A macro expanding to associated items. - MacCall(P), + MacCall(Box), /// An associated delegation item. Delegation(Box), /// An associated list or glob delegation item. @@ -3978,7 +3981,7 @@ pub enum ForeignItemKind { /// A foreign type. TyAlias(Box), /// A macro expanding to foreign items. - MacCall(P), + MacCall(Box), } impl ForeignItemKind { @@ -4041,7 +4044,7 @@ mod size_asserts { static_assert_size!(GenericArg, 24); static_assert_size!(GenericBound, 88); static_assert_size!(Generics, 40); - static_assert_size!(Impl, 136); + static_assert_size!(Impl, 64); static_assert_size!(Item, 144); static_assert_size!(ItemKind, 80); static_assert_size!(LitKind, 24); @@ -4054,6 +4057,7 @@ mod size_asserts { static_assert_size!(PathSegment, 24); static_assert_size!(Stmt, 32); static_assert_size!(StmtKind, 16); + static_assert_size!(TraitImplHeader, 80); static_assert_size!(Ty, 64); static_assert_size!(TyKind, 40); // tidy-alphabetical-end diff --git a/compiler/rustc_ast/src/ast_traits.rs b/compiler/rustc_ast/src/ast_traits.rs index 9d91f41d6c79..3d2477e5f033 100644 --- a/compiler/rustc_ast/src/ast_traits.rs +++ b/compiler/rustc_ast/src/ast_traits.rs @@ -5,7 +5,6 @@ use std::fmt; use std::marker::PhantomData; -use crate::ptr::P; use crate::tokenstream::LazyAttrTokenStream; use crate::{ Arm, AssocItem, AttrItem, AttrKind, AttrVec, Attribute, Block, Crate, Expr, ExprField, @@ -53,7 +52,7 @@ impl_has_node_id!( WherePredicate, ); -impl HasNodeId for P { +impl HasNodeId for Box { fn node_id(&self) -> NodeId { (**self).node_id() } @@ -119,7 +118,7 @@ impl HasTokens for Option { } } -impl HasTokens for P { +impl HasTokens for Box { fn tokens(&self) -> Option<&LazyAttrTokenStream> { (**self).tokens() } @@ -245,7 +244,7 @@ impl_has_attrs!( ); impl_has_attrs_none!(Attribute, AttrItem, Block, Pat, Path, Ty, Visibility); -impl HasAttrs for P { +impl HasAttrs for Box { const SUPPORTS_CUSTOM_INNER_ATTRS: bool = T::SUPPORTS_CUSTOM_INNER_ATTRS; fn attrs(&self) -> &[Attribute] { (**self).attrs() @@ -322,8 +321,8 @@ impl AstNodeWrapper { } // FIXME: remove after `stmt_expr_attributes` is stabilized. -impl From, Tag>> for AstNodeWrapper { - fn from(value: AstNodeWrapper, Tag>) -> Self { +impl From, Tag>> for AstNodeWrapper { + fn from(value: AstNodeWrapper, Tag>) -> Self { AstNodeWrapper { wrapped: *value.wrapped, tag: value.tag } } } diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 4348a4bb120e..6ada93b4c89b 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -13,7 +13,6 @@ use crate::ast::{ Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path, PathSegment, Safety, }; -use crate::ptr::P; use crate::token::{self, CommentKind, Delimiter, InvisibleOrigin, MetaVarKind, Token}; use crate::tokenstream::{ DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree, @@ -660,7 +659,7 @@ pub fn mk_attr_from_item( span: Span, ) -> Attribute { Attribute { - kind: AttrKind::Normal(P(NormalAttr { item, tokens })), + kind: AttrKind::Normal(Box::new(NormalAttr { item, tokens })), id: g.mk_attr_id(), style, span, @@ -710,7 +709,7 @@ pub fn mk_attr_name_value_str( span: Span, ) -> Attribute { let lit = token::Lit::new(token::Str, escape_string_symbol(val), None); - let expr = P(Expr { + let expr = Box::new(Expr { id: DUMMY_NODE_ID, kind: ExprKind::Lit(lit), span, diff --git a/compiler/rustc_ast/src/expand/autodiff_attrs.rs b/compiler/rustc_ast/src/expand/autodiff_attrs.rs index 2f918faaf752..33451f997483 100644 --- a/compiler/rustc_ast/src/expand/autodiff_attrs.rs +++ b/compiler/rustc_ast/src/expand/autodiff_attrs.rs @@ -7,7 +7,6 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use crate::expand::{Decodable, Encodable, HashStable_Generic}; -use crate::ptr::P; use crate::{Ty, TyKind}; /// Forward and Reverse Mode are well known names for automatic differentiation implementations. @@ -162,7 +161,7 @@ pub fn valid_ret_activity(mode: DiffMode, activity: DiffActivity) -> bool { /// since Duplicated expects a mutable ref/ptr and we would thus end up with a shadow value /// who is an indirect type, which doesn't match the primal scalar type. We can't prevent /// users here from marking scalars as Duplicated, due to type aliases. -pub fn valid_ty_for_activity(ty: &P, activity: DiffActivity) -> bool { +pub fn valid_ty_for_activity(ty: &Box, activity: DiffActivity) -> bool { use DiffActivity::*; // It's always allowed to mark something as Const, since we won't compute derivatives wrt. it. // Dual variants also support all types. diff --git a/compiler/rustc_ast/src/format.rs b/compiler/rustc_ast/src/format.rs index c2a1de60a981..cadebb2254d9 100644 --- a/compiler/rustc_ast/src/format.rs +++ b/compiler/rustc_ast/src/format.rs @@ -3,7 +3,6 @@ use rustc_macros::{Decodable, Encodable, Walkable}; use rustc_span::{Ident, Span, Symbol}; use crate::Expr; -use crate::ptr::P; use crate::token::LitKind; // Definitions: @@ -147,7 +146,7 @@ impl FormatArguments { #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct FormatArgument { pub kind: FormatArgumentKind, - pub expr: P, + pub expr: Box, } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] diff --git a/compiler/rustc_ast/src/lib.rs b/compiler/rustc_ast/src/lib.rs index 896d1e1148a9..f1951049b476 100644 --- a/compiler/rustc_ast/src/lib.rs +++ b/compiler/rustc_ast/src/lib.rs @@ -37,7 +37,6 @@ pub mod expand; pub mod format; pub mod mut_visit; pub mod node_id; -pub mod ptr; pub mod token; pub mod tokenstream; pub mod visit; diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 06708e2e703a..be8e1d22c9db 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -17,7 +17,6 @@ use smallvec::{SmallVec, smallvec}; use thin_vec::ThinVec; use crate::ast::*; -use crate::ptr::P; use crate::tokenstream::*; use crate::visit::{AssocCtxt, BoundKind, FnCtxt, LifetimeCtxt, VisitorResult, try_visit}; @@ -41,7 +40,7 @@ pub(crate) trait MutVisitable { fn visit_mut(&mut self, visitor: &mut V, extra: Self::Extra); } -impl MutVisitable for P +impl MutVisitable for Box where T: MutVisitable, { @@ -293,15 +292,15 @@ macro_rules! generate_flat_map_visitor_fns { } generate_flat_map_visitor_fns! { - visit_items, P, flat_map_item; - visit_foreign_items, P, flat_map_foreign_item; + visit_items, Box, flat_map_item; + visit_foreign_items, Box, flat_map_foreign_item; visit_generic_params, GenericParam, flat_map_generic_param; visit_stmts, Stmt, flat_map_stmt; - visit_exprs, P, filter_map_expr; + visit_exprs, Box, filter_map_expr; visit_expr_fields, ExprField, flat_map_expr_field; visit_pat_fields, PatField, flat_map_pat_field; visit_variants, Variant, flat_map_variant; - visit_assoc_items, P, flat_map_assoc_item, ctxt: AssocCtxt; + visit_assoc_items, Box, flat_map_assoc_item, ctxt: AssocCtxt; visit_where_predicates, WherePredicate, flat_map_where_predicate; visit_params, Param, flat_map_param; visit_field_defs, FieldDef, flat_map_field_def; @@ -333,12 +332,12 @@ generate_walk_flat_map_fns! { walk_flat_map_where_predicate(WherePredicate) => visit_where_predicate; walk_flat_map_field_def(FieldDef) => visit_field_def; walk_flat_map_expr_field(ExprField) => visit_expr_field; - walk_flat_map_item(P) => visit_item; - walk_flat_map_foreign_item(P) => visit_foreign_item; - walk_flat_map_assoc_item(P, ctxt: AssocCtxt) => visit_assoc_item; + walk_flat_map_item(Box) => visit_item; + walk_flat_map_foreign_item(Box) => visit_foreign_item; + walk_flat_map_assoc_item(Box, ctxt: AssocCtxt) => visit_assoc_item; } -pub fn walk_filter_map_expr(vis: &mut T, mut e: P) -> Option> { +pub fn walk_filter_map_expr(vis: &mut T, mut e: Box) -> Option> { vis.visit_expr(&mut e); Some(e) } diff --git a/compiler/rustc_ast/src/ptr.rs b/compiler/rustc_ast/src/ptr.rs deleted file mode 100644 index fffeab8bbca6..000000000000 --- a/compiler/rustc_ast/src/ptr.rs +++ /dev/null @@ -1,11 +0,0 @@ -/// A pointer type that uniquely owns a heap allocation of type T. -/// -/// This used to be its own type, but now it's just a typedef for `Box` and we are planning to -/// remove it soon. -pub type P = Box; - -/// Construct a `P` from a `T` value. -#[allow(non_snake_case)] -pub fn P(value: T) -> P { - Box::new(value) -} diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index e55399adfb85..f4f35a4d2ee0 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -907,6 +907,12 @@ impl TokenTreeCursor { pub fn bump(&mut self) { self.index += 1; } + + // For skipping ahead in rare circumstances. + #[inline] + pub fn bump_to_end(&mut self) { + self.index = self.stream.len(); + } } /// A `TokenStream` cursor that produces `Token`s. It's a bit odd that diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index ab15cb28fa12..68b3d2b03686 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -20,7 +20,6 @@ use rustc_span::{Ident, Span, Symbol}; use thin_vec::ThinVec; use crate::ast::*; -use crate::ptr::P; use crate::tokenstream::DelimSpan; #[derive(Copy, Clone, Debug, PartialEq)] @@ -82,7 +81,7 @@ pub(crate) trait Visitable<'a, V: Visitor<'a>> { fn visit(&'a self, visitor: &mut V, extra: Self::Extra) -> V::Result; } -impl<'a, V: Visitor<'a>, T: ?Sized> Visitable<'a, V> for P +impl<'a, V: Visitor<'a>, T: ?Sized> Visitable<'a, V> for Box where T: Visitable<'a, V>, { @@ -322,7 +321,7 @@ macro_rules! common_visitor_and_walkers { Fn(FnCtxt, &'a $($mut)? Visibility, &'a $($mut)? Fn), /// E.g., `|x, y| body`. - Closure(&'a $($mut)? ClosureBinder, &'a $($mut)? Option, &'a $($mut)? P, &'a $($mut)? P), + Closure(&'a $($mut)? ClosureBinder, &'a $($mut)? Option, &'a $($mut)? Box, &'a $($mut)? Box), } impl<'a> FnKind<'a> { @@ -390,9 +389,9 @@ macro_rules! common_visitor_and_walkers { ThinVec<(NodeId, Path)>, ThinVec, ThinVec, - ThinVec>, - ThinVec>, - ThinVec>, + ThinVec>, + ThinVec>, + ThinVec>, ); // This macro generates `impl Visitable` and `impl MutVisitable` that forward to `Walkable` @@ -676,11 +675,11 @@ macro_rules! common_visitor_and_walkers { // Do nothing. } - fn flat_map_foreign_item(&mut self, ni: P) -> SmallVec<[P; 1]> { + fn flat_map_foreign_item(&mut self, ni: Box) -> SmallVec<[Box; 1]> { walk_flat_map_foreign_item(self, ni) } - fn flat_map_item(&mut self, i: P) -> SmallVec<[P; 1]> { + fn flat_map_item(&mut self, i: Box) -> SmallVec<[Box; 1]> { walk_flat_map_item(self, i) } @@ -690,9 +689,9 @@ macro_rules! common_visitor_and_walkers { fn flat_map_assoc_item( &mut self, - i: P, + i: Box, ctxt: AssocCtxt, - ) -> SmallVec<[P; 1]> { + ) -> SmallVec<[Box; 1]> { walk_flat_map_assoc_item(self, i, ctxt) } @@ -704,7 +703,7 @@ macro_rules! common_visitor_and_walkers { walk_flat_map_arm(self, arm) } - fn filter_map_expr(&mut self, e: P) -> Option> { + fn filter_map_expr(&mut self, e: Box) -> Option> { walk_filter_map_expr(self, e) } @@ -930,8 +929,13 @@ macro_rules! common_visitor_and_walkers { } impl_walkable!(|&$($mut)? $($lt)? self: Impl, vis: &mut V| { - let Impl { defaultness, safety, generics, constness, polarity, of_trait, self_ty, items } = self; - visit_visitable!($($mut)? vis, defaultness, safety, generics, constness, polarity, of_trait, self_ty); + let Impl { generics, of_trait, self_ty, items } = self; + try_visit!(vis.visit_generics(generics)); + if let Some(box of_trait) = of_trait { + let TraitImplHeader { defaultness, safety, constness, polarity, trait_ref } = of_trait; + visit_visitable!($($mut)? vis, defaultness, safety, constness, polarity, trait_ref); + } + try_visit!(vis.visit_ty(self_ty)); visit_visitable_with!($($mut)? vis, items, AssocCtxt::Impl { of_trait: of_trait.is_some() }); V::Result::output() }); @@ -1144,15 +1148,15 @@ macro_rules! generate_list_visit_fns { } generate_list_visit_fns! { - visit_items, P, visit_item; - visit_foreign_items, P, visit_foreign_item; + visit_items, Box, visit_item; + visit_foreign_items, Box, visit_foreign_item; visit_generic_params, GenericParam, visit_generic_param; visit_stmts, Stmt, visit_stmt; - visit_exprs, P, visit_expr; + visit_exprs, Box, visit_expr; visit_expr_fields, ExprField, visit_expr_field; visit_pat_fields, PatField, visit_pat_field; visit_variants, Variant, visit_variant; - visit_assoc_items, P, visit_assoc_item, ctxt: AssocCtxt; + visit_assoc_items, Box, visit_assoc_item, ctxt: AssocCtxt; visit_where_predicates, WherePredicate, visit_where_predicate; visit_params, Param, visit_param; visit_field_defs, FieldDef, visit_field_def; diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index af279e07acc6..d44faad017ee 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -48,6 +48,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { | asm::InlineAsmArch::Arm64EC | asm::InlineAsmArch::RiscV32 | asm::InlineAsmArch::RiscV64 + | asm::InlineAsmArch::LoongArch32 | asm::InlineAsmArch::LoongArch64 | asm::InlineAsmArch::S390x ); diff --git a/compiler/rustc_ast_lowering/src/block.rs b/compiler/rustc_ast_lowering/src/block.rs index 2cc07694afbc..f1e810a8b9ea 100644 --- a/compiler/rustc_ast_lowering/src/block.rs +++ b/compiler/rustc_ast_lowering/src/block.rs @@ -1,5 +1,6 @@ use rustc_ast::{Block, BlockCheckMode, Local, LocalKind, Stmt, StmtKind}; use rustc_hir as hir; +use rustc_hir::Target; use rustc_span::sym; use smallvec::SmallVec; @@ -109,7 +110,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { }; let span = self.lower_span(l.span); let source = hir::LocalSource::Normal; - self.lower_attrs(hir_id, &l.attrs, l.span); + self.lower_attrs(hir_id, &l.attrs, l.span, Target::Statement); self.arena.alloc(hir::LetStmt { hir_id, super_, ty, pat, init, els, span, source }) } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 1245d4897547..cbd17d66b754 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1,14 +1,13 @@ use std::ops::ControlFlow; use std::sync::Arc; -use rustc_ast::ptr::P as AstP; use rustc_ast::*; use rustc_ast_pretty::pprust::expr_to_string; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{HirId, find_attr}; +use rustc_hir::{HirId, Target, find_attr}; use rustc_middle::span_bug; use rustc_middle::ty::TyCtxt; use rustc_session::errors::report_lit_error; @@ -53,7 +52,7 @@ impl<'v> rustc_ast::visit::Visitor<'v> for WillCreateDefIdsVisitor { } impl<'hir> LoweringContext<'_, 'hir> { - fn lower_exprs(&mut self, exprs: &[AstP]) -> &'hir [hir::Expr<'hir>] { + fn lower_exprs(&mut self, exprs: &[Box]) -> &'hir [hir::Expr<'hir>] { self.arena.alloc_from_iter(exprs.iter().map(|x| self.lower_expr_mut(x))) } @@ -75,7 +74,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if !e.attrs.is_empty() { let old_attrs = self.attrs.get(&ex.hir_id.local_id).copied().unwrap_or(&[]); let new_attrs = self - .lower_attrs_vec(&e.attrs, e.span, ex.hir_id) + .lower_attrs_vec(&e.attrs, e.span, ex.hir_id, Target::from_expr(e)) .into_iter() .chain(old_attrs.iter().cloned()); let new_attrs = &*self.arena.alloc_from_iter(new_attrs); @@ -98,7 +97,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } let expr_hir_id = self.lower_node_id(e.id); - self.lower_attrs(expr_hir_id, &e.attrs, e.span); + let attrs = self.lower_attrs(expr_hir_id, &e.attrs, e.span, Target::from_expr(e)); let kind = match &e.kind { ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)), @@ -232,10 +231,10 @@ impl<'hir> LoweringContext<'_, 'hir> { *fn_arg_span, ), None => self.lower_expr_closure( + attrs, binder, *capture_clause, e.id, - expr_hir_id, *constness, *movability, fn_decl, @@ -455,7 +454,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_legacy_const_generics( &mut self, mut f: Expr, - args: ThinVec>, + args: ThinVec>, legacy_args_idx: &[usize], ) -> hir::ExprKind<'hir> { let ExprKind::Path(None, path) = &mut f.kind else { @@ -495,7 +494,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.create_def(node_id, None, DefKind::AnonConst, f.span); let mut visitor = WillCreateDefIdsVisitor {}; let const_value = if let ControlFlow::Break(span) = visitor.visit_expr(&arg) { - AstP(Expr { + Box::new(Expr { id: self.next_node_id(), kind: ExprKind::Err(invalid_expr_error(self.tcx, span)), span: f.span, @@ -516,7 +515,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // Add generic args to the last element of the path. let last_segment = path.segments.last_mut().unwrap(); assert!(last_segment.args.is_none()); - last_segment.args = Some(AstP(GenericArgs::AngleBracketed(AngleBracketedArgs { + last_segment.args = Some(Box::new(GenericArgs::AngleBracketed(AngleBracketedArgs { span: DUMMY_SP, args: generic_args, }))); @@ -640,7 +639,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let guard = arm.guard.as_ref().map(|cond| self.lower_expr(cond)); let hir_id = self.next_id(); let span = self.lower_span(arm.span); - self.lower_attrs(hir_id, &arm.attrs, arm.span); + self.lower_attrs(hir_id, &arm.attrs, arm.span, Target::Arm); let is_never_pattern = pat.is_never_pattern(); // We need to lower the body even if it's unneeded for never pattern in match, // ensure that we can get HirId for DefId if need (issue #137708). @@ -812,7 +811,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_attrs( inner_hir_id, &[Attribute { - kind: AttrKind::Normal(ptr::P(NormalAttr::from_ident(Ident::new( + kind: AttrKind::Normal(Box::new(NormalAttr::from_ident(Ident::new( sym::track_caller, span, )))), @@ -821,6 +820,7 @@ impl<'hir> LoweringContext<'_, 'hir> { span: unstable_span, }], span, + Target::Fn, ); } } @@ -1052,10 +1052,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_expr_closure( &mut self, + attrs: &[rustc_hir::Attribute], binder: &ClosureBinder, capture_clause: CaptureBy, closure_id: NodeId, - closure_hir_id: hir::HirId, constness: Const, movability: Movability, decl: &FnDecl, @@ -1067,15 +1067,9 @@ impl<'hir> LoweringContext<'_, 'hir> { let (binder_clause, generic_params) = self.lower_closure_binder(binder); let (body_id, closure_kind) = self.with_new_scopes(fn_decl_span, move |this| { - let mut coroutine_kind = if this - .attrs - .get(&closure_hir_id.local_id) - .is_some_and(|attrs| attrs.iter().any(|attr| attr.has_name(sym::coroutine))) - { - Some(hir::CoroutineKind::Coroutine(Movability::Movable)) - } else { - None - }; + + let mut coroutine_kind = find_attr!(attrs, AttributeKind::Coroutine(_) => hir::CoroutineKind::Coroutine(Movability::Movable)); + // FIXME(contracts): Support contracts on closures? let body_id = this.lower_fn_body(decl, None, |this| { this.coroutine_kind = coroutine_kind; @@ -1291,7 +1285,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn extract_tuple_struct_path<'a>( &mut self, expr: &'a Expr, - ) -> Option<(&'a Option>, &'a Path)> { + ) -> Option<(&'a Option>, &'a Path)> { if let ExprKind::Path(qself, path) = &expr.kind { // Does the path resolve to something disallowed in a tuple struct/variant pattern? if let Some(partial_res) = self.resolver.get_partial_res(expr.id) { @@ -1313,7 +1307,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn extract_unit_struct_path<'a>( &mut self, expr: &'a Expr, - ) -> Option<(&'a Option>, &'a Path)> { + ) -> Option<(&'a Option>, &'a Path)> { if let ExprKind::Path(qself, path) = &expr.kind { // Does the path resolve to something disallowed in a unit struct/variant pattern? if let Some(partial_res) = self.resolver.get_partial_res(expr.id) { @@ -1484,7 +1478,7 @@ impl<'hir> LoweringContext<'_, 'hir> { /// Each sub-assignment is recorded in `assignments`. fn destructure_sequence( &mut self, - elements: &[AstP], + elements: &[Box], ctx: &str, eq_sign_span: Span, assignments: &mut Vec>, @@ -1661,7 +1655,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_expr_field(&mut self, f: &ExprField) -> hir::ExprField<'hir> { let hir_id = self.lower_node_id(f.id); - self.lower_attrs(hir_id, &f.attrs, f.span); + self.lower_attrs(hir_id, &f.attrs, f.span, Target::ExprField); hir::ExprField { hir_id, ident: self.lower_ident(f.ident), @@ -1917,7 +1911,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // // Also, add the attributes to the outer returned expr node. let expr = self.expr_drop_temps_mut(for_span, match_expr); - self.lower_attrs(expr.hir_id, &e.attrs, e.span); + self.lower_attrs(expr.hir_id, &e.attrs, e.span, Target::from_expr(e)); expr } @@ -1974,7 +1968,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let val_ident = Ident::with_dummy_span(sym::val); let (val_pat, val_pat_nid) = self.pat_ident(span, val_ident); let val_expr = self.expr_ident(span, val_ident, val_pat_nid); - self.lower_attrs(val_expr.hir_id, &attrs, span); + self.lower_attrs(val_expr.hir_id, &attrs, span, Target::Expression); let continue_pat = self.pat_cf_continue(unstable_span, val_pat); self.arm(continue_pat, val_expr) }; @@ -2005,7 +1999,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let ret_expr = self.checked_return(Some(from_residual_expr)); self.arena.alloc(self.expr(try_span, ret_expr)) }; - self.lower_attrs(ret_expr.hir_id, &attrs, span); + self.lower_attrs(ret_expr.hir_id, &attrs, span, Target::Expression); let break_pat = self.pat_cf_break(try_span, residual_local); self.arm(break_pat, ret_expr) diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs index 5b63206d7d62..5f8933aa2beb 100644 --- a/compiler/rustc_ast_lowering/src/index.rs +++ b/compiler/rustc_ast_lowering/src/index.rs @@ -311,7 +311,7 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> { ); self.with_parent(const_arg.hir_id, |this| { - intravisit::walk_ambig_const_arg(this, const_arg); + intravisit::walk_const_arg(this, const_arg); }); } diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 9f54af575280..cd0f9f2403e3 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1,12 +1,11 @@ use rustc_abi::ExternAbi; -use rustc_ast::ptr::P; use rustc_ast::visit::AssocCtxt; use rustc_ast::*; use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err}; use rustc_hir::attrs::AttributeKind; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; -use rustc_hir::{self as hir, HirId, LifetimeSource, PredicateOrigin, find_attr}; +use rustc_hir::{self as hir, HirId, LifetimeSource, PredicateOrigin, Target, find_attr}; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::span_bug; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; @@ -81,7 +80,7 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> { self.with_lctx(CRATE_NODE_ID, |lctx| { let module = lctx.lower_mod(&c.items, &c.spans); // FIXME(jdonszelman): is dummy span ever a problem here? - lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, DUMMY_SP); + lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, DUMMY_SP, Target::Crate); hir::OwnerNode::Crate(module) }) } @@ -102,7 +101,7 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> { impl<'hir> LoweringContext<'_, 'hir> { pub(super) fn lower_mod( &mut self, - items: &[P], + items: &[Box], spans: &ModSpans, ) -> &'hir hir::Mod<'hir> { self.arena.alloc(hir::Mod { @@ -137,7 +136,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { let vis_span = self.lower_span(i.vis.span); let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id); - let attrs = self.lower_attrs(hir_id, &i.attrs, i.span); + let attrs = self.lower_attrs(hir_id, &i.attrs, i.span, Target::from_ast_item(i)); let kind = self.lower_item_kind(i.span, i.id, hir_id, attrs, vis_span, &i.kind); let item = hir::Item { owner_id: hir_id.expect_owner(), @@ -252,7 +251,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::Mod(_, ident, mod_kind) => { let ident = self.lower_ident(*ident); match mod_kind { - ModKind::Loaded(items, _, spans, _) => { + ModKind::Loaded(items, _, spans) => { hir::ItemKind::Mod(ident, self.lower_mod(items, spans)) } ModKind::Unloaded => panic!("`mod` items should have been loaded by now"), @@ -341,13 +340,9 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::ItemKind::Union(ident, generics, vdata) } - ItemKind::Impl(box Impl { - safety, - polarity, - defaultness, - constness, + ItemKind::Impl(Impl { generics: ast_generics, - of_trait: trait_ref, + of_trait, self_ty: ty, items: impl_items, }) => { @@ -365,54 +360,30 @@ impl<'hir> LoweringContext<'_, 'hir> { // lifetime to be added, but rather a reference to a // parent lifetime. let itctx = ImplTraitContext::Universal; - let (generics, (trait_ref, lowered_ty)) = + let (generics, (of_trait, lowered_ty)) = self.lower_generics(ast_generics, id, itctx, |this| { - let modifiers = TraitBoundModifiers { - constness: BoundConstness::Never, - asyncness: BoundAsyncness::Normal, - // we don't use this in bound lowering - polarity: BoundPolarity::Positive, - }; - - let trait_ref = trait_ref.as_ref().map(|trait_ref| { - this.lower_trait_ref( - modifiers, - trait_ref, - ImplTraitContext::Disallowed(ImplTraitPosition::Trait), - ) - }); + let of_trait = of_trait + .as_deref() + .map(|of_trait| this.lower_trait_impl_header(of_trait)); let lowered_ty = this.lower_ty( ty, ImplTraitContext::Disallowed(ImplTraitPosition::ImplSelf), ); - (trait_ref, lowered_ty) + (of_trait, lowered_ty) }); let new_impl_items = self .arena .alloc_from_iter(impl_items.iter().map(|item| self.lower_impl_item_ref(item))); - // `defaultness.has_value()` is never called for an `impl`, always `true` in order - // to not cause an assertion failure inside the `lower_defaultness` function. - let has_val = true; - let (defaultness, defaultness_span) = self.lower_defaultness(*defaultness, has_val); - let polarity = match polarity { - ImplPolarity::Positive => ImplPolarity::Positive, - ImplPolarity::Negative(s) => ImplPolarity::Negative(self.lower_span(*s)), - }; - hir::ItemKind::Impl(self.arena.alloc(hir::Impl { - constness: self.lower_constness(*constness), - safety: self.lower_safety(*safety, hir::Safety::Safe), - polarity, - defaultness, - defaultness_span, + hir::ItemKind::Impl(hir::Impl { generics, - of_trait: trait_ref, + of_trait, self_ty: lowered_ty, items: new_impl_items, - })) + }) } ItemKind::Trait(box Trait { constness, @@ -462,17 +433,17 @@ impl<'hir> LoweringContext<'_, 'hir> { } ItemKind::MacroDef(ident, MacroDef { body, macro_rules }) => { let ident = self.lower_ident(*ident); - let body = P(self.lower_delim_args(body)); + let body = Box::new(self.lower_delim_args(body)); let def_id = self.local_def_id(id); let def_kind = self.tcx.def_kind(def_id); - let DefKind::Macro(macro_kind) = def_kind else { + let DefKind::Macro(macro_kinds) = def_kind else { unreachable!( "expected DefKind::Macro for macro item, found {}", def_kind.descr(def_id.to_def_id()) ); }; let macro_def = self.arena.alloc(ast::MacroDef { body, macro_rules: *macro_rules }); - hir::ItemKind::Macro(ident, macro_def, macro_kind) + hir::ItemKind::Macro(ident, macro_def, macro_kinds) } ItemKind::Delegation(box delegation) => { let delegation_results = self.lower_delegation(delegation, id, false); @@ -650,7 +621,8 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> { let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id); let owner_id = hir_id.expect_owner(); - let attrs = self.lower_attrs(hir_id, &i.attrs, i.span); + let attrs = + self.lower_attrs(hir_id, &i.attrs, i.span, Target::from_foreign_item_kind(&i.kind)); let (ident, kind) = match &i.kind { ForeignItemKind::Fn(box Fn { sig, ident, generics, define_opaque, .. }) => { let fdec = &sig.decl; @@ -719,7 +691,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_variant(&mut self, item_kind: &ItemKind, v: &Variant) -> hir::Variant<'hir> { let hir_id = self.lower_node_id(v.id); - self.lower_attrs(hir_id, &v.attrs, v.span); + self.lower_attrs(hir_id, &v.attrs, v.span, Target::Variant); hir::Variant { hir_id, def_id: self.local_def_id(v.id), @@ -802,7 +774,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) -> hir::FieldDef<'hir> { let ty = self.lower_ty(&f.ty, ImplTraitContext::Disallowed(ImplTraitPosition::FieldTy)); let hir_id = self.lower_node_id(f.id); - self.lower_attrs(hir_id, &f.attrs, f.span); + self.lower_attrs(hir_id, &f.attrs, f.span, Target::Field); hir::FieldDef { span: self.lower_span(f.span), hir_id, @@ -821,7 +793,12 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id); - let attrs = self.lower_attrs(hir_id, &i.attrs, i.span); + let attrs = self.lower_attrs( + hir_id, + &i.attrs, + i.span, + Target::from_assoc_item_kind(&i.kind, AssocCtxt::Trait), + ); let trait_item_def_id = hir_id.expect_owner(); let (ident, generics, kind, has_default) = match &i.kind { @@ -983,6 +960,44 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::Err(guar)) } + fn lower_trait_impl_header( + &mut self, + trait_impl_header: &TraitImplHeader, + ) -> &'hir hir::TraitImplHeader<'hir> { + let TraitImplHeader { constness, safety, polarity, defaultness, ref trait_ref } = + *trait_impl_header; + let constness = self.lower_constness(constness); + let safety = self.lower_safety(safety, hir::Safety::Safe); + let polarity = match polarity { + ImplPolarity::Positive => ImplPolarity::Positive, + ImplPolarity::Negative(s) => ImplPolarity::Negative(self.lower_span(s)), + }; + // `defaultness.has_value()` is never called for an `impl`, always `true` in order + // to not cause an assertion failure inside the `lower_defaultness` function. + let has_val = true; + let (defaultness, defaultness_span) = self.lower_defaultness(defaultness, has_val); + let modifiers = TraitBoundModifiers { + constness: BoundConstness::Never, + asyncness: BoundAsyncness::Normal, + // we don't use this in bound lowering + polarity: BoundPolarity::Positive, + }; + let trait_ref = self.lower_trait_ref( + modifiers, + trait_ref, + ImplTraitContext::Disallowed(ImplTraitPosition::Trait), + ); + + self.arena.alloc(hir::TraitImplHeader { + constness, + safety, + polarity, + defaultness, + defaultness_span, + trait_ref, + }) + } + fn lower_impl_item( &mut self, i: &AssocItem, @@ -992,7 +1007,12 @@ impl<'hir> LoweringContext<'_, 'hir> { let has_value = true; let (defaultness, _) = self.lower_defaultness(i.kind.defaultness(), has_value); let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id); - let attrs = self.lower_attrs(hir_id, &i.attrs, i.span); + let attrs = self.lower_attrs( + hir_id, + &i.attrs, + i.span, + Target::from_assoc_item_kind(&i.kind, AssocCtxt::Impl { of_trait: is_in_trait_impl }), + ); let (ident, (generics, kind)) = match &i.kind { AssocItemKind::Const(box ConstItem { @@ -1162,7 +1182,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param(&mut self, param: &Param) -> hir::Param<'hir> { let hir_id = self.lower_node_id(param.id); - self.lower_attrs(hir_id, ¶m.attrs, param.span); + self.lower_attrs(hir_id, ¶m.attrs, param.span, Target::Param); hir::Param { hir_id, pat: self.lower_pat(¶m.pat), @@ -1842,7 +1862,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) -> hir::WherePredicate<'hir> { let hir_id = self.lower_node_id(pred.id); let span = self.lower_span(pred.span); - self.lower_attrs(hir_id, &pred.attrs, span); + self.lower_attrs(hir_id, &pred.attrs, span, Target::WherePredicate); let kind = self.arena.alloc(match &pred.kind { WherePredicateKind::BoundPredicate(WhereBoundPredicate { bound_generic_params, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 189c82b614c2..70595391b85b 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -54,7 +54,7 @@ use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId}; use rustc_hir::lints::DelayedLint; use rustc_hir::{ self as hir, AngleBrackets, ConstArg, GenericArg, HirId, ItemLocalMap, LifetimeSource, - LifetimeSyntax, ParamName, TraitCandidate, + LifetimeSyntax, ParamName, Target, TraitCandidate, }; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_macros::extension; @@ -296,6 +296,7 @@ enum RelaxedBoundPolicy<'a> { enum RelaxedBoundForbiddenReason { TraitObjectTy, SuperTrait, + AssocTyBounds, LateBoundVarsInScope, } @@ -675,7 +676,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let bodies = SortedMap::from_presorted_elements(bodies); // Don't hash unless necessary, because it's expensive. - let (opt_hash_including_bodies, attrs_hash, delayed_lints_hash) = + let rustc_middle::hir::Hashes { opt_hash_including_bodies, attrs_hash, delayed_lints_hash } = self.tcx.hash_owner_nodes(node, &bodies, &attrs, &delayed_lints, define_opaque); let num_nodes = self.item_local_id_counter.as_usize(); let (nodes, parenting) = index::index_hir(self.tcx, node, &bodies, num_nodes); @@ -942,11 +943,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { id: HirId, attrs: &[Attribute], target_span: Span, + target: Target, ) -> &'hir [hir::Attribute] { if attrs.is_empty() { &[] } else { - let lowered_attrs = self.lower_attrs_vec(attrs, self.lower_span(target_span), id); + let lowered_attrs = + self.lower_attrs_vec(attrs, self.lower_span(target_span), id, target); assert_eq!(id.owner, self.current_hir_id_owner); let ret = self.arena.alloc_from_iter(lowered_attrs); @@ -971,12 +974,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { attrs: &[Attribute], target_span: Span, target_hir_id: HirId, + target: Target, ) -> Vec { let l = self.span_lowerer(); self.attribute_parser.parse_attribute_list( attrs, target_span, target_hir_id, + target, OmitDoc::Lower, |s| l.lower(s), |l| { @@ -1109,9 +1114,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { &*self.arena.alloc(self.ty(constraint.span, hir::TyKind::Err(guar))); hir::AssocItemConstraintKind::Equality { term: err_ty.into() } } else { - // FIXME(#135229): These should be forbidden! - let bounds = - self.lower_param_bounds(bounds, RelaxedBoundPolicy::Allowed, itctx); + let bounds = self.lower_param_bounds( + bounds, + RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::AssocTyBounds), + itctx, + ); hir::AssocItemConstraintKind::Bound { bounds } } } @@ -1217,7 +1224,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { fn lower_path_ty( &mut self, t: &Ty, - qself: &Option>, + qself: &Option>, path: &Path, param_mode: ParamMode, itctx: ImplTraitContext, @@ -1939,7 +1946,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let (name, kind) = self.lower_generic_param_kind(param, source); let hir_id = self.lower_node_id(param.id); - self.lower_attrs(hir_id, ¶m.attrs, param.span()); + self.lower_attrs(hir_id, ¶m.attrs, param.span(), Target::Param); hir::GenericParam { hir_id, def_id: self.local_def_id(param.id), @@ -2124,7 +2131,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { diag.emit(); return; } - RelaxedBoundForbiddenReason::LateBoundVarsInScope => {} + RelaxedBoundForbiddenReason::AssocTyBounds + | RelaxedBoundForbiddenReason::LateBoundVarsInScope => {} }; } } diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs index e44406210481..b8f862478756 100644 --- a/compiler/rustc_ast_lowering/src/pat.rs +++ b/compiler/rustc_ast_lowering/src/pat.rs @@ -1,10 +1,9 @@ use std::sync::Arc; -use rustc_ast::ptr::P; use rustc_ast::*; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{self as hir, LangItem}; +use rustc_hir::{self as hir, LangItem, Target}; use rustc_middle::span_bug; use rustc_span::source_map::{Spanned, respan}; use rustc_span::{DesugaringKind, Ident, Span}; @@ -94,7 +93,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let fs = self.arena.alloc_from_iter(fields.iter().map(|f| { let hir_id = self.lower_node_id(f.id); - self.lower_attrs(hir_id, &f.attrs, f.span); + self.lower_attrs(hir_id, &f.attrs, f.span, Target::PatField); hir::PatField { hir_id, @@ -154,7 +153,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { fn lower_pat_tuple( &mut self, - pats: &[P], + pats: &[Box], ctx: &str, ) -> (&'hir [hir::Pat<'hir>], hir::DotDotPos) { let mut elems = Vec::with_capacity(pats.len()); @@ -209,7 +208,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { /// When encountering `($binding_mode $ident @)? ..` (`slice`), /// this is interpreted as a sub-slice pattern semantically. /// Patterns that follow, which are not like `slice` -- or an error occurs, are in `after`. - fn lower_pat_slice(&mut self, pats: &[P]) -> hir::PatKind<'hir> { + fn lower_pat_slice(&mut self, pats: &[Box]) -> hir::PatKind<'hir> { let mut before = Vec::new(); let mut after = Vec::new(); let mut slice = None; diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index c80ef275c801..3322e0fb66b4 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -24,7 +24,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { pub(crate) fn lower_qpath( &mut self, id: NodeId, - qself: &Option>, + qself: &Option>, p: &Path, param_mode: ParamMode, allow_return_type_notation: AllowReturnTypeNotation, diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl index af93d55c8982..c0679c1b8fff 100644 --- a/compiler/rustc_ast_passes/messages.ftl +++ b/compiler/rustc_ast_passes/messages.ftl @@ -17,9 +17,13 @@ ast_passes_abi_must_not_have_parameters_or_return_type= ast_passes_abi_must_not_have_return_type= invalid signature for `extern {$abi}` function - .note = functions with the "custom" ABI cannot have a return type + .note = functions with the {$abi} ABI cannot have a return type .help = remove the return type +ast_passes_abi_x86_interrupt = + invalid signature for `extern "x86-interrupt"` function + .note = functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found {$param_count}) + ast_passes_assoc_const_without_body = associated constant in `impl` without body .suggestion = provide a definition for the constant @@ -32,6 +36,13 @@ ast_passes_assoc_type_without_body = associated type in `impl` without body .suggestion = provide a definition for the type +ast_passes_async_fn_in_const_trait_or_trait_impl = + async functions are not allowed in `const` {$in_impl -> + [true] trait impls + *[false] traits + } + .label = associated functions of `const` cannot be declared `async` + ast_passes_at_least_one_trait = at least one trait must be specified ast_passes_auto_generic = auto traits cannot have generic parameters @@ -40,7 +51,7 @@ ast_passes_auto_generic = auto traits cannot have generic parameters ast_passes_auto_items = auto traits cannot have associated items .label = {ast_passes_auto_items} - .suggestion = remove these associated items + .suggestion = remove the associated items ast_passes_auto_super_lifetime = auto traits cannot have super traits or lifetime bounds .label = {ast_passes_auto_super_lifetime} @@ -175,11 +186,6 @@ ast_passes_generic_default_trailing = generic parameters with a default must be ast_passes_incompatible_features = `{$f1}` and `{$f2}` are incompatible, using them at the same time is not allowed .help = remove one of these features -ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation} - .because = {$annotation} because of this - .type = inherent impl for this type - .only_trait = only trait implementations may be annotated with {$annotation} - ast_passes_item_invalid_safety = items outside of `unsafe extern {"{ }"}` cannot be declared with `safe` safety qualifier .suggestion = remove safe from this item @@ -241,6 +247,10 @@ ast_passes_tilde_const_disallowed = `[const]` is not allowed here .trait_assoc_ty = associated types in non-`const` traits cannot have `[const]` trait bounds .trait_impl_assoc_ty = associated types in non-const impls cannot have `[const]` trait bounds .inherent_assoc_ty = inherent associated types cannot have `[const]` trait bounds + .struct = structs cannot have `[const]` trait bounds + .enum = enums cannot have `[const]` trait bounds + .union = unions cannot have `[const]` trait bounds + .anon_const = anonymous constants cannot have `[const]` trait bounds .object = trait objects cannot have `[const]` trait bounds .item = this item cannot have `[const]` trait bounds diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 895a457ec1d5..ef4410566c51 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -22,7 +22,6 @@ use std::str::FromStr; use itertools::{Either, Itertools}; use rustc_abi::{CanonAbi, ExternAbi, InterruptKind}; -use rustc_ast::ptr::P; use rustc_ast::visit::{AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, walk_list}; use rustc_ast::*; use rustc_ast_pretty::pprust::{self, State}; @@ -294,6 +293,21 @@ impl<'a> AstValidator<'a> { }); } + fn check_async_fn_in_const_trait_or_impl(&self, sig: &FnSig, parent: &TraitOrTraitImpl) { + let Some(const_keyword) = parent.constness() else { return }; + + let Some(CoroutineKind::Async { span: async_keyword, .. }) = sig.header.coroutine_kind + else { + return; + }; + + self.dcx().emit_err(errors::AsyncFnInConstTraitOrTraitImpl { + async_keyword, + in_impl: matches!(parent, TraitOrTraitImpl::TraitImpl { .. }), + const_keyword, + }); + } + fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { self.check_decl_num_args(fn_decl); self.check_decl_cvariadic_pos(fn_decl); @@ -391,7 +405,24 @@ impl<'a> AstValidator<'a> { if let InterruptKind::X86 = interrupt_kind { // "x86-interrupt" is special because it does have arguments. // FIXME(workingjubilee): properly lint on acceptable input types. - if let FnRetTy::Ty(ref ret_ty) = sig.decl.output { + let inputs = &sig.decl.inputs; + let param_count = inputs.len(); + if !matches!(param_count, 1 | 2) { + let mut spans: Vec = + inputs.iter().map(|arg| arg.span).collect(); + if spans.is_empty() { + spans = vec![sig.span]; + } + self.dcx().emit_err(errors::AbiX86Interrupt { spans, param_count }); + } + + if let FnRetTy::Ty(ref ret_ty) = sig.decl.output + && match &ret_ty.kind { + TyKind::Never => false, + TyKind::Tup(tup) if tup.is_empty() => false, + _ => true, + } + { self.dcx().emit_err(errors::AbiMustNotHaveReturnType { span: ret_ty.span, abi, @@ -450,7 +481,13 @@ impl<'a> AstValidator<'a> { fn reject_params_or_return(&self, abi: ExternAbi, ident: &Ident, sig: &FnSig) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); - if let FnRetTy::Ty(ref ret_ty) = sig.decl.output { + if let FnRetTy::Ty(ref ret_ty) = sig.decl.output + && match &ret_ty.kind { + TyKind::Never => false, + TyKind::Tup(tup) if tup.is_empty() => false, + _ => true, + } + { spans.push(ret_ty.span); } @@ -699,23 +736,27 @@ impl<'a> AstValidator<'a> { } } - fn deny_super_traits(&self, bounds: &GenericBounds, ident_span: Span) { + fn deny_super_traits(&self, bounds: &GenericBounds, ident: Span) { if let [.., last] = &bounds[..] { - let span = ident_span.shrink_to_hi().to(last.span()); - self.dcx().emit_err(errors::AutoTraitBounds { span, ident: ident_span }); + let span = bounds.iter().map(|b| b.span()).collect(); + let removal = ident.shrink_to_hi().to(last.span()); + self.dcx().emit_err(errors::AutoTraitBounds { span, removal, ident }); } } - fn deny_where_clause(&self, where_clause: &WhereClause, ident_span: Span) { + fn deny_where_clause(&self, where_clause: &WhereClause, ident: Span) { if !where_clause.predicates.is_empty() { // FIXME: The current diagnostic is misleading since it only talks about // super trait and lifetime bounds while we should just say “bounds”. - self.dcx() - .emit_err(errors::AutoTraitBounds { span: where_clause.span, ident: ident_span }); + self.dcx().emit_err(errors::AutoTraitBounds { + span: vec![where_clause.span], + removal: where_clause.span, + ident, + }); } } - fn deny_items(&self, trait_items: &[P], ident_span: Span) { + fn deny_items(&self, trait_items: &[Box], ident_span: Span) { if !trait_items.is_empty() { let spans: Vec<_> = trait_items.iter().map(|i| i.kind.ident().unwrap().span).collect(); let total = trait_items.first().unwrap().span.to(trait_items.last().unwrap().span); @@ -951,13 +992,16 @@ impl<'a> Visitor<'a> for AstValidator<'a> { } match &item.kind { - ItemKind::Impl(box Impl { - safety, - polarity, - defaultness: _, - constness, + ItemKind::Impl(Impl { generics, - of_trait: Some(t), + of_trait: + Some(box TraitImplHeader { + safety, + polarity, + defaultness: _, + constness, + trait_ref: t, + }), self_ty, items, }) => { @@ -989,46 +1033,12 @@ impl<'a> Visitor<'a> for AstValidator<'a> { walk_list!(this, visit_assoc_item, items, AssocCtxt::Impl { of_trait: true }); }); } - ItemKind::Impl(box Impl { - safety, - polarity, - defaultness, - constness, - generics, - of_trait: None, - self_ty, - items, - }) => { - let error = |annotation_span, annotation, only_trait| errors::InherentImplCannot { - span: self_ty.span, - annotation_span, - annotation, - self_ty: self_ty.span, - only_trait, - }; - + ItemKind::Impl(Impl { generics, of_trait: None, self_ty, items }) => { self.visit_attrs_vis(&item.attrs, &item.vis); self.visibility_not_permitted( &item.vis, errors::VisibilityNotPermittedNote::IndividualImplItems, ); - if let &Safety::Unsafe(span) = safety { - self.dcx().emit_err(errors::InherentImplCannotUnsafe { - span: self_ty.span, - annotation_span: span, - annotation: "unsafe", - self_ty: self_ty.span, - }); - } - if let &ImplPolarity::Negative(span) = polarity { - self.dcx().emit_err(error(span, "negative", false)); - } - if let &Defaultness::Default(def_span) = defaultness { - self.dcx().emit_err(error(def_span, "`default`", true)); - } - if let &Const::Yes(span) = constness { - self.dcx().emit_err(error(span, "`const`", true)); - } self.with_tilde_const(Some(TildeConstReason::Impl { span: item.span }), |this| { this.visit_generics(generics) @@ -1124,7 +1134,9 @@ impl<'a> Visitor<'a> for AstValidator<'a> { ); } } - visit::walk_item(self, item) + self.with_tilde_const(Some(TildeConstReason::Enum { span: item.span }), |this| { + visit::walk_item(this, item) + }); } ItemKind::Trait(box Trait { constness, @@ -1168,33 +1180,39 @@ impl<'a> Visitor<'a> for AstValidator<'a> { self.dcx().emit_err(errors::UnsafeItem { span, kind: "module" }); } // Ensure that `path` attributes on modules are recorded as used (cf. issue #35584). - if !matches!(mod_kind, ModKind::Loaded(_, Inline::Yes, _, _)) + if !matches!(mod_kind, ModKind::Loaded(_, Inline::Yes, _)) && !attr::contains_name(&item.attrs, sym::path) { self.check_mod_file_item_asciionly(*ident); } visit::walk_item(self, item) } - ItemKind::Struct(ident, generics, vdata) => match vdata { - VariantData::Struct { fields, .. } => { - self.visit_attrs_vis_ident(&item.attrs, &item.vis, ident); - self.visit_generics(generics); - walk_list!(self, visit_field_def, fields); - } - _ => visit::walk_item(self, item), - }, + ItemKind::Struct(ident, generics, vdata) => { + self.with_tilde_const(Some(TildeConstReason::Struct { span: item.span }), |this| { + match vdata { + VariantData::Struct { fields, .. } => { + this.visit_attrs_vis_ident(&item.attrs, &item.vis, ident); + this.visit_generics(generics); + walk_list!(this, visit_field_def, fields); + } + _ => visit::walk_item(this, item), + } + }) + } ItemKind::Union(ident, generics, vdata) => { if vdata.fields().is_empty() { self.dcx().emit_err(errors::FieldlessUnion { span: item.span }); } - match vdata { - VariantData::Struct { fields, .. } => { - self.visit_attrs_vis_ident(&item.attrs, &item.vis, ident); - self.visit_generics(generics); - walk_list!(self, visit_field_def, fields); + self.with_tilde_const(Some(TildeConstReason::Union { span: item.span }), |this| { + match vdata { + VariantData::Struct { fields, .. } => { + this.visit_attrs_vis_ident(&item.attrs, &item.vis, ident); + this.visit_generics(generics); + walk_list!(this, visit_field_def, fields); + } + _ => visit::walk_item(this, item), } - _ => visit::walk_item(self, item), - } + }); } ItemKind::Const(box ConstItem { defaultness, expr, .. }) => { self.check_defaultness(item.span, *defaultness); @@ -1586,6 +1604,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { self.visibility_not_permitted(&item.vis, errors::VisibilityNotPermittedNote::TraitImpl); if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind { self.check_trait_fn_not_const(sig.header.constness, parent); + self.check_async_fn_in_const_trait_or_impl(sig, parent); } } @@ -1623,6 +1642,13 @@ impl<'a> Visitor<'a> for AstValidator<'a> { _ => self.with_in_trait_impl(None, |this| visit::walk_assoc_item(this, item, ctxt)), } } + + fn visit_anon_const(&mut self, anon_const: &'a AnonConst) { + self.with_tilde_const( + Some(TildeConstReason::AnonConst { span: anon_const.value.span }), + |this| visit::walk_anon_const(this, anon_const), + ) + } } /// When encountering an equality constraint in a `where` clause, emit an error. If the code seems @@ -1754,7 +1780,7 @@ fn deny_equality_constraints( .map(|segment| segment.ident.name) .zip(poly.trait_ref.path.segments.iter().map(|segment| segment.ident.name)) .all(|(a, b)| a == b) - && let Some(potential_assoc) = full_path.segments.iter().last() + && let Some(potential_assoc) = full_path.segments.last() { suggest(poly, potential_assoc, predicate); } diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index fd4b2528541e..b9b2d2719545 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -62,6 +62,16 @@ pub(crate) struct TraitFnConst { pub make_trait_const_sugg: Option, } +#[derive(Diagnostic)] +#[diag(ast_passes_async_fn_in_const_trait_or_trait_impl)] +pub(crate) struct AsyncFnInConstTraitOrTraitImpl { + #[primary_span] + pub async_keyword: Span, + pub in_impl: bool, + #[label] + pub const_keyword: Span, +} + #[derive(Diagnostic)] #[diag(ast_passes_forbidden_bound)] pub(crate) struct ForbiddenBound { @@ -344,7 +354,7 @@ pub(crate) struct ModuleNonAscii { #[diag(ast_passes_auto_generic, code = E0567)] pub(crate) struct AutoTraitGeneric { #[primary_span] - #[suggestion(code = "", applicability = "machine-applicable")] + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] pub span: Span, #[label] pub ident: Span, @@ -354,8 +364,9 @@ pub(crate) struct AutoTraitGeneric { #[diag(ast_passes_auto_super_lifetime, code = E0568)] pub(crate) struct AutoTraitBounds { #[primary_span] - #[suggestion(code = "", applicability = "machine-applicable")] - pub span: Span, + pub span: Vec, + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] + pub removal: Span, #[label] pub ident: Span, } @@ -365,7 +376,7 @@ pub(crate) struct AutoTraitBounds { pub(crate) struct AutoTraitItems { #[primary_span] pub spans: Vec, - #[suggestion(code = "", applicability = "machine-applicable")] + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] pub total: Span, #[label] pub ident: Span, @@ -463,32 +474,6 @@ pub(crate) struct UnsafeNegativeImpl { pub r#unsafe: Span, } -#[derive(Diagnostic)] -#[diag(ast_passes_inherent_cannot_be)] -pub(crate) struct InherentImplCannot<'a> { - #[primary_span] - pub span: Span, - #[label(ast_passes_because)] - pub annotation_span: Span, - pub annotation: &'a str, - #[label(ast_passes_type)] - pub self_ty: Span, - #[note(ast_passes_only_trait)] - pub only_trait: bool, -} - -#[derive(Diagnostic)] -#[diag(ast_passes_inherent_cannot_be, code = E0197)] -pub(crate) struct InherentImplCannotUnsafe<'a> { - #[primary_span] - pub span: Span, - #[label(ast_passes_because)] - pub annotation_span: Span, - pub annotation: &'a str, - #[label(ast_passes_type)] - pub self_ty: Span, -} - #[derive(Diagnostic)] #[diag(ast_passes_unsafe_item)] pub(crate) struct UnsafeItem { @@ -623,6 +608,26 @@ pub(crate) enum TildeConstReason { #[primary_span] span: Span, }, + #[note(ast_passes_struct)] + Struct { + #[primary_span] + span: Span, + }, + #[note(ast_passes_enum)] + Enum { + #[primary_span] + span: Span, + }, + #[note(ast_passes_union)] + Union { + #[primary_span] + span: Span, + }, + #[note(ast_passes_anon_const)] + AnonConst { + #[primary_span] + span: Span, + }, #[note(ast_passes_object)] TraitObject, #[note(ast_passes_item)] @@ -886,3 +891,12 @@ pub(crate) struct AbiMustNotHaveReturnType { pub span: Span, pub abi: ExternAbi, } + +#[derive(Diagnostic)] +#[diag(ast_passes_abi_x86_interrupt)] +#[note] +pub(crate) struct AbiX86Interrupt { + #[primary_span] + pub spans: Vec, + pub param_count: usize, +} diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 662357ce8841..e763c9d69fc2 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -217,18 +217,18 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } } - ast::ItemKind::Impl(box ast::Impl { polarity, defaultness, of_trait, .. }) => { - if let &ast::ImplPolarity::Negative(span) = polarity { + ast::ItemKind::Impl(ast::Impl { of_trait: Some(of_trait), .. }) => { + if let ast::ImplPolarity::Negative(span) = of_trait.polarity { gate!( &self, negative_impls, - span.to(of_trait.as_ref().map_or(span, |t| t.path.span)), + span.to(of_trait.trait_ref.path.span), "negative trait bounds are not fully implemented; \ use marker types for now" ); } - if let ast::Defaultness::Default(_) = defaultness { + if let ast::Defaultness::Default(_) = of_trait.defaultness { gate!(&self, specialization, i.span, "specialization is unstable"); } } @@ -524,6 +524,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(where_clause_attrs, "attributes in `where` clause are unstable"); gate_all!(super_let, "`super let` is experimental"); gate_all!(frontmatter, "frontmatters are experimental"); + gate_all!(coroutines, "coroutine syntax is experimental"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index f0cf0c1487f7..85f76036df72 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -10,7 +10,6 @@ use std::borrow::Cow; use std::sync::Arc; use rustc_ast::attr::AttrIdGenerator; -use rustc_ast::ptr::P; use rustc_ast::token::{self, CommentKind, Delimiter, IdentIsRaw, Token, TokenKind}; use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree}; use rustc_ast::util::classify; @@ -1178,7 +1177,7 @@ impl<'a> State<'a> { self.end(rb); } - fn commasep_exprs(&mut self, b: Breaks, exprs: &[P]) { + fn commasep_exprs(&mut self, b: Breaks, exprs: &[Box]) { self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e, FixupContext::default()), |e| e.span) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 8a2cb64b2a08..bdf73ac32f0d 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -2,7 +2,6 @@ use std::fmt::Write; use ast::{ForLoopKind, MatchKind}; use itertools::{Itertools, Position}; -use rustc_ast::ptr::P; use rustc_ast::util::classify; use rustc_ast::util::literal::escape_byte_str_symbol; use rustc_ast::util::parser::{self, ExprPrecedence, Fixity}; @@ -54,7 +53,7 @@ impl<'a> State<'a> { self.print_else(elseopt) } - fn print_call_post(&mut self, args: &[P]) { + fn print_call_post(&mut self, args: &[Box]) { self.popen(); self.commasep_exprs(Inconsistent, args); self.pclose() @@ -111,7 +110,7 @@ impl<'a> State<'a> { } } - fn print_expr_vec(&mut self, exprs: &[P]) { + fn print_expr_vec(&mut self, exprs: &[Box]) { let ib = self.ibox(INDENT_UNIT); self.word("["); self.commasep_exprs(Inconsistent, exprs); @@ -149,7 +148,7 @@ impl<'a> State<'a> { fn print_expr_struct( &mut self, - qself: &Option>, + qself: &Option>, path: &ast::Path, fields: &[ast::ExprField], rest: &ast::StructRest, @@ -204,7 +203,7 @@ impl<'a> State<'a> { self.word("}"); } - fn print_expr_tup(&mut self, exprs: &[P]) { + fn print_expr_tup(&mut self, exprs: &[Box]) { self.popen(); self.commasep_exprs(Inconsistent, exprs); if exprs.len() == 1 { @@ -213,7 +212,7 @@ impl<'a> State<'a> { self.pclose() } - fn print_expr_call(&mut self, func: &ast::Expr, args: &[P], fixup: FixupContext) { + fn print_expr_call(&mut self, func: &ast::Expr, args: &[Box], fixup: FixupContext) { // Independent of parenthesization related to precedence, we must // parenthesize `func` if this is a statement context in which without // parentheses, a statement boundary would occur inside `func` or @@ -247,7 +246,7 @@ impl<'a> State<'a> { &mut self, segment: &ast::PathSegment, receiver: &ast::Expr, - base_args: &[P], + base_args: &[Box], fixup: FixupContext, ) { // The fixup here is different than in `print_expr_call` because diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 11c97a552c69..ab402cbb8dc1 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -2,7 +2,6 @@ use ast::StaticItem; use itertools::{Itertools, Position}; use rustc_ast as ast; use rustc_ast::ModKind; -use rustc_ast::ptr::P; use rustc_span::Ident; use crate::pp::BoxMarker; @@ -309,39 +308,41 @@ impl<'a> State<'a> { let (cb, ib) = self.head(visibility_qualified(&item.vis, "union")); self.print_struct(struct_def, generics, *ident, item.span, true, cb, ib); } - ast::ItemKind::Impl(box ast::Impl { - safety, - polarity, - defaultness, - constness, - generics, - of_trait, - self_ty, - items, - }) => { + ast::ItemKind::Impl(ast::Impl { generics, of_trait, self_ty, items }) => { let (cb, ib) = self.head(""); self.print_visibility(&item.vis); - self.print_defaultness(*defaultness); - self.print_safety(*safety); - self.word("impl"); - - if generics.params.is_empty() { - self.nbsp(); - } else { - self.print_generic_params(&generics.params); - self.space(); - } - self.print_constness(*constness); + let impl_generics = |this: &mut Self| { + this.word("impl"); - if let ast::ImplPolarity::Negative(_) = polarity { - self.word("!"); - } - - if let Some(t) = of_trait { - self.print_trait_ref(t); + if generics.params.is_empty() { + this.nbsp(); + } else { + this.print_generic_params(&generics.params); + this.space(); + } + }; + + if let Some(box of_trait) = of_trait { + let ast::TraitImplHeader { + defaultness, + safety, + constness, + polarity, + ref trait_ref, + } = *of_trait; + self.print_defaultness(defaultness); + self.print_safety(safety); + impl_generics(self); + self.print_constness(constness); + if let ast::ImplPolarity::Negative(_) = polarity { + self.word("!"); + } + self.print_trait_ref(trait_ref); self.space(); self.word_space("for"); + } else { + impl_generics(self); } self.print_type(self_ty); @@ -628,10 +629,10 @@ impl<'a> State<'a> { &mut self, attrs: &[ast::Attribute], vis: &ast::Visibility, - qself: &Option>, + qself: &Option>, path: &ast::Path, kind: DelegationKind<'_>, - body: &Option>, + body: &Option>, ) { let body_cb_ib = body.as_ref().map(|body| (body, self.head(""))); self.print_visibility(vis); diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 35ff48cb5f24..067d95a0f482 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -10,6 +10,14 @@ attr_parsing_empty_attribute = unused attribute .suggestion = remove this attribute +attr_parsing_invalid_target = `#[{$name}]` attribute cannot be used on {$target} + .help = `#[{$name}]` can {$only}be applied to {$applied} + .suggestion = remove the attribute +attr_parsing_invalid_target_lint = `#[{$name}]` attribute cannot be used on {$target} + .warn = {-attr_parsing_previously_accepted} + .help = `#[{$name}]` can {$only}be applied to {$applied} + .suggestion = remove the attribute + attr_parsing_empty_confusables = expected at least one confusable name attr_parsing_expected_one_cfg_pattern = @@ -132,6 +140,7 @@ attr_parsing_unknown_version_literal = attr_parsing_unrecognized_repr_hint = unrecognized representation hint .help = valid reprs are `Rust` (default), `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` + .note = for more information, visit attr_parsing_unstable_cfg_target_compact = compact `cfg(target(..))` is experimental and subject to change diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index 95104b896acc..088fa73d7427 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -1,12 +1,6 @@ use std::iter; -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; - -use super::{CombineAttributeParser, ConvertFn}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; +use super::prelude::*; use crate::session_diagnostics; pub(crate) struct AllowInternalUnstableParser; @@ -15,7 +9,13 @@ impl CombineAttributeParser for AllowInternalUnstableParser { type Item = (Symbol, Span); const CONVERT: ConvertFn = |items, span| AttributeKind::AllowInternalUnstable(items, span); - const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ..."); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::MacroDef), + Allow(Target::Fn), + Warn(Target::Field), + Warn(Target::Arm), + ]); + const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]); fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, @@ -32,7 +32,12 @@ impl CombineAttributeParser for UnstableFeatureBoundParser { const PATH: &'static [rustc_span::Symbol] = &[sym::unstable_feature_bound]; type Item = (Symbol, Span); const CONVERT: ConvertFn = |items, _| AttributeKind::UnstableFeatureBound(items); - const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ..."); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Impl { of_trait: true }), + Allow(Target::Trait), + ]); + const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]); fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, @@ -53,7 +58,14 @@ impl CombineAttributeParser for AllowConstFnUnstableParser { type Item = Symbol; const CONVERT: ConvertFn = |items, first_span| AttributeKind::AllowConstFnUnstable(items, first_span); - const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ..."); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + ]); + const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]); fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, diff --git a/compiler/rustc_attr_parsing/src/attributes/body.rs b/compiler/rustc_attr_parsing/src/attributes/body.rs new file mode 100644 index 000000000000..a1492d761946 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/body.rs @@ -0,0 +1,12 @@ +//! Attributes that can be found in function body. + +use super::prelude::*; + +pub(crate) struct CoroutineParser; + +impl NoArgsAttributeParser for CoroutineParser { + const PATH: &[Symbol] = &[sym::coroutine]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Closure)]); + const CREATE: fn(rustc_span::Span) -> AttributeKind = |span| AttributeKind::Coroutine(span); +} diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index 947be28bc956..695ee6664763 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -16,7 +16,10 @@ use crate::{ CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg, }; -pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate"); +pub const CFG_TEMPLATE: AttributeTemplate = template!( + List: &["predicate"], + "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute" +); pub fn parse_cfg_attr<'c, S: Stage>( cx: &'c mut AcceptContext<'_, '_, S>, diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 7c8ef90ed7f1..843e411d25b0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,14 +1,7 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::{AttributeKind, CoverageAttrKind, OptimizeAttr, UsedBy}; +use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, UsedBy}; use rustc_session::parse::feature_err; -use rustc_span::{Span, Symbol, sym}; - -use super::{ - AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn, - NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, -}; -use crate::context::{AcceptContext, FinalizeContext, Stage}; -use crate::parser::ArgParser; + +use super::prelude::*; use crate::session_diagnostics::{NakedFunctionIncompatibleAttribute, NullOnExport}; pub(crate) struct OptimizeParser; @@ -17,7 +10,14 @@ impl SingleAttributeParser for OptimizeParser { const PATH: &[Symbol] = &[sym::optimize]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(List: "size|speed|none"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Closure), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Inherent)), + ]); + const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(list) = args.list() else { @@ -49,6 +49,15 @@ pub(crate) struct ColdParser; impl NoArgsAttributeParser for ColdParser { const PATH: &[Symbol] = &[sym::cold]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::ForeignFn), + Allow(Target::Closure), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::Cold; } @@ -58,6 +67,17 @@ impl SingleAttributeParser for CoverageParser { const PATH: &[Symbol] = &[sym::coverage]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Closure), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Impl { of_trait: true }), + Allow(Target::Impl { of_trait: false }), + Allow(Target::Mod), + Allow(Target::Crate), + ]); const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { @@ -97,6 +117,16 @@ impl SingleAttributeParser for ExportNameParser { const PATH: &[rustc_span::Symbol] = &[sym::export_name]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Static), + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Warn(Target::Field), + Warn(Target::Arm), + Warn(Target::MacroDef), + ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { @@ -138,6 +168,12 @@ impl AttributeParser for NakedParser { this.span = Some(cx.attr_span); } })]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + ]); fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option { // FIXME(jdonszelmann): upgrade this list to *parsed* attributes @@ -230,6 +266,18 @@ pub(crate) struct TrackCallerParser; impl NoArgsAttributeParser for TrackCallerParser { const PATH: &[Symbol] = &[sym::track_caller]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::ForeignFn), + Allow(Target::Closure), + Warn(Target::MacroDef), + Warn(Target::Arm), + Warn(Target::Field), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller; } @@ -237,6 +285,12 @@ pub(crate) struct NoMangleParser; impl NoArgsAttributeParser for NoMangleParser { const PATH: &[Symbol] = &[sym::no_mangle]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Fn), + Allow(Target::Static), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::TraitImpl)), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle; } @@ -253,7 +307,7 @@ pub(crate) struct UsedParser { impl AttributeParser for UsedParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::used], - template!(Word, List: "compiler|linker"), + template!(Word, List: &["compiler", "linker"]), |group: &mut Self, cx, args| { let used_by = match args { ArgParser::NoArgs => UsedBy::Linker, @@ -310,6 +364,7 @@ impl AttributeParser for UsedParser { } }, )]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static)]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { // Ratcheting behaviour, if both `linker` and `compiler` are specified, use `linker` @@ -327,7 +382,7 @@ impl CombineAttributeParser for TargetFeatureParser { type Item = (Symbol, Span); const PATH: &[Symbol] = &[sym::target_feature]; const CONVERT: ConvertFn = |items, span| AttributeKind::TargetFeature(items, span); - const TEMPLATE: AttributeTemplate = template!(List: "enable = \"feat1, feat2\""); + const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]); fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, @@ -373,12 +428,15 @@ impl CombineAttributeParser for TargetFeatureParser { } features } -} -pub(crate) struct OmitGdbPrettyPrinterSectionParser; - -impl NoArgsAttributeParser for OmitGdbPrettyPrinterSectionParser { - const PATH: &[Symbol] = &[sym::omit_gdb_pretty_printer_section]; - const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::OmitGdbPrettyPrinterSection; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Warn(Target::Statement), + Warn(Target::Field), + Warn(Target::Arm), + Warn(Target::MacroDef), + ]); } diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs index 7d24c89a6e8d..97e78dfb136b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs +++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs @@ -1,11 +1,5 @@ -use rustc_feature::template; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; -use thin_vec::ThinVec; - -use super::{AcceptMapping, AttributeParser}; -use crate::context::{FinalizeContext, Stage}; -use crate::session_diagnostics; +use super::prelude::*; +use crate::session_diagnostics::EmptyConfusables; #[derive(Default)] pub(crate) struct ConfusablesParser { @@ -16,7 +10,7 @@ pub(crate) struct ConfusablesParser { impl AttributeParser for ConfusablesParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::rustc_confusables], - template!(List: r#""name1", "name2", ..."#), + template!(List: &[r#""name1", "name2", ..."#]), |this, cx, args| { let Some(list) = args.list() else { cx.expected_list(cx.attr_span); @@ -24,7 +18,7 @@ impl AttributeParser for ConfusablesParser { }; if list.is_empty() { - cx.emit_err(session_diagnostics::EmptyConfusables { span: cx.attr_span }); + cx.emit_err(EmptyConfusables { span: cx.attr_span }); } for param in list.mixed() { @@ -41,6 +35,8 @@ impl AttributeParser for ConfusablesParser { this.first_span.get_or_insert(cx.attr_span); }, )]; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Method(MethodKind::Inherent))]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if self.confusables.is_empty() { diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index 38ec4bd5645c..31c698228ef4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -1,12 +1,10 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; -use rustc_span::{Span, Symbol, sym}; +use rustc_hir::attrs::{DeprecatedSince, Deprecation}; +use super::prelude::*; use super::util::parse_version; -use super::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; -use crate::session_diagnostics; +use crate::session_diagnostics::{ + DeprecatedItemSuggestion, InvalidSince, MissingNote, MissingSince, +}; pub(crate) struct DeprecationParser; @@ -38,9 +36,35 @@ impl SingleAttributeParser for DeprecationParser { const PATH: &[Symbol] = &[sym::deprecated]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Fn), + Allow(Target::Mod), + Allow(Target::Struct), + Allow(Target::Enum), + Allow(Target::Union), + Allow(Target::Const), + Allow(Target::Static), + Allow(Target::MacroDef), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::TyAlias), + Allow(Target::Use), + Allow(Target::ForeignFn), + Allow(Target::ForeignStatic), + Allow(Target::ForeignTy), + Allow(Target::Field), + Allow(Target::Trait), + Allow(Target::AssocTy), + Allow(Target::AssocConst), + Allow(Target::Variant), + Allow(Target::Impl { of_trait: false }), //FIXME This does not make sense + Allow(Target::Crate), + Error(Target::WherePredicate), + ]); const TEMPLATE: AttributeTemplate = template!( Word, - List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#, + List: &[r#"since = "version""#, r#"note = "reason""#, r#"since = "version", note = "reason""#], NameValueStr: "reason" ); @@ -75,7 +99,7 @@ impl SingleAttributeParser for DeprecationParser { } Some(name @ sym::suggestion) => { if !features.deprecated_suggestion() { - cx.emit_err(session_diagnostics::DeprecatedItemSuggestion { + cx.emit_err(DeprecatedItemSuggestion { span: param.span(), is_nightly: cx.sess().is_nightly_build(), details: (), @@ -117,18 +141,18 @@ impl SingleAttributeParser for DeprecationParser { } else if let Some(version) = parse_version(since) { DeprecatedSince::RustcVersion(version) } else { - cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span }); + cx.emit_err(InvalidSince { span: cx.attr_span }); DeprecatedSince::Err } } else if is_rustc { - cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span }); + cx.emit_err(MissingSince { span: cx.attr_span }); DeprecatedSince::Err } else { DeprecatedSince::Unspecified }; if is_rustc && note.is_none() { - cx.emit_err(session_diagnostics::MissingNote { span: cx.attr_span }); + cx.emit_err(MissingNote { span: cx.attr_span }); return None; } diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs index bbcd9ab530c5..7293cee842c2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs +++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs @@ -5,12 +5,14 @@ use rustc_span::{Symbol, sym}; use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; +use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct DummyParser; impl SingleAttributeParser for DummyParser { const PATH: &[Symbol] = &[sym::rustc_dummy]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser<'_>) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index 8437713206e3..101fa71b8a68 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -2,15 +2,9 @@ // note: need to model better how duplicate attr errors work when not using // SingleAttributeParser which is what we have two of here. -use rustc_feature::{AttributeTemplate, template}; use rustc_hir::attrs::{AttributeKind, InlineAttr}; -use rustc_hir::lints::AttributeLintKind; -use rustc_span::{Symbol, sym}; -use super::{AcceptContext, AttributeOrder, OnDuplicate}; -use crate::attributes::SingleAttributeParser; -use crate::context::Stage; -use crate::parser::ArgParser; +use super::prelude::*; pub(crate) struct InlineParser; @@ -18,7 +12,25 @@ impl SingleAttributeParser for InlineParser { const PATH: &'static [Symbol] = &[sym::inline]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(Word, List: "always|never"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Closure), + Allow(Target::Delegation { mac: false }), + Warn(Target::Method(MethodKind::Trait { body: false })), + Warn(Target::ForeignFn), + Warn(Target::Field), + Warn(Target::MacroDef), + Warn(Target::Arm), + Warn(Target::AssocConst), + ]); + const TEMPLATE: AttributeTemplate = template!( + Word, + List: &["always", "never"], + "https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { match args { @@ -43,8 +55,8 @@ impl SingleAttributeParser for InlineParser { } } ArgParser::NameValue(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "inline"); + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "inline"); let span = cx.attr_span; cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); return None; @@ -59,7 +71,8 @@ impl SingleAttributeParser for RustcForceInlineParser { const PATH: &'static [Symbol] = &[sym::rustc_force_inline]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(Word, List: "reason", NameValueStr: "reason"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const TEMPLATE: AttributeTemplate = template!(Word, List: &["reason"], NameValueStr: "reason"); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let reason = match args { diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 7eab30908704..7a765f71a5e9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -1,13 +1,8 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection}; -use rustc_span::{Span, Symbol, sym}; +use rustc_hir::attrs::Linkage; -use crate::attributes::{ - AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, -}; -use crate::context::{AcceptContext, Stage, parse_single_integer}; -use crate::parser::ArgParser; +use super::prelude::*; +use super::util::parse_single_integer; use crate::session_diagnostics::{LinkOrdinalOutOfRange, NullOnLinkSection}; pub(crate) struct LinkNameParser; @@ -16,7 +11,14 @@ impl SingleAttributeParser for LinkNameParser { const PATH: &[Symbol] = &[sym::link_name]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::ForeignFn), + Allow(Target::ForeignStatic), + ]); + const TEMPLATE: AttributeTemplate = template!( + NameValueStr: "name", + "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { @@ -38,7 +40,12 @@ impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowListWarnRest(&[Allow(Target::Static), Allow(Target::Fn)]); + const TEMPLATE: AttributeTemplate = template!( + NameValueStr: "name", + "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { @@ -64,6 +71,7 @@ pub(crate) struct ExportStableParser; impl NoArgsAttributeParser for ExportStableParser { const PATH: &[Symbol] = &[sym::export_stable]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ExportStable; } @@ -71,6 +79,7 @@ pub(crate) struct FfiConstParser; impl NoArgsAttributeParser for FfiConstParser { const PATH: &[Symbol] = &[sym::ffi_const]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst; } @@ -78,6 +87,7 @@ pub(crate) struct FfiPureParser; impl NoArgsAttributeParser for FfiPureParser { const PATH: &[Symbol] = &[sym::ffi_pure]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; } @@ -85,6 +95,12 @@ pub(crate) struct StdInternalSymbolParser; impl NoArgsAttributeParser for StdInternalSymbolParser { const PATH: &[Symbol] = &[sym::rustc_std_internal_symbol]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::ForeignFn), + Allow(Target::Static), + Allow(Target::ForeignStatic), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::StdInternalSymbol; } @@ -94,7 +110,12 @@ impl SingleAttributeParser for LinkOrdinalParser { const PATH: &[Symbol] = &[sym::link_ordinal]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const TEMPLATE: AttributeTemplate = template!(List: "ordinal"); + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::ForeignFn), Allow(Target::ForeignStatic)]); + const TEMPLATE: AttributeTemplate = template!( + List: &["ordinal"], + "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let ordinal = parse_single_integer(cx, args)?; @@ -120,3 +141,87 @@ impl SingleAttributeParser for LinkOrdinalParser { Some(LinkOrdinal { ordinal, span: cx.attr_span }) } } + +pub(crate) struct LinkageParser; + +impl SingleAttributeParser for LinkageParser { + const PATH: &[Symbol] = &[sym::linkage]; + + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; + + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Static), + Allow(Target::ForeignStatic), + Allow(Target::ForeignFn), + ]); + + const TEMPLATE: AttributeTemplate = template!(NameValueStr: [ + "available_externally", + "common", + "extern_weak", + "external", + "internal", + "linkonce", + "linkonce_odr", + "weak", + "weak_odr", + ]); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { + let Some(name_value) = args.name_value() else { + cx.expected_name_value(cx.attr_span, Some(sym::linkage)); + return None; + }; + + let Some(value) = name_value.value_as_str() else { + cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit())); + return None; + }; + + // Use the names from src/llvm/docs/LangRef.rst here. Most types are only + // applicable to variable declarations and may not really make sense for + // Rust code in the first place but allow them anyway and trust that the + // user knows what they're doing. Who knows, unanticipated use cases may pop + // up in the future. + // + // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported + // and don't have to be, LLVM treats them as no-ops. + let linkage = match value { + sym::available_externally => Linkage::AvailableExternally, + sym::common => Linkage::Common, + sym::extern_weak => Linkage::ExternalWeak, + sym::external => Linkage::External, + sym::internal => Linkage::Internal, + sym::linkonce => Linkage::LinkOnceAny, + sym::linkonce_odr => Linkage::LinkOnceODR, + sym::weak => Linkage::WeakAny, + sym::weak_odr => Linkage::WeakODR, + + _ => { + cx.expected_specific_argument( + name_value.value_span, + vec![ + "available_externally", + "common", + "extern_weak", + "external", + "internal", + "linkonce", + "linkonce_odr", + "weak", + "weak_odr", + ], + ); + return None; + } + }; + + Some(AttributeKind::Linkage(linkage, cx.attr_span)) + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 9530fec07d61..63b0809d0d8c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -1,13 +1,16 @@ -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; - -use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; -use crate::context::Stage; +use super::prelude::*; pub(crate) struct AsPtrParser; impl NoArgsAttributeParser for AsPtrParser { const PATH: &[Symbol] = &[sym::rustc_as_ptr]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::AsPtr; } @@ -15,6 +18,11 @@ pub(crate) struct PubTransparentParser; impl NoArgsAttributeParser for PubTransparentParser { const PATH: &[Symbol] = &[sym::rustc_pub_transparent]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Struct), + Allow(Target::Enum), + Allow(Target::Union), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::PubTransparent; } @@ -22,6 +30,11 @@ pub(crate) struct PassByValueParser; impl NoArgsAttributeParser for PassByValueParser { const PATH: &[Symbol] = &[sym::rustc_pass_by_value]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Struct), + Allow(Target::Enum), + Allow(Target::TyAlias), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::PassByValue; } @@ -29,5 +42,10 @@ pub(crate) struct AutomaticallyDerivedParser; impl NoArgsAttributeParser for AutomaticallyDerivedParser { const PATH: &[Symbol] = &[sym::automatically_derived]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Impl { of_trait: true }), + Error(Target::Crate), + Error(Target::WherePredicate), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::AutomaticallyDerived; } diff --git a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs index 868c113a6d13..528090b8673d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/loop_match.rs +++ b/compiler/rustc_attr_parsing/src/attributes/loop_match.rs @@ -1,13 +1,10 @@ -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; - -use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; -use crate::context::Stage; +use super::prelude::*; pub(crate) struct LoopMatchParser; impl NoArgsAttributeParser for LoopMatchParser { const PATH: &[Symbol] = &[sym::loop_match]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::LoopMatch; } @@ -15,5 +12,6 @@ pub(crate) struct ConstContinueParser; impl NoArgsAttributeParser for ConstContinueParser { const PATH: &[Symbol] = &[sym::const_continue]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Expression)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstContinue; } diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index 886f7a889d30..180130c7be4f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -1,18 +1,14 @@ use rustc_errors::DiagArgValue; -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::{AttributeKind, MacroUseArgs}; -use rustc_span::{Span, Symbol, sym}; -use thin_vec::ThinVec; +use rustc_hir::attrs::MacroUseArgs; -use crate::attributes::{AcceptMapping, AttributeParser, NoArgsAttributeParser, OnDuplicate}; -use crate::context::{AcceptContext, FinalizeContext, Stage}; -use crate::parser::ArgParser; -use crate::session_diagnostics; +use super::prelude::*; +use crate::session_diagnostics::IllFormedAttributeInputLint; pub(crate) struct MacroEscapeParser; impl NoArgsAttributeParser for MacroEscapeParser { const PATH: &[Symbol] = &[sym::macro_escape]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = MACRO_USE_ALLOWED_TARGETS; const CREATE: fn(Span) -> AttributeKind = AttributeKind::MacroEscape; } @@ -31,7 +27,16 @@ pub(crate) struct MacroUseParser { first_span: Option, } -const MACRO_USE_TEMPLATE: AttributeTemplate = template!(Word, List: "name1, name2, ..."); +const MACRO_USE_TEMPLATE: AttributeTemplate = template!( + Word, List: &["name1, name2, ..."], + "https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute" +); +const MACRO_USE_ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Mod), + Allow(Target::ExternCrate), + Allow(Target::Crate), + Error(Target::WherePredicate), +]); impl AttributeParser for MacroUseParser { const ATTRIBUTES: AcceptMapping = &[( @@ -96,8 +101,8 @@ impl AttributeParser for MacroUseParser { } } ArgParser::NameValue(_) => { - let suggestions = MACRO_USE_TEMPLATE.suggestions(false, sym::macro_use); - cx.emit_err(session_diagnostics::IllFormedAttributeInputLint { + let suggestions = MACRO_USE_TEMPLATE.suggestions(cx.attr_style, sym::macro_use); + cx.emit_err(IllFormedAttributeInputLint { num_suggestions: suggestions.len(), suggestions: DiagArgValue::StrListSepByAnd( suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(), @@ -108,8 +113,23 @@ impl AttributeParser for MacroUseParser { } }, )]; + const ALLOWED_TARGETS: AllowedTargets = MACRO_USE_ALLOWED_TARGETS; fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { Some(AttributeKind::MacroUse { span: self.first_span?, arguments: self.state }) } } + +pub(crate) struct AllowInternalUnsafeParser; + +impl NoArgsAttributeParser for AllowInternalUnsafeParser { + const PATH: &[Symbol] = &[sym::allow_internal_unsafe]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::MacroDef), + Warn(Target::Field), + Warn(Target::Arm), + ]); + const CREATE: fn(Span) -> AttributeKind = |span| AttributeKind::AllowInternalUnsafe(span); +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index c574ef78bdf7..b98678041d74 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -7,9 +7,9 @@ //! Specifically, you might not care about managing the state of your [`AttributeParser`] //! state machine yourself. In this case you can choose to implement: //! -//! - [`SingleAttributeParser`]: makes it easy to implement an attribute which should error if it +//! - [`SingleAttributeParser`](crate::attributes::SingleAttributeParser): makes it easy to implement an attribute which should error if it //! appears more than once in a list of attributes -//! - [`CombineAttributeParser`]: makes it easy to implement an attribute which should combine the +//! - [`CombineAttributeParser`](crate::attributes::CombineAttributeParser): makes it easy to implement an attribute which should combine the //! contents of attributes, if an attribute appear multiple times in a list //! //! Attributes should be added to `crate::context::ATTRIBUTE_PARSERS` to be parsed. @@ -24,8 +24,13 @@ use thin_vec::ThinVec; use crate::context::{AcceptContext, FinalizeContext, Stage}; use crate::parser::ArgParser; use crate::session_diagnostics::UnusedMultiple; +use crate::target_checking::AllowedTargets; + +/// All the parsers require roughly the same imports, so this prelude has most of the often-needed ones. +mod prelude; pub(crate) mod allow_unstable; +pub(crate) mod body; pub(crate) mod cfg; pub(crate) mod cfg_old; pub(crate) mod codegen_attrs; @@ -42,6 +47,7 @@ pub(crate) mod no_implicit_prelude; pub(crate) mod non_exhaustive; pub(crate) mod path; pub(crate) mod proc_macro_attrs; +pub(crate) mod prototype; pub(crate) mod repr; pub(crate) mod rustc_internal; pub(crate) mod semantics; @@ -79,6 +85,8 @@ pub(crate) trait AttributeParser: Default + 'static { /// If an attribute has this symbol, the `accept` function will be called on it. const ATTRIBUTES: AcceptMapping; + const ALLOWED_TARGETS: AllowedTargets; + /// The parser has gotten a chance to accept the attributes on an item, /// here it can produce an attribute. /// @@ -115,6 +123,8 @@ pub(crate) trait SingleAttributeParser: 'static { /// and this specified whether to, for example, warn or error on the other one. const ON_DUPLICATE: OnDuplicate; + const ALLOWED_TARGETS: AllowedTargets; + /// The template this attribute parser should implement. Used for diagnostics. const TEMPLATE: AttributeTemplate; @@ -162,6 +172,7 @@ impl, S: Stage> AttributeParser for Single } }, )]; + const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { Some(self.1?.0) @@ -246,6 +257,7 @@ pub(crate) enum AttributeOrder { pub(crate) trait NoArgsAttributeParser: 'static { const PATH: &[Symbol]; const ON_DUPLICATE: OnDuplicate; + const ALLOWED_TARGETS: AllowedTargets; /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; @@ -263,6 +275,7 @@ impl, S: Stage> SingleAttributeParser for Without const PATH: &[Symbol] = T::PATH; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = T::ON_DUPLICATE; + const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { @@ -292,6 +305,8 @@ pub(crate) trait CombineAttributeParser: 'static { /// where `x` is a vec of these individual reprs. const CONVERT: ConvertFn; + const ALLOWED_TARGETS: AllowedTargets; + /// The template this attribute parser should implement. Used for diagnostics. const TEMPLATE: AttributeTemplate; @@ -323,15 +338,13 @@ impl, S: Stage> Default for Combine { } impl, S: Stage> AttributeParser for Combine { - const ATTRIBUTES: AcceptMapping = &[( - T::PATH, - >::TEMPLATE, - |group: &mut Combine, cx, args| { + const ATTRIBUTES: AcceptMapping = + &[(T::PATH, T::TEMPLATE, |group: &mut Combine, cx, args| { // Keep track of the span of the first attribute, for diagnostics group.first_span.get_or_insert(cx.attr_span); group.items.extend(T::extend(cx, args)) - }, - )]; + })]; + const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if let Some(first_span) = self.first_span { diff --git a/compiler/rustc_attr_parsing/src/attributes/must_use.rs b/compiler/rustc_attr_parsing/src/attributes/must_use.rs index d767abbc250f..e6a5141d7830 100644 --- a/compiler/rustc_attr_parsing/src/attributes/must_use.rs +++ b/compiler/rustc_attr_parsing/src/attributes/must_use.rs @@ -1,12 +1,7 @@ use rustc_errors::DiagArgValue; -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Symbol, sym}; -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; -use crate::session_diagnostics; +use super::prelude::*; +use crate::session_diagnostics::IllFormedAttributeInputLint; pub(crate) struct MustUseParser; @@ -14,7 +9,25 @@ impl SingleAttributeParser for MustUseParser { const PATH: &[Symbol] = &[sym::must_use]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ + Allow(Target::Fn), + Allow(Target::Enum), + Allow(Target::Struct), + Allow(Target::Union), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::ForeignFn), + // `impl Trait` in return position can trip + // `unused_must_use` if `Trait` is marked as + // `#[must_use]` + Allow(Target::Trait), + Error(Target::WherePredicate), + ]); + const TEMPLATE: AttributeTemplate = template!( + Word, NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { Some(AttributeKind::MustUse { @@ -32,9 +45,9 @@ impl SingleAttributeParser for MustUseParser { Some(value_str) } ArgParser::List(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "must_use"); - cx.emit_err(session_diagnostics::IllFormedAttributeInputLint { + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "must_use"); + cx.emit_err(IllFormedAttributeInputLint { num_suggestions: suggestions.len(), suggestions: DiagArgValue::StrListSepByAnd( suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(), diff --git a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs index 40f8d00685ea..40073ea0f461 100644 --- a/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/no_implicit_prelude.rs @@ -1,13 +1,11 @@ -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, sym}; - -use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; -use crate::context::Stage; +use super::prelude::*; pub(crate) struct NoImplicitPreludeParser; impl NoArgsAttributeParser for NoImplicitPreludeParser { const PATH: &[rustc_span::Symbol] = &[sym::no_implicit_prelude]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Allow(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoImplicitPrelude; } diff --git a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs index 361ac8e959dc..4e6aec95e66e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs +++ b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs @@ -1,13 +1,24 @@ +use rustc_hir::Target; use rustc_hir::attrs::AttributeKind; use rustc_span::{Span, Symbol, sym}; use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; use crate::context::Stage; +use crate::target_checking::AllowedTargets; +use crate::target_checking::Policy::{Allow, Warn}; pub(crate) struct NonExhaustiveParser; impl NoArgsAttributeParser for NonExhaustiveParser { const PATH: &[Symbol] = &[sym::non_exhaustive]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Enum), + Allow(Target::Struct), + Allow(Target::Variant), + Warn(Target::Field), + Warn(Target::Arm), + Warn(Target::MacroDef), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::NonExhaustive; } diff --git a/compiler/rustc_attr_parsing/src/attributes/path.rs b/compiler/rustc_attr_parsing/src/attributes/path.rs index 5700d780d712..e4cb806bb427 100644 --- a/compiler/rustc_attr_parsing/src/attributes/path.rs +++ b/compiler/rustc_attr_parsing/src/attributes/path.rs @@ -1,10 +1,4 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Symbol, sym}; - -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; +use super::prelude::*; pub(crate) struct PathParser; @@ -12,7 +6,12 @@ impl SingleAttributeParser for PathParser { const PATH: &[Symbol] = &[sym::path]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; - const TEMPLATE: AttributeTemplate = template!(NameValueStr: "file"); + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowListWarnRest(&[Allow(Target::Mod), Error(Target::Crate)]); + const TEMPLATE: AttributeTemplate = template!( + NameValueStr: "file", + "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs new file mode 100644 index 000000000000..2bcdee55c756 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs @@ -0,0 +1,20 @@ +// parsing +// templates +pub(super) use rustc_feature::{AttributeTemplate, template}; +// data structures +pub(super) use rustc_hir::attrs::AttributeKind; +pub(super) use rustc_hir::lints::AttributeLintKind; +pub(super) use rustc_hir::{MethodKind, Target}; +pub(super) use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym}; +pub(super) use thin_vec::ThinVec; + +pub(super) use crate::attributes::{ + AcceptMapping, AttributeOrder, AttributeParser, CombineAttributeParser, ConvertFn, + NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, +}; +// contexts +pub(super) use crate::context::{AcceptContext, FinalizeContext, Stage}; +pub(super) use crate::parser::*; +// target checking +pub(super) use crate::target_checking::Policy::{Allow, Error, Warn}; +pub(super) use crate::target_checking::{ALL_TARGETS, AllowedTargets}; diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs index b156a7c58450..076b45f10137 100644 --- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs @@ -1,18 +1,11 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; -use thin_vec::ThinVec; - -use crate::attributes::{ - AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, -}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; +use super::prelude::*; pub(crate) struct ProcMacroParser; impl NoArgsAttributeParser for ProcMacroParser { const PATH: &[Symbol] = &[sym::proc_macro]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacro; } @@ -20,6 +13,8 @@ pub(crate) struct ProcMacroAttributeParser; impl NoArgsAttributeParser for ProcMacroAttributeParser { const PATH: &[Symbol] = &[sym::proc_macro_attribute]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ProcMacroAttribute; } @@ -28,8 +23,12 @@ impl SingleAttributeParser for ProcMacroDeriveParser { const PATH: &[Symbol] = &[sym::proc_macro_derive]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const TEMPLATE: AttributeTemplate = - template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"); + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Fn), Warn(Target::Crate)]); + const TEMPLATE: AttributeTemplate = template!( + List: &["TraitName", "TraitName, attributes(name1, name2, ...)"], + "https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let (trait_name, helper_attrs) = parse_derive_like(cx, args, true)?; @@ -46,8 +45,9 @@ impl SingleAttributeParser for RustcBuiltinMacroParser { const PATH: &[Symbol] = &[sym::rustc_builtin_macro]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); const TEMPLATE: AttributeTemplate = - template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"); + template!(List: &["TraitName", "TraitName, attributes(name1, name2, ...)"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let (builtin_name, helper_attrs) = parse_derive_like(cx, args, false)?; diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs new file mode 100644 index 000000000000..0ef36bc7e4cb --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs @@ -0,0 +1,141 @@ +//! Attributes that are only used on function prototypes. + +use rustc_feature::{AttributeTemplate, template}; +use rustc_hir::Target; +use rustc_hir::attrs::{AttributeKind, MirDialect, MirPhase}; +use rustc_span::{Span, Symbol, sym}; + +use super::{AttributeOrder, OnDuplicate}; +use crate::attributes::SingleAttributeParser; +use crate::context::{AcceptContext, Stage}; +use crate::parser::ArgParser; +use crate::target_checking::AllowedTargets; +use crate::target_checking::Policy::Allow; + +pub(crate) struct CustomMirParser; + +impl SingleAttributeParser for CustomMirParser { + const PATH: &[rustc_span::Symbol] = &[sym::custom_mir]; + + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; + + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + + const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { + let Some(list) = args.list() else { + cx.expected_list(cx.attr_span); + return None; + }; + + let mut dialect = None; + let mut phase = None; + let mut failed = false; + + for item in list.mixed() { + let Some(meta_item) = item.meta_item() else { + cx.expected_name_value(item.span(), None); + failed = true; + break; + }; + + if let Some(arg) = meta_item.word_is(sym::dialect) { + extract_value(cx, sym::dialect, arg, meta_item.span(), &mut dialect, &mut failed); + } else if let Some(arg) = meta_item.word_is(sym::phase) { + extract_value(cx, sym::phase, arg, meta_item.span(), &mut phase, &mut failed); + } else if let Some(word) = meta_item.path().word() { + let word = word.to_string(); + cx.unknown_key(meta_item.span(), word, &["dialect", "phase"]); + failed = true; + } else { + cx.expected_name_value(meta_item.span(), None); + failed = true; + }; + } + + let dialect = parse_dialect(cx, dialect, &mut failed); + let phase = parse_phase(cx, phase, &mut failed); + + if failed { + return None; + } + + Some(AttributeKind::CustomMir(dialect, phase, cx.attr_span)) + } +} + +fn extract_value( + cx: &mut AcceptContext<'_, '_, S>, + key: Symbol, + arg: &ArgParser<'_>, + span: Span, + out_val: &mut Option<(Symbol, Span)>, + failed: &mut bool, +) { + if out_val.is_some() { + cx.duplicate_key(span, key); + *failed = true; + return; + } + + let Some(val) = arg.name_value() else { + cx.expected_single_argument(arg.span().unwrap_or(span)); + *failed = true; + return; + }; + + let Some(value_sym) = val.value_as_str() else { + cx.expected_string_literal(val.value_span, Some(val.value_as_lit())); + *failed = true; + return; + }; + + *out_val = Some((value_sym, val.value_span)); +} + +fn parse_dialect( + cx: &mut AcceptContext<'_, '_, S>, + dialect: Option<(Symbol, Span)>, + failed: &mut bool, +) -> Option<(MirDialect, Span)> { + let (dialect, span) = dialect?; + + let dialect = match dialect { + sym::analysis => MirDialect::Analysis, + sym::built => MirDialect::Built, + sym::runtime => MirDialect::Runtime, + + _ => { + cx.expected_specific_argument(span, vec!["analysis", "built", "runtime"]); + *failed = true; + return None; + } + }; + + Some((dialect, span)) +} + +fn parse_phase( + cx: &mut AcceptContext<'_, '_, S>, + phase: Option<(Symbol, Span)>, + failed: &mut bool, +) -> Option<(MirPhase, Span)> { + let (phase, span) = phase?; + + let phase = match phase { + sym::initial => MirPhase::Initial, + sym::post_cleanup => MirPhase::PostCleanup, + sym::optimized => MirPhase::Optimized, + + _ => { + cx.expected_specific_argument(span, vec!["initial", "post-cleanup", "optimized"]); + *failed = true; + return None; + } + }; + + Some((phase, span)) +} diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index 6087afe6ded4..23aabd155976 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -1,14 +1,9 @@ use rustc_abi::Align; use rustc_ast::{IntTy, LitIntType, LitKind, UintTy}; -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::{AttributeKind, IntType, ReprAttr}; -use rustc_span::{DUMMY_SP, Span, Symbol, sym}; +use rustc_hir::attrs::{IntType, ReprAttr}; -use super::{AcceptMapping, AttributeParser, CombineAttributeParser, ConvertFn, FinalizeContext}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::{ArgParser, MetaItemListParser, MetaItemParser}; -use crate::session_diagnostics; -use crate::session_diagnostics::IncorrectReprFormatGenericCause; +use super::prelude::*; +use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause}; /// Parse #[repr(...)] forms. /// @@ -26,8 +21,10 @@ impl CombineAttributeParser for ReprParser { const CONVERT: ConvertFn = |items, first_span| AttributeKind::Repr { reprs: items, first_span }; // FIXME(jdonszelmann): never used - const TEMPLATE: AttributeTemplate = - template!(List: "C | Rust | align(...) | packed(...) | | transparent"); + const TEMPLATE: AttributeTemplate = template!( + List: &["C", "Rust", "transparent", "align(...)", "packed(...)", ""], + "https://doc.rust-lang.org/reference/type-layout.html#representations" + ); fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, @@ -58,6 +55,10 @@ impl CombineAttributeParser for ReprParser { reprs } + + //FIXME Still checked fully in `check_attr.rs` + //This one is slightly more complicated because the allowed targets depend on the arguments + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); } macro_rules! int_pat { @@ -275,7 +276,7 @@ pub(crate) struct AlignParser(Option<(Align, Span)>); impl AlignParser { const PATH: &'static [Symbol] = &[sym::rustc_align]; - const TEMPLATE: AttributeTemplate = template!(List: ""); + const TEMPLATE: AttributeTemplate = template!(List: &[""]); fn parse<'c, S: Stage>( &mut self, @@ -316,6 +317,14 @@ impl AlignParser { impl AttributeParser for AlignParser { const ATTRIBUTES: AcceptMapping = &[(Self::PATH, Self::TEMPLATE, Self::parse)]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::ForeignFn), + ]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { let (align, span) = self.0?; diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index b465d2e62ff2..a995549fc7c8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1,10 +1,5 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Symbol, sym}; - -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage, parse_single_integer}; -use crate::parser::ArgParser; +use super::prelude::*; +use super::util::parse_single_integer; pub(crate) struct RustcLayoutScalarValidRangeStart; @@ -12,7 +7,8 @@ impl SingleAttributeParser for RustcLayoutScalarValidRangeStart { const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_start]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const TEMPLATE: AttributeTemplate = template!(List: "start"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const TEMPLATE: AttributeTemplate = template!(List: &["start"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { parse_single_integer(cx, args) @@ -26,7 +22,8 @@ impl SingleAttributeParser for RustcLayoutScalarValidRangeEnd { const PATH: &'static [Symbol] = &[sym::rustc_layout_scalar_valid_range_end]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const TEMPLATE: AttributeTemplate = template!(List: "end"); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const TEMPLATE: AttributeTemplate = template!(List: &["end"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { parse_single_integer(cx, args) @@ -40,6 +37,7 @@ impl SingleAttributeParser for RustcObjectLifetimeDefaultParser { const PATH: &[rustc_span::Symbol] = &[sym::rustc_object_lifetime_default]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/semantics.rs b/compiler/rustc_attr_parsing/src/attributes/semantics.rs index 70a8a0020996..d7f624832971 100644 --- a/compiler/rustc_attr_parsing/src/attributes/semantics.rs +++ b/compiler/rustc_attr_parsing/src/attributes/semantics.rs @@ -1,12 +1,9 @@ -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; - -use crate::attributes::{NoArgsAttributeParser, OnDuplicate}; -use crate::context::Stage; +use super::prelude::*; pub(crate) struct MayDangleParser; impl NoArgsAttributeParser for MayDangleParser { const PATH: &[Symbol] = &[sym::may_dangle]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` const CREATE: fn(span: Span) -> AttributeKind = AttributeKind::MayDangle; } diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index 3c4ec133d51c..b94e23477ffe 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -1,19 +1,13 @@ use std::num::NonZero; use rustc_errors::ErrorGuaranteed; -use rustc_feature::template; -use rustc_hir::attrs::AttributeKind; use rustc_hir::{ - DefaultBodyStability, PartialConstStability, Stability, StabilityLevel, StableSince, - UnstableReason, VERSION_PLACEHOLDER, + DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel, + StableSince, Target, UnstableReason, VERSION_PLACEHOLDER, }; -use rustc_span::{Ident, Span, Symbol, sym}; +use super::prelude::*; use super::util::parse_version; -use super::{AcceptMapping, AttributeParser, OnDuplicate}; -use crate::attributes::NoArgsAttributeParser; -use crate::context::{AcceptContext, FinalizeContext, Stage}; -use crate::parser::{ArgParser, MetaItemParser}; use crate::session_diagnostics::{self, UnsupportedLiteralReason}; macro_rules! reject_outside_std { @@ -26,6 +20,36 @@ macro_rules! reject_outside_std { }; } +const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Struct), + Allow(Target::Enum), + Allow(Target::Union), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::Impl { of_trait: false }), + Allow(Target::Impl { of_trait: true }), + Allow(Target::MacroDef), + Allow(Target::Crate), + Allow(Target::Mod), + Allow(Target::Use), // FIXME I don't think this does anything? + Allow(Target::Const), + Allow(Target::AssocConst), + Allow(Target::AssocTy), + Allow(Target::Trait), + Allow(Target::TraitAlias), + Allow(Target::TyAlias), + Allow(Target::Variant), + Allow(Target::Field), + Allow(Target::Param), + Allow(Target::Static), + Allow(Target::ForeignFn), + Allow(Target::ForeignStatic), + Allow(Target::ExternCrate), +]); + #[derive(Default)] pub(crate) struct StabilityParser { allowed_through_unstable_modules: Option, @@ -48,7 +72,7 @@ impl AttributeParser for StabilityParser { const ATTRIBUTES: AcceptMapping = &[ ( &[sym::stable], - template!(List: r#"feature = "name", since = "version""#), + template!(List: &[r#"feature = "name", since = "version""#]), |this, cx, args| { reject_outside_std!(cx); if !this.check_duplicate(cx) @@ -60,7 +84,7 @@ impl AttributeParser for StabilityParser { ), ( &[sym::unstable], - template!(List: r#"feature = "name", reason = "...", issue = "N""#), + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), |this, cx, args| { reject_outside_std!(cx); if !this.check_duplicate(cx) @@ -87,6 +111,7 @@ impl AttributeParser for StabilityParser { }, ), ]; + const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS; fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option { if let Some(atum) = self.allowed_through_unstable_modules { @@ -131,7 +156,7 @@ pub(crate) struct BodyStabilityParser { impl AttributeParser for BodyStabilityParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::rustc_default_body_unstable], - template!(List: r#"feature = "name", reason = "...", issue = "N""#), + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), |this, cx, args| { reject_outside_std!(cx); if this.stability.is_some() { @@ -142,6 +167,7 @@ impl AttributeParser for BodyStabilityParser { } }, )]; + const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS; fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { let (stability, span) = self.stability?; @@ -154,6 +180,10 @@ pub(crate) struct ConstStabilityIndirectParser; impl NoArgsAttributeParser for ConstStabilityIndirectParser { const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Fn), + Allow(Target::Method(MethodKind::Inherent)), + ]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ConstStabilityIndirect; } @@ -177,34 +207,43 @@ impl ConstStabilityParser { impl AttributeParser for ConstStabilityParser { const ATTRIBUTES: AcceptMapping = &[ - (&[sym::rustc_const_stable], template!(List: r#"feature = "name""#), |this, cx, args| { - reject_outside_std!(cx); + ( + &[sym::rustc_const_stable], + template!(List: &[r#"feature = "name""#]), + |this, cx, args| { + reject_outside_std!(cx); - if !this.check_duplicate(cx) - && let Some((feature, level)) = parse_stability(cx, args) - { - this.stability = Some(( - PartialConstStability { level, feature, promotable: false }, - cx.attr_span, - )); - } - }), - (&[sym::rustc_const_unstable], template!(List: r#"feature = "name""#), |this, cx, args| { - reject_outside_std!(cx); - if !this.check_duplicate(cx) - && let Some((feature, level)) = parse_unstability(cx, args) - { - this.stability = Some(( - PartialConstStability { level, feature, promotable: false }, - cx.attr_span, - )); - } - }), + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_stability(cx, args) + { + this.stability = Some(( + PartialConstStability { level, feature, promotable: false }, + cx.attr_span, + )); + } + }, + ), + ( + &[sym::rustc_const_unstable], + template!(List: &[r#"feature = "name""#]), + |this, cx, args| { + reject_outside_std!(cx); + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_unstability(cx, args) + { + this.stability = Some(( + PartialConstStability { level, feature, promotable: false }, + cx.attr_span, + )); + } + }, + ), (&[sym::rustc_promotable], template!(Word), |this, cx, _| { reject_outside_std!(cx); this.promotable = true; }), ]; + const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS; fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option { if self.promotable { diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index a90ed830cd1d..2b01c09ab96d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -1,11 +1,4 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_hir::lints::AttributeLintKind; -use rustc_span::{Symbol, sym}; - -use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; +use super::prelude::*; pub(crate) struct IgnoreParser; @@ -13,7 +6,12 @@ impl SingleAttributeParser for IgnoreParser { const PATH: &[Symbol] = &[sym::ignore]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; - const TEMPLATE: AttributeTemplate = template!(Word, NameValueStr: "reason"); + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]); + const TEMPLATE: AttributeTemplate = template!( + Word, NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute" + ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { Some(AttributeKind::Ignore { @@ -23,7 +21,7 @@ impl SingleAttributeParser for IgnoreParser { ArgParser::NameValue(name_value) => { let Some(str_value) = name_value.value_as_str() else { let suggestions = >::TEMPLATE - .suggestions(false, "ignore"); + .suggestions(cx.attr_style, "ignore"); let span = cx.attr_span; cx.emit_lint( AttributeLintKind::IllFormedAttributeInput { suggestions }, @@ -34,8 +32,8 @@ impl SingleAttributeParser for IgnoreParser { Some(str_value) } ArgParser::List(_) => { - let suggestions = - >::TEMPLATE.suggestions(false, "ignore"); + let suggestions = >::TEMPLATE + .suggestions(cx.attr_style, "ignore"); let span = cx.attr_span; cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span); return None; @@ -44,3 +42,59 @@ impl SingleAttributeParser for IgnoreParser { }) } } + +pub(crate) struct ShouldPanicParser; + +impl SingleAttributeParser for ShouldPanicParser { + const PATH: &[Symbol] = &[sym::should_panic]; + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowListWarnRest(&[Allow(Target::Fn), Error(Target::WherePredicate)]); + const TEMPLATE: AttributeTemplate = template!( + Word, List: &[r#"expected = "reason""#], NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute" + ); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { + Some(AttributeKind::ShouldPanic { + span: cx.attr_span, + reason: match args { + ArgParser::NoArgs => None, + ArgParser::NameValue(name_value) => { + let Some(str_value) = name_value.value_as_str() else { + cx.expected_string_literal( + name_value.value_span, + Some(name_value.value_as_lit()), + ); + return None; + }; + Some(str_value) + } + ArgParser::List(list) => { + let Some(single) = list.single() else { + cx.expected_single_argument(list.span); + return None; + }; + let Some(single) = single.meta_item() else { + cx.expected_name_value(single.span(), Some(sym::expected)); + return None; + }; + if !single.path().word_is(sym::expected) { + cx.expected_specific_argument_strings(list.span, vec!["expected"]); + return None; + } + let Some(nv) = single.args().name_value() else { + cx.expected_name_value(single.span(), Some(sym::expected)); + return None; + }; + let Some(expected) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return None; + }; + Some(expected) + } + }, + }) + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index a954617ca577..0410d818f74e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -1,22 +1,22 @@ -use core::mem; - -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; -use rustc_span::{Span, Symbol, sym}; +use std::mem; +use super::prelude::*; use crate::attributes::{ AttributeOrder, NoArgsAttributeParser, OnDuplicate, SingleAttributeParser, }; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; +use crate::target_checking::Policy::{Allow, Warn}; +use crate::target_checking::{ALL_TARGETS, AllowedTargets}; pub(crate) struct SkipDuringMethodDispatchParser; impl SingleAttributeParser for SkipDuringMethodDispatchParser { const PATH: &[Symbol] = &[sym::rustc_skip_during_method_dispatch]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const TEMPLATE: AttributeTemplate = template!(List: "array, boxed_slice"); + const TEMPLATE: AttributeTemplate = template!(List: &["array, boxed_slice"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let mut array = false; @@ -58,6 +58,7 @@ pub(crate) struct ParenSugarParser; impl NoArgsAttributeParser for ParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ParenSugar; } @@ -65,6 +66,7 @@ pub(crate) struct TypeConstParser; impl NoArgsAttributeParser for TypeConstParser { const PATH: &[Symbol] = &[sym::type_const]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::AssocConst)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::TypeConst; } @@ -74,6 +76,12 @@ pub(crate) struct MarkerParser; impl NoArgsAttributeParser for MarkerParser { const PATH: &[Symbol] = &[sym::marker]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Trait), + Warn(Target::Field), + Warn(Target::Arm), + Warn(Target::MacroDef), + ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::Marker; } @@ -81,6 +89,7 @@ pub(crate) struct DenyExplicitImplParser; impl NoArgsAttributeParser for DenyExplicitImplParser { const PATH: &[Symbol] = &[sym::rustc_deny_explicit_impl]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::DenyExplicitImpl; } @@ -88,6 +97,7 @@ pub(crate) struct DoNotImplementViaObjectParser; impl NoArgsAttributeParser for DoNotImplementViaObjectParser { const PATH: &[Symbol] = &[sym::rustc_do_not_implement_via_object]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::DoNotImplementViaObject; } @@ -98,6 +108,7 @@ pub(crate) struct ConstTraitParser; impl NoArgsAttributeParser for ConstTraitParser { const PATH: &[Symbol] = &[sym::const_trait]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::ConstTrait; } @@ -107,6 +118,7 @@ pub(crate) struct SpecializationTraitParser; impl NoArgsAttributeParser for SpecializationTraitParser { const PATH: &[Symbol] = &[sym::rustc_specialization_trait]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::SpecializationTrait; } @@ -114,6 +126,7 @@ pub(crate) struct UnsafeSpecializationMarkerParser; impl NoArgsAttributeParser for UnsafeSpecializationMarkerParser { const PATH: &[Symbol] = &[sym::rustc_unsafe_specialization_marker]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::UnsafeSpecializationMarker; } @@ -123,6 +136,7 @@ pub(crate) struct CoinductiveParser; impl NoArgsAttributeParser for CoinductiveParser { const PATH: &[Symbol] = &[sym::rustc_coinductive]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::Coinductive; } @@ -130,6 +144,8 @@ pub(crate) struct AllowIncoherentImplParser; impl NoArgsAttributeParser for AllowIncoherentImplParser { const PATH: &[Symbol] = &[sym::rustc_allow_incoherent_impl]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Method(MethodKind::Inherent))]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::AllowIncoherentImpl; } @@ -137,6 +153,7 @@ pub(crate) struct CoherenceIsCoreParser; impl NoArgsAttributeParser for CoherenceIsCoreParser { const PATH: &[Symbol] = &[sym::rustc_coherence_is_core]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::CoherenceIsCore; } @@ -144,6 +161,8 @@ pub(crate) struct FundamentalParser; impl NoArgsAttributeParser for FundamentalParser { const PATH: &[Symbol] = &[sym::fundamental]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Struct), Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::Fundamental; } @@ -151,5 +170,6 @@ pub(crate) struct PointeeParser; impl NoArgsAttributeParser for PointeeParser { const PATH: &[Symbol] = &[sym::pointee]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` const CREATE: fn(Span) -> AttributeKind = AttributeKind::Pointee; } diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs index 1c57dc1ebe2e..ce638d095309 100644 --- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs +++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs @@ -1,11 +1,6 @@ -use rustc_feature::{AttributeTemplate, template}; -use rustc_hir::attrs::AttributeKind; use rustc_span::hygiene::Transparency; -use rustc_span::{Symbol, sym}; -use super::{AttributeOrder, OnDuplicate, SingleAttributeParser}; -use crate::context::{AcceptContext, Stage}; -use crate::parser::ArgParser; +use super::prelude::*; pub(crate) struct TransparencyParser; @@ -18,8 +13,9 @@ impl SingleAttributeParser for TransparencyParser { const ON_DUPLICATE: OnDuplicate = OnDuplicate::Custom(|cx, used, unused| { cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes"); }); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]); const TEMPLATE: AttributeTemplate = - template!(NameValueStr: "transparent|semitransparent|opaque"); + template!(NameValueStr: ["transparent", "semitransparent", "opaque"]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index 10134915b278..ef9701cb7cb8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -1,8 +1,12 @@ +use rustc_ast::LitKind; use rustc_ast::attr::{AttributeExt, first_attr_value_str_by_name}; use rustc_feature::is_builtin_attr_name; use rustc_hir::RustcVersion; use rustc_span::{Symbol, sym}; +use crate::context::{AcceptContext, Stage}; +use crate::parser::ArgParser; + /// Parse a rustc version number written inside string literal in an attribute, /// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are /// not accepted in this position, unlike when parsing CFG_RELEASE. @@ -56,3 +60,32 @@ pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>( } false } + +/// Parse a single integer. +/// +/// Used by attributes that take a single integer as argument, such as +/// `#[link_ordinal]` and `#[rustc_layout_scalar_valid_range_start]`. +/// `cx` is the context given to the attribute. +/// `args` is the parser for the attribute arguments. +pub(crate) fn parse_single_integer( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser<'_>, +) -> Option { + let Some(list) = args.list() else { + cx.expected_list(cx.attr_span); + return None; + }; + let Some(single) = list.single() else { + cx.expected_single_argument(list.span); + return None; + }; + let Some(lit) = single.lit() else { + cx.expected_integer_literal(single.span()); + return None; + }; + let LitKind::Int(num, _ty) = lit.kind else { + cx.expected_integer_literal(single.span()); + return None; + }; + Some(num.0) +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 54c0fbcd0626..d79177076c77 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -4,22 +4,23 @@ use std::ops::{Deref, DerefMut}; use std::sync::LazyLock; use private::Sealed; -use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId}; -use rustc_errors::{DiagCtxtHandle, Diagnostic}; -use rustc_feature::{AttributeTemplate, Features}; +use rustc_ast::{AttrStyle, MetaItemLit, NodeId}; +use rustc_errors::Diagnostic; +use rustc_feature::AttributeTemplate; use rustc_hir::attrs::AttributeKind; use rustc_hir::lints::{AttributeLint, AttributeLintKind}; -use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirId}; +use rustc_hir::{AttrPath, HirId}; use rustc_session::Session; -use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym}; +use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use crate::AttributeParser; use crate::attributes::allow_unstable::{ AllowConstFnUnstableParser, AllowInternalUnstableParser, UnstableFeatureBoundParser, }; +use crate::attributes::body::CoroutineParser; use crate::attributes::codegen_attrs::{ - ColdParser, CoverageParser, ExportNameParser, NakedParser, NoMangleParser, - OmitGdbPrettyPrinterSectionParser, OptimizeParser, TargetFeatureParser, TrackCallerParser, - UsedParser, + ColdParser, CoverageParser, ExportNameParser, NakedParser, NoMangleParser, OptimizeParser, + TargetFeatureParser, TrackCallerParser, UsedParser, }; use crate::attributes::confusables::ConfusablesParser; use crate::attributes::deprecation::DeprecationParser; @@ -27,13 +28,15 @@ use crate::attributes::dummy::DummyParser; use crate::attributes::inline::{InlineParser, RustcForceInlineParser}; use crate::attributes::link_attrs::{ ExportStableParser, FfiConstParser, FfiPureParser, LinkNameParser, LinkOrdinalParser, - LinkSectionParser, StdInternalSymbolParser, + LinkSectionParser, LinkageParser, StdInternalSymbolParser, }; use crate::attributes::lint_helpers::{ AsPtrParser, AutomaticallyDerivedParser, PassByValueParser, PubTransparentParser, }; use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser}; -use crate::attributes::macro_attrs::{MacroEscapeParser, MacroUseParser}; +use crate::attributes::macro_attrs::{ + AllowInternalUnsafeParser, MacroEscapeParser, MacroUseParser, +}; use crate::attributes::must_use::MustUseParser; use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser; use crate::attributes::non_exhaustive::NonExhaustiveParser; @@ -41,6 +44,7 @@ use crate::attributes::path::PathParser as PathAttributeParser; use crate::attributes::proc_macro_attrs::{ ProcMacroAttributeParser, ProcMacroDeriveParser, ProcMacroParser, RustcBuiltinMacroParser, }; +use crate::attributes::prototype::CustomMirParser; use crate::attributes::repr::{AlignParser, ReprParser}; use crate::attributes::rustc_internal::{ RustcLayoutScalarValidRangeEnd, RustcLayoutScalarValidRangeStart, @@ -50,7 +54,7 @@ use crate::attributes::semantics::MayDangleParser; use crate::attributes::stability::{ BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser, }; -use crate::attributes::test_attrs::IgnoreParser; +use crate::attributes::test_attrs::{IgnoreParser, ShouldPanicParser}; use crate::attributes::traits::{ AllowIncoherentImplParser, CoherenceIsCoreParser, CoinductiveParser, ConstTraitParser, DenyExplicitImplParser, DoNotImplementViaObjectParser, FundamentalParser, MarkerParser, @@ -59,18 +63,28 @@ use crate::attributes::traits::{ }; use crate::attributes::transparency::TransparencyParser; use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs}; -use crate::parser::{ArgParser, MetaItemParser, PathParser}; +use crate::parser::{ArgParser, PathParser}; use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem}; +use crate::target_checking::AllowedTargets; -macro_rules! group_type { - ($stage: ty) => { - LazyLock<( - BTreeMap<&'static [Symbol], Vec<(AttributeTemplate, Box Fn(&mut AcceptContext<'_, 'sess, $stage>, &ArgParser<'a>) + Send + Sync>)>>, - Vec) -> Option>> - )> - }; +type GroupType = LazyLock>; + +pub(super) struct GroupTypeInner { + pub(super) accepters: BTreeMap<&'static [Symbol], Vec>>, + pub(super) finalizers: Vec>, +} + +pub(super) struct GroupTypeInnerAccept { + pub(super) template: AttributeTemplate, + pub(super) accept_fn: AcceptFn, + pub(super) allowed_targets: AllowedTargets, } +type AcceptFn = + Box Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser<'a>) + Send + Sync>; +type FinalizeFn = + Box) -> Option>; + macro_rules! attribute_parsers { ( pub(crate) static $name: ident = [$($names: ty),* $(,)?]; @@ -93,11 +107,11 @@ macro_rules! attribute_parsers { } }; ( - @[$ty: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?]; + @[$stage: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?]; ) => { - pub(crate) static $name: group_type!($ty) = LazyLock::new(|| { - let mut accepts = BTreeMap::<_, Vec<(AttributeTemplate, Box Fn(&mut AcceptContext<'_, 'sess, $ty>, &ArgParser<'a>) + Send + Sync>)>>::new(); - let mut finalizes = Vec::) -> Option>>::new(); + pub(crate) static $name: GroupType<$stage> = LazyLock::new(|| { + let mut accepts = BTreeMap::<_, Vec>>::new(); + let mut finalizes = Vec::>::new(); $( { thread_local! { @@ -105,11 +119,15 @@ macro_rules! attribute_parsers { }; for (path, template, accept_fn) in <$names>::ATTRIBUTES { - accepts.entry(*path).or_default().push((*template, Box::new(|cx, args| { - STATE_OBJECT.with_borrow_mut(|s| { - accept_fn(s, cx, args) - }) - }))); + accepts.entry(*path).or_default().push(GroupTypeInnerAccept { + template: *template, + accept_fn: Box::new(|cx, args| { + STATE_OBJECT.with_borrow_mut(|s| { + accept_fn(s, cx, args) + }) + }), + allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS, + }); } finalizes.push(Box::new(|cx| { @@ -119,7 +137,7 @@ macro_rules! attribute_parsers { } )* - (accepts, finalizes) + GroupTypeInner { accepters:accepts, finalizers:finalizes } }); }; } @@ -146,6 +164,7 @@ attribute_parsers!( // tidy-alphabetical-start Single, + Single, Single, Single, Single, @@ -154,6 +173,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, @@ -163,9 +183,11 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single>, + Single>, Single>, Single>, Single>, @@ -174,6 +196,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, @@ -187,7 +210,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, @@ -213,24 +235,24 @@ mod private { #[allow(private_interfaces)] pub trait Stage: Sized + 'static + Sealed { type Id: Copy; - const SHOULD_EMIT_LINTS: bool; - fn parsers() -> &'static group_type!(Self); + fn parsers() -> &'static GroupType; fn emit_err<'sess>( &self, sess: &'sess Session, diag: impl for<'x> Diagnostic<'x>, ) -> ErrorGuaranteed; + + fn should_emit(&self) -> ShouldEmit; } // allow because it's a sealed trait #[allow(private_interfaces)] impl Stage for Early { type Id = NodeId; - const SHOULD_EMIT_LINTS: bool = false; - fn parsers() -> &'static group_type!(Self) { + fn parsers() -> &'static GroupType { &early::ATTRIBUTE_PARSERS } fn emit_err<'sess>( @@ -244,15 +266,18 @@ impl Stage for Early { sess.dcx().create_err(diag).delay_as_bug() } } + + fn should_emit(&self) -> ShouldEmit { + self.emit_errors + } } // allow because it's a sealed trait #[allow(private_interfaces)] impl Stage for Late { type Id = HirId; - const SHOULD_EMIT_LINTS: bool = true; - fn parsers() -> &'static group_type!(Self) { + fn parsers() -> &'static GroupType { &late::ATTRIBUTE_PARSERS } fn emit_err<'sess>( @@ -262,6 +287,10 @@ impl Stage for Late { ) -> ErrorGuaranteed { tcx.dcx().emit_err(diag) } + + fn should_emit(&self) -> ShouldEmit { + ShouldEmit::ErrorsAndLints + } } /// used when parsing attributes for miscellaneous things *before* ast lowering @@ -282,6 +311,7 @@ pub struct AcceptContext<'f, 'sess, S: Stage> { /// The span of the attribute currently being parsed pub(crate) attr_span: Span, + pub(crate) attr_style: AttrStyle, /// The expected structure of the attribute. /// /// Used in reporting errors to give a hint to users what the attribute *should* look like. @@ -300,7 +330,7 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> { /// must be delayed until after HIR is built. This method will take care of the details of /// that. pub(crate) fn emit_lint(&mut self, lint: AttributeLintKind, span: Span) { - if !S::SHOULD_EMIT_LINTS { + if !self.stage.should_emit().should_emit() { return; } let id = self.target_id; @@ -363,6 +393,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span)) }), }, + attr_style: self.attr_style, }) } @@ -373,6 +404,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedIntegerLiteral, + attr_style: self.attr_style, }) } @@ -383,6 +415,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedList, + attr_style: self.attr_style, }) } @@ -393,6 +426,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedNoArgs, + attr_style: self.attr_style, }) } @@ -404,6 +438,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedIdentifier, + attr_style: self.attr_style, }) } @@ -416,6 +451,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedNameValue(name), + attr_style: self.attr_style, }) } @@ -427,6 +463,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::DuplicateKey(key), + attr_style: self.attr_style, }) } @@ -439,6 +476,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::UnexpectedLiteral, + attr_style: self.attr_style, }) } @@ -449,6 +487,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedSingleArgument, + attr_style: self.attr_style, }) } @@ -459,6 +498,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { template: self.template.clone(), attribute: self.attr_path.clone(), reason: AttributeParseErrorReason::ExpectedAtLeastOneArgument, + attr_style: self.attr_style, }) } @@ -477,6 +517,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: false, list: false, }, + attr_style: self.attr_style, }) } @@ -495,6 +536,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: false, list: true, }, + attr_style: self.attr_style, }) } @@ -513,6 +555,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { strings: true, list: false, }, + attr_style: self.attr_style, }) } @@ -548,7 +591,7 @@ pub struct SharedContext<'p, 'sess, S: Stage> { /// The id ([`NodeId`] if `S` is `Early`, [`HirId`] if `S` is `Late`) of the syntactical component this attribute was applied to pub(crate) target_id: S::Id, - emit_lint: &'p mut dyn FnMut(AttributeLint), + pub(crate) emit_lint: &'p mut dyn FnMut(AttributeLint), } /// Context given to every attribute parser during finalization. @@ -619,325 +662,3 @@ impl ShouldEmit { } } } - -/// Context created once, for example as part of the ast lowering -/// context, through which all attributes can be lowered. -pub struct AttributeParser<'sess, S: Stage = Late> { - pub(crate) tools: Vec, - features: Option<&'sess Features>, - sess: &'sess Session, - stage: S, - - /// *Only* parse attributes with this symbol. - /// - /// Used in cases where we want the lowering infrastructure for parse just a single attribute. - parse_only: Option, -} - -impl<'sess> AttributeParser<'sess, Early> { - /// This method allows you to parse attributes *before* you have access to features or tools. - /// One example where this is necessary, is to parse `feature` attributes themselves for - /// example. - /// - /// Try to use this as little as possible. Attributes *should* be lowered during - /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would - /// crash if you tried to do so through [`parse_limited`](Self::parse_limited). - /// - /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with - /// that symbol are picked out of the list of instructions and parsed. Those are returned. - /// - /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while - /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed - /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors - pub fn parse_limited( - sess: &'sess Session, - attrs: &[ast::Attribute], - sym: Symbol, - target_span: Span, - target_node_id: NodeId, - features: Option<&'sess Features>, - ) -> Option { - let mut p = Self { - features, - tools: Vec::new(), - parse_only: Some(sym), - sess, - stage: Early { emit_errors: ShouldEmit::Nothing }, - }; - let mut parsed = p.parse_attribute_list( - attrs, - target_span, - target_node_id, - OmitDoc::Skip, - std::convert::identity, - |_lint| { - panic!("can't emit lints here for now (nothing uses this atm)"); - }, - ); - assert!(parsed.len() <= 1); - - parsed.pop() - } - - pub fn parse_single( - sess: &'sess Session, - attr: &ast::Attribute, - target_span: Span, - target_node_id: NodeId, - features: Option<&'sess Features>, - emit_errors: ShouldEmit, - parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> T, - template: &AttributeTemplate, - ) -> T { - let mut parser = Self { - features, - tools: Vec::new(), - parse_only: None, - sess, - stage: Early { emit_errors }, - }; - let ast::AttrKind::Normal(normal_attr) = &attr.kind else { - panic!("parse_single called on a doc attr") - }; - let meta_parser = MetaItemParser::from_attr(normal_attr, parser.dcx()); - let path = meta_parser.path(); - let args = meta_parser.args(); - let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext { - shared: SharedContext { - cx: &mut parser, - target_span, - target_id: target_node_id, - emit_lint: &mut |_lint| { - panic!("can't emit lints here for now (nothing uses this atm)"); - }, - }, - attr_span: attr.span, - template, - attr_path: path.get_attribute_path(), - }; - parse_fn(&mut cx, args) - } -} - -impl<'sess, S: Stage> AttributeParser<'sess, S> { - pub fn new( - sess: &'sess Session, - features: &'sess Features, - tools: Vec, - stage: S, - ) -> Self { - Self { features: Some(features), tools, parse_only: None, sess, stage } - } - - pub(crate) fn sess(&self) -> &'sess Session { - &self.sess - } - - pub(crate) fn features(&self) -> &'sess Features { - self.features.expect("features not available at this point in the compiler") - } - - pub(crate) fn features_option(&self) -> Option<&'sess Features> { - self.features - } - - pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> { - self.sess().dcx() - } - - /// Parse a list of attributes. - /// - /// `target_span` is the span of the thing this list of attributes is applied to, - /// and when `omit_doc` is set, doc attributes are filtered out. - pub fn parse_attribute_list( - &mut self, - attrs: &[ast::Attribute], - target_span: Span, - target_id: S::Id, - omit_doc: OmitDoc, - - lower_span: impl Copy + Fn(Span) -> Span, - mut emit_lint: impl FnMut(AttributeLint), - ) -> Vec { - let mut attributes = Vec::new(); - let mut attr_paths = Vec::new(); - - for attr in attrs { - // If we're only looking for a single attribute, skip all the ones we don't care about. - if let Some(expected) = self.parse_only { - if !attr.has_name(expected) { - continue; - } - } - - // Sometimes, for example for `#![doc = include_str!("readme.md")]`, - // doc still contains a non-literal. You might say, when we're lowering attributes - // that's expanded right? But no, sometimes, when parsing attributes on macros, - // we already use the lowering logic and these are still there. So, when `omit_doc` - // is set we *also* want to ignore these. - if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) { - continue; - } - - match &attr.kind { - ast::AttrKind::DocComment(comment_kind, symbol) => { - if omit_doc == OmitDoc::Skip { - continue; - } - - attributes.push(Attribute::Parsed(AttributeKind::DocComment { - style: attr.style, - kind: *comment_kind, - span: lower_span(attr.span), - comment: *symbol, - })) - } - // // FIXME: make doc attributes go through a proper attribute parser - // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => { - // let p = GenericMetaItemParser::from_attr(&n, self.dcx()); - // - // attributes.push(Attribute::Parsed(AttributeKind::DocComment { - // style: attr.style, - // kind: CommentKind::Line, - // span: attr.span, - // comment: p.args().name_value(), - // })) - // } - ast::AttrKind::Normal(n) => { - attr_paths.push(PathParser::Ast(&n.item.path)); - - let parser = MetaItemParser::from_attr(n, self.dcx()); - let path = parser.path(); - let args = parser.args(); - let parts = path.segments().map(|i| i.name).collect::>(); - - if let Some(accepts) = S::parsers().0.get(parts.as_slice()) { - for (template, accept) in accepts { - let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext { - shared: SharedContext { - cx: self, - target_span, - target_id, - emit_lint: &mut emit_lint, - }, - attr_span: lower_span(attr.span), - template, - attr_path: path.get_attribute_path(), - }; - - accept(&mut cx, args) - } - } else { - // If we're here, we must be compiling a tool attribute... Or someone - // forgot to parse their fancy new attribute. Let's warn them in any case. - // If you are that person, and you really think your attribute should - // remain unparsed, carefully read the documentation in this module and if - // you still think so you can add an exception to this assertion. - - // FIXME(jdonszelmann): convert other attributes, and check with this that - // we caught em all - // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg]; - // assert!( - // self.tools.contains(&parts[0]) || true, - // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]), - // "attribute {path} wasn't parsed and isn't a know tool attribute", - // ); - - attributes.push(Attribute::Unparsed(Box::new(AttrItem { - path: AttrPath::from_ast(&n.item.path), - args: self.lower_attr_args(&n.item.args, lower_span), - id: HashIgnoredAttrId { attr_id: attr.id }, - style: attr.style, - span: lower_span(attr.span), - }))); - } - } - } - } - - let mut parsed_attributes = Vec::new(); - for f in &S::parsers().1 { - if let Some(attr) = f(&mut FinalizeContext { - shared: SharedContext { - cx: self, - target_span, - target_id, - emit_lint: &mut emit_lint, - }, - all_attrs: &attr_paths, - }) { - parsed_attributes.push(Attribute::Parsed(attr)); - } - } - - attributes.extend(parsed_attributes); - - attributes - } - - /// Returns whether there is a parser for an attribute with this name - pub fn is_parsed_attribute(path: &[Symbol]) -> bool { - Late::parsers().0.contains_key(path) - } - - fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs { - match args { - ast::AttrArgs::Empty => AttrArgs::Empty, - ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()), - // This is an inert key-value attribute - it will never be visible to macros - // after it gets lowered to HIR. Therefore, we can extract literals to handle - // nonterminals in `#[doc]` (e.g. `#[doc = $e]`). - ast::AttrArgs::Eq { eq_span, expr } => { - // In valid code the value always ends up as a single literal. Otherwise, a dummy - // literal suffices because the error is handled elsewhere. - let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind - && let Ok(lit) = - ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span)) - { - lit - } else { - let guar = self.dcx().span_delayed_bug( - args.span().unwrap_or(DUMMY_SP), - "expr in place where literal is expected (builtin attr parsing)", - ); - ast::MetaItemLit { - symbol: sym::dummy, - suffix: None, - kind: ast::LitKind::Err(guar), - span: DUMMY_SP, - } - }; - AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit } - } - } - } -} - -/// Parse a single integer. -/// -/// Used by attributes that take a single integer as argument, such as -/// `#[link_ordinal]` and `#[rustc_layout_scalar_valid_range_start]`. -/// `cx` is the context given to the attribute. -/// `args` is the parser for the attribute arguments. -pub(crate) fn parse_single_integer( - cx: &mut AcceptContext<'_, '_, S>, - args: &ArgParser<'_>, -) -> Option { - let Some(list) = args.list() else { - cx.expected_list(cx.attr_span); - return None; - }; - let Some(single) = list.single() else { - cx.expected_single_argument(list.span); - return None; - }; - let Some(lit) = single.lit() else { - cx.expected_integer_literal(single.span()); - return None; - }; - let LitKind::Int(num, _ty) = lit.kind else { - cx.expected_integer_literal(single.span()); - return None; - }; - Some(num.0) -} diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs new file mode 100644 index 000000000000..22bbea766f29 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -0,0 +1,321 @@ +use rustc_ast as ast; +use rustc_ast::NodeId; +use rustc_errors::DiagCtxtHandle; +use rustc_feature::{AttributeTemplate, Features}; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::lints::AttributeLint; +use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target}; +use rustc_session::Session; +use rustc_span::{DUMMY_SP, Span, Symbol, sym}; + +use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage}; +use crate::parser::{ArgParser, MetaItemParser, PathParser}; +use crate::{Early, Late, OmitDoc, ShouldEmit}; + +/// Context created once, for example as part of the ast lowering +/// context, through which all attributes can be lowered. +pub struct AttributeParser<'sess, S: Stage = Late> { + pub(crate) tools: Vec, + pub(crate) features: Option<&'sess Features>, + pub(crate) sess: &'sess Session, + pub(crate) stage: S, + + /// *Only* parse attributes with this symbol. + /// + /// Used in cases where we want the lowering infrastructure for parse just a single attribute. + parse_only: Option, +} + +impl<'sess> AttributeParser<'sess, Early> { + /// This method allows you to parse attributes *before* you have access to features or tools. + /// One example where this is necessary, is to parse `feature` attributes themselves for + /// example. + /// + /// Try to use this as little as possible. Attributes *should* be lowered during + /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would + /// crash if you tried to do so through [`parse_limited`](Self::parse_limited). + /// + /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with + /// that symbol are picked out of the list of instructions and parsed. Those are returned. + /// + /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while + /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed + /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors + pub fn parse_limited( + sess: &'sess Session, + attrs: &[ast::Attribute], + sym: Symbol, + target_span: Span, + target_node_id: NodeId, + features: Option<&'sess Features>, + ) -> Option { + let mut p = Self { + features, + tools: Vec::new(), + parse_only: Some(sym), + sess, + stage: Early { emit_errors: ShouldEmit::Nothing }, + }; + let mut parsed = p.parse_attribute_list( + attrs, + target_span, + target_node_id, + Target::Crate, // Does not matter, we're not going to emit errors anyways + OmitDoc::Skip, + std::convert::identity, + |_lint| { + panic!("can't emit lints here for now (nothing uses this atm)"); + }, + ); + assert!(parsed.len() <= 1); + + parsed.pop() + } + + pub fn parse_single( + sess: &'sess Session, + attr: &ast::Attribute, + target_span: Span, + target_node_id: NodeId, + features: Option<&'sess Features>, + emit_errors: ShouldEmit, + parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> T, + template: &AttributeTemplate, + ) -> T { + let mut parser = Self { + features, + tools: Vec::new(), + parse_only: None, + sess, + stage: Early { emit_errors }, + }; + let ast::AttrKind::Normal(normal_attr) = &attr.kind else { + panic!("parse_single called on a doc attr") + }; + let meta_parser = MetaItemParser::from_attr(normal_attr, parser.dcx()); + let path = meta_parser.path(); + let args = meta_parser.args(); + let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext { + shared: SharedContext { + cx: &mut parser, + target_span, + target_id: target_node_id, + emit_lint: &mut |_lint| { + panic!("can't emit lints here for now (nothing uses this atm)"); + }, + }, + attr_span: attr.span, + attr_style: attr.style, + template, + attr_path: path.get_attribute_path(), + }; + parse_fn(&mut cx, args) + } +} + +impl<'sess, S: Stage> AttributeParser<'sess, S> { + pub fn new( + sess: &'sess Session, + features: &'sess Features, + tools: Vec, + stage: S, + ) -> Self { + Self { features: Some(features), tools, parse_only: None, sess, stage } + } + + pub(crate) fn sess(&self) -> &'sess Session { + &self.sess + } + + pub(crate) fn features(&self) -> &'sess Features { + self.features.expect("features not available at this point in the compiler") + } + + pub(crate) fn features_option(&self) -> Option<&'sess Features> { + self.features + } + + pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> { + self.sess().dcx() + } + + /// Parse a list of attributes. + /// + /// `target_span` is the span of the thing this list of attributes is applied to, + /// and when `omit_doc` is set, doc attributes are filtered out. + pub fn parse_attribute_list( + &mut self, + attrs: &[ast::Attribute], + target_span: Span, + target_id: S::Id, + target: Target, + omit_doc: OmitDoc, + + lower_span: impl Copy + Fn(Span) -> Span, + mut emit_lint: impl FnMut(AttributeLint), + ) -> Vec { + let mut attributes = Vec::new(); + let mut attr_paths = Vec::new(); + + for attr in attrs { + // If we're only looking for a single attribute, skip all the ones we don't care about. + if let Some(expected) = self.parse_only { + if !attr.has_name(expected) { + continue; + } + } + + // Sometimes, for example for `#![doc = include_str!("readme.md")]`, + // doc still contains a non-literal. You might say, when we're lowering attributes + // that's expanded right? But no, sometimes, when parsing attributes on macros, + // we already use the lowering logic and these are still there. So, when `omit_doc` + // is set we *also* want to ignore these. + if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) { + continue; + } + + match &attr.kind { + ast::AttrKind::DocComment(comment_kind, symbol) => { + if omit_doc == OmitDoc::Skip { + continue; + } + + attributes.push(Attribute::Parsed(AttributeKind::DocComment { + style: attr.style, + kind: *comment_kind, + span: lower_span(attr.span), + comment: *symbol, + })) + } + // // FIXME: make doc attributes go through a proper attribute parser + // ast::AttrKind::Normal(n) if n.has_name(sym::doc) => { + // let p = GenericMetaItemParser::from_attr(&n, self.dcx()); + // + // attributes.push(Attribute::Parsed(AttributeKind::DocComment { + // style: attr.style, + // kind: CommentKind::Line, + // span: attr.span, + // comment: p.args().name_value(), + // })) + // } + ast::AttrKind::Normal(n) => { + attr_paths.push(PathParser::Ast(&n.item.path)); + + let parser = MetaItemParser::from_attr(n, self.dcx()); + let path = parser.path(); + let args = parser.args(); + let path_parts = path.segments().map(|i| i.name).collect::>(); + + if let Some(accepts) = S::parsers().accepters.get(path_parts.as_slice()) { + for accept in accepts { + let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext { + shared: SharedContext { + cx: self, + target_span, + target_id, + emit_lint: &mut emit_lint, + }, + attr_span: lower_span(attr.span), + attr_style: attr.style, + template: &accept.template, + attr_path: path.get_attribute_path(), + }; + + (accept.accept_fn)(&mut cx, args); + + if self.stage.should_emit().should_emit() { + self.check_target( + path.get_attribute_path(), + attr.span, + &accept.allowed_targets, + target, + target_id, + &mut emit_lint, + ); + } + } + } else { + // If we're here, we must be compiling a tool attribute... Or someone + // forgot to parse their fancy new attribute. Let's warn them in any case. + // If you are that person, and you really think your attribute should + // remain unparsed, carefully read the documentation in this module and if + // you still think so you can add an exception to this assertion. + + // FIXME(jdonszelmann): convert other attributes, and check with this that + // we caught em all + // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg]; + // assert!( + // self.tools.contains(&parts[0]) || true, + // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]), + // "attribute {path} wasn't parsed and isn't a know tool attribute", + // ); + + attributes.push(Attribute::Unparsed(Box::new(AttrItem { + path: AttrPath::from_ast(&n.item.path), + args: self.lower_attr_args(&n.item.args, lower_span), + id: HashIgnoredAttrId { attr_id: attr.id }, + style: attr.style, + span: lower_span(attr.span), + }))); + } + } + } + } + + let mut parsed_attributes = Vec::new(); + for f in &S::parsers().finalizers { + if let Some(attr) = f(&mut FinalizeContext { + shared: SharedContext { + cx: self, + target_span, + target_id, + emit_lint: &mut emit_lint, + }, + all_attrs: &attr_paths, + }) { + parsed_attributes.push(Attribute::Parsed(attr)); + } + } + + attributes.extend(parsed_attributes); + + attributes + } + + /// Returns whether there is a parser for an attribute with this name + pub fn is_parsed_attribute(path: &[Symbol]) -> bool { + Late::parsers().accepters.contains_key(path) + } + + fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs { + match args { + ast::AttrArgs::Empty => AttrArgs::Empty, + ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(args.clone()), + // This is an inert key-value attribute - it will never be visible to macros + // after it gets lowered to HIR. Therefore, we can extract literals to handle + // nonterminals in `#[doc]` (e.g. `#[doc = $e]`). + ast::AttrArgs::Eq { eq_span, expr } => { + // In valid code the value always ends up as a single literal. Otherwise, a dummy + // literal suffices because the error is handled elsewhere. + let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind + && let Ok(lit) = + ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span)) + { + lit + } else { + let guar = self.dcx().span_delayed_bug( + args.span().unwrap_or(DUMMY_SP), + "expr in place where literal is expected (builtin attr parsing)", + ); + ast::MetaItemLit { + symbol: sym::dummy, + suffix: None, + kind: ast::LitKind::Err(guar), + span: DUMMY_SP, + } + }; + AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit } + } + } + } +} diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index fc1377e53143..99842cd9687a 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -84,18 +84,32 @@ // tidy-alphabetical-end #[macro_use] +/// All the individual attribute parsers for each of rustc's built-in attributes. mod attributes; + +/// All the important types given to attribute parsers when parsing pub(crate) mod context; -mod lints; + +/// Code that other crates interact with, to actually parse a list (or sometimes single) +/// attribute. +mod interface; + +/// Despite this entire module called attribute parsing and the term being a little overloaded, +/// in this module the code lives that actually breaks up tokenstreams into semantic pieces of attributes, +/// like lists or name-value pairs. pub mod parser; + +mod lints; mod session_diagnostics; +mod target_checking; pub use attributes::cfg::{CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr}; pub use attributes::cfg_old::*; pub use attributes::util::{ find_crate_name, is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version, }; -pub use context::{AttributeParser, Early, Late, OmitDoc, ShouldEmit}; +pub use context::{Early, Late, OmitDoc, ShouldEmit}; +pub use interface::AttributeParser; pub use lints::emit_attribute_lint; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_attr_parsing/src/lints.rs b/compiler/rustc_attr_parsing/src/lints.rs index 22f5531bc807..7030f28f23c7 100644 --- a/compiler/rustc_attr_parsing/src/lints.rs +++ b/compiler/rustc_attr_parsing/src/lints.rs @@ -1,6 +1,9 @@ +use std::borrow::Cow; + use rustc_errors::{DiagArgValue, LintEmitter}; -use rustc_hir::HirId; use rustc_hir::lints::{AttributeLint, AttributeLintKind}; +use rustc_hir::{HirId, Target}; +use rustc_span::sym; use crate::session_diagnostics; @@ -34,5 +37,28 @@ pub fn emit_attribute_lint(lint: &AttributeLint, lint_emi *first_span, session_diagnostics::EmptyAttributeList { attr_span: *first_span }, ), + AttributeLintKind::InvalidTarget { name, target, applied, only } => lint_emitter + .emit_node_span_lint( + // This check is here because `deprecated` had its own lint group and removing this would be a breaking change + if name.segments[0].name == sym::deprecated + && ![Target::Closure, Target::Expression, Target::Statement, Target::Arm] + .contains(target) + { + rustc_session::lint::builtin::USELESS_DEPRECATED + } else { + rustc_session::lint::builtin::UNUSED_ATTRIBUTES + }, + *id, + *span, + session_diagnostics::InvalidTargetLint { + name, + target: target.plural_name(), + applied: DiagArgValue::StrListSepByAnd( + applied.into_iter().map(|i| Cow::Owned(i.to_string())).collect(), + ), + only, + attr_span: *span, + }, + ), } } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 1de25ca252b8..2993881f7175 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1,6 +1,6 @@ use std::num::IntErrorKind; -use rustc_ast as ast; +use rustc_ast::{self as ast, AttrStyle}; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, @@ -480,6 +480,32 @@ pub(crate) struct EmptyAttributeList { pub attr_span: Span, } +#[derive(LintDiagnostic)] +#[diag(attr_parsing_invalid_target_lint)] +#[warning] +#[help] +pub(crate) struct InvalidTargetLint<'a> { + pub name: &'a AttrPath, + pub target: &'a str, + pub applied: DiagArgValue, + pub only: &'static str, + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] + pub attr_span: Span, +} + +#[derive(Diagnostic)] +#[help] +#[diag(attr_parsing_invalid_target)] +pub(crate) struct InvalidTarget { + #[primary_span] + #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")] + pub span: Span, + pub name: AttrPath, + pub target: &'static str, + pub applied: DiagArgValue, + pub only: &'static str, +} + #[derive(Diagnostic)] #[diag(attr_parsing_invalid_alignment_value, code = E0589)] pub(crate) struct InvalidAlignmentValue { @@ -498,6 +524,7 @@ pub(crate) struct ReprIdent { #[derive(Diagnostic)] #[diag(attr_parsing_unrecognized_repr_hint, code = E0552)] #[help] +#[note] pub(crate) struct UnrecognizedReprHint { #[primary_span] pub span: Span, @@ -555,6 +582,7 @@ pub(crate) enum AttributeParseErrorReason { pub(crate) struct AttributeParseError { pub(crate) span: Span, pub(crate) attr_span: Span, + pub(crate) attr_style: AttrStyle, pub(crate) template: AttributeTemplate, pub(crate) attribute: AttrPath, pub(crate) reason: AttributeParseErrorReason, @@ -690,7 +718,11 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError { } } - let suggestions = self.template.suggestions(false, &name); + if let Some(link) = self.template.docs { + diag.note(format!("for more information, visit <{link}>")); + } + let suggestions = self.template.suggestions(self.attr_style, &name); + diag.span_suggestions( self.attr_span, if suggestions.len() == 1 { diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs new file mode 100644 index 000000000000..9568b791b3f8 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -0,0 +1,247 @@ +use std::borrow::Cow; + +use rustc_errors::DiagArgValue; +use rustc_feature::Features; +use rustc_hir::lints::{AttributeLint, AttributeLintKind}; +use rustc_hir::{AttrPath, MethodKind, Target}; +use rustc_span::Span; + +use crate::AttributeParser; +use crate::context::Stage; +use crate::session_diagnostics::InvalidTarget; + +#[derive(Debug)] +pub(crate) enum AllowedTargets { + AllowList(&'static [Policy]), + AllowListWarnRest(&'static [Policy]), +} + +pub(crate) enum AllowedResult { + Allowed, + Warn, + Error, +} + +impl AllowedTargets { + pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult { + match self { + AllowedTargets::AllowList(list) => { + if list.contains(&Policy::Allow(target)) { + AllowedResult::Allowed + } else if list.contains(&Policy::Warn(target)) { + AllowedResult::Warn + } else { + AllowedResult::Error + } + } + AllowedTargets::AllowListWarnRest(list) => { + if list.contains(&Policy::Allow(target)) { + AllowedResult::Allowed + } else if list.contains(&Policy::Error(target)) { + AllowedResult::Error + } else { + AllowedResult::Warn + } + } + } + } + + pub(crate) fn allowed_targets(&self) -> Vec { + match self { + AllowedTargets::AllowList(list) => list, + AllowedTargets::AllowListWarnRest(list) => list, + } + .iter() + .filter_map(|target| match target { + Policy::Allow(target) => Some(*target), + Policy::Warn(_) => None, + Policy::Error(_) => None, + }) + .collect() + } +} + +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum Policy { + Allow(Target), + Warn(Target), + Error(Target), +} + +impl AttributeParser<'_, S> { + pub(crate) fn check_target( + &self, + attr_name: AttrPath, + attr_span: Span, + allowed_targets: &AllowedTargets, + target: Target, + target_id: S::Id, + mut emit_lint: impl FnMut(AttributeLint), + ) { + match allowed_targets.is_allowed(target) { + AllowedResult::Allowed => {} + AllowedResult::Warn => { + let allowed_targets = allowed_targets.allowed_targets(); + let (applied, only) = + allowed_targets_applied(allowed_targets, target, self.features); + emit_lint(AttributeLint { + id: target_id, + span: attr_span, + kind: AttributeLintKind::InvalidTarget { + name: attr_name, + target, + only: if only { "only " } else { "" }, + applied, + }, + }); + } + AllowedResult::Error => { + let allowed_targets = allowed_targets.allowed_targets(); + let (applied, only) = + allowed_targets_applied(allowed_targets, target, self.features); + self.dcx().emit_err(InvalidTarget { + span: attr_span, + name: attr_name, + target: target.plural_name(), + only: if only { "only " } else { "" }, + applied: DiagArgValue::StrListSepByAnd( + applied.into_iter().map(Cow::Owned).collect(), + ), + }); + } + } + } +} + +/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. +/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string +pub(crate) fn allowed_targets_applied( + mut allowed_targets: Vec, + target: Target, + features: Option<&Features>, +) -> (Vec, bool) { + // Remove unstable targets from `allowed_targets` if their features are not enabled + if let Some(features) = features { + if !features.fn_delegation() { + allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. })); + } + if !features.stmt_expr_attributes() { + allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement)); + } + if !features.extern_types() { + allowed_targets.retain(|t| !matches!(t, Target::ForeignTy)); + } + } + + // We define groups of "similar" targets. + // If at least two of the targets are allowed, and the `target` is not in the group, + // we collapse the entire group to a single entry to simplify the target list + const FUNCTION_LIKE: &[Target] = &[ + Target::Fn, + Target::Closure, + Target::ForeignFn, + Target::Method(MethodKind::Inherent), + Target::Method(MethodKind::Trait { body: false }), + Target::Method(MethodKind::Trait { body: true }), + Target::Method(MethodKind::TraitImpl), + ]; + const METHOD_LIKE: &[Target] = &[ + Target::Method(MethodKind::Inherent), + Target::Method(MethodKind::Trait { body: false }), + Target::Method(MethodKind::Trait { body: true }), + Target::Method(MethodKind::TraitImpl), + ]; + const IMPL_LIKE: &[Target] = + &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }]; + const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum]; + + let mut added_fake_targets = Vec::new(); + filter_targets( + &mut allowed_targets, + FUNCTION_LIKE, + "functions", + target, + &mut added_fake_targets, + ); + filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets); + filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets); + filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets); + + // If there is now only 1 target left, show that as the only possible target + ( + added_fake_targets + .iter() + .copied() + .chain(allowed_targets.iter().map(|t| t.plural_name())) + .map(|i| i.to_string()) + .collect(), + allowed_targets.len() + added_fake_targets.len() == 1, + ) +} + +fn filter_targets( + allowed_targets: &mut Vec, + target_group: &'static [Target], + target_group_name: &'static str, + target: Target, + added_fake_targets: &mut Vec<&'static str>, +) { + if target_group.contains(&target) { + return; + } + if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 { + return; + } + allowed_targets.retain(|t| !target_group.contains(t)); + added_fake_targets.push(target_group_name); +} + +/// This is the list of all targets to which a attribute can be applied +/// This is used for: +/// - `rustc_dummy`, which can be applied to all targets +/// - Attributes that are not parted to the new target system yet can use this list as a placeholder +pub(crate) const ALL_TARGETS: &'static [Policy] = { + use Policy::Allow; + &[ + Allow(Target::ExternCrate), + Allow(Target::Use), + Allow(Target::Static), + Allow(Target::Const), + Allow(Target::Fn), + Allow(Target::Closure), + Allow(Target::Mod), + Allow(Target::ForeignMod), + Allow(Target::GlobalAsm), + Allow(Target::TyAlias), + Allow(Target::Enum), + Allow(Target::Variant), + Allow(Target::Struct), + Allow(Target::Field), + Allow(Target::Union), + Allow(Target::Trait), + Allow(Target::TraitAlias), + Allow(Target::Impl { of_trait: false }), + Allow(Target::Impl { of_trait: true }), + Allow(Target::Expression), + Allow(Target::Statement), + Allow(Target::Arm), + Allow(Target::AssocConst), + Allow(Target::Method(MethodKind::Inherent)), + Allow(Target::Method(MethodKind::Trait { body: false })), + Allow(Target::Method(MethodKind::Trait { body: true })), + Allow(Target::Method(MethodKind::TraitImpl)), + Allow(Target::AssocTy), + Allow(Target::ForeignFn), + Allow(Target::ForeignStatic), + Allow(Target::ForeignTy), + Allow(Target::MacroDef), + Allow(Target::Param), + Allow(Target::PatField), + Allow(Target::ExprField), + Allow(Target::WherePredicate), + Allow(Target::MacroCall), + Allow(Target::Crate), + Allow(Target::Delegation { mac: false }), + Allow(Target::Delegation { mac: true }), + ] +}; diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl index 33b80c4b03d6..f59e106c7ac3 100644 --- a/compiler/rustc_borrowck/messages.ftl +++ b/compiler/rustc_borrowck/messages.ftl @@ -90,7 +90,7 @@ borrowck_lifetime_constraints_error = lifetime may not live long enough borrowck_limitations_implies_static = - due to current limitations in the borrow checker, this implies a `'static` lifetime + due to a current limitation of the type system, this implies a `'static` lifetime borrowck_move_closure_suggestion = consider adding 'move' keyword before the nested closure diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 57db2e9fb574..d3f6c01ab8c3 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -1,7 +1,6 @@ use std::fmt; use rustc_data_structures::fx::FxIndexMap; -use rustc_data_structures::graph; use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; use rustc_middle::mir::{ self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges, @@ -317,9 +316,8 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { loans_out_of_scope_at_location: FxIndexMap::default(), }; for (loan_idx, loan_data) in borrow_set.iter_enumerated() { - let issuing_region = loan_data.region; let loan_issued_at = loan_data.reserve_location; - prec.precompute_loans_out_of_scope(loan_idx, issuing_region, loan_issued_at); + prec.precompute_loans_out_of_scope(loan_idx, loan_issued_at); } prec.loans_out_of_scope_at_location @@ -328,45 +326,7 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> { /// Loans are in scope while they are live: whether they are contained within any live region. /// In the location-insensitive analysis, a loan will be contained in a region if the issuing /// region can reach it in the subset graph. So this is a reachability problem. - fn precompute_loans_out_of_scope( - &mut self, - loan_idx: BorrowIndex, - issuing_region: RegionVid, - loan_issued_at: Location, - ) { - let sccs = self.regioncx.constraint_sccs(); - let universal_regions = self.regioncx.universal_regions(); - - // The loop below was useful for the location-insensitive analysis but shouldn't be - // impactful in the location-sensitive case. It seems that it does, however, as without it a - // handful of tests fail. That likely means some liveness or outlives data related to choice - // regions is missing - // FIXME: investigate the impact of loans traversing applied member constraints and why some - // tests fail otherwise. - // - // We first handle the cases where the loan doesn't go out of scope, depending on the - // issuing region's successors. - for successor in graph::depth_first_search(&self.regioncx.region_graph(), issuing_region) { - // Via applied member constraints - // - // The issuing region can flow into the choice regions, and they are either: - // - placeholders or free regions themselves, - // - or also transitively outlive a free region. - // - // That is to say, if there are applied member constraints here, the loan escapes the - // function and cannot go out of scope. We could early return here. - // - // For additional insurance via fuzzing and crater, we verify that the constraint's min - // choice indeed escapes the function. In the future, we could e.g. turn this check into - // a debug assert and early return as an optimization. - let scc = sccs.scc(successor); - for constraint in self.regioncx.applied_member_constraints(scc) { - if universal_regions.is_universal_region(constraint.min_choice) { - return; - } - } - } - + fn precompute_loans_out_of_scope(&mut self, loan_idx: BorrowIndex, loan_issued_at: Location) { let first_block = loan_issued_at.block; let first_bb_data = &self.body.basic_blocks[first_block]; diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index be8b3f0bc1e3..7e20a5133e07 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -410,18 +410,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } let typeck = self.infcx.tcx.typeck(self.mir_def_id()); let parent = self.infcx.tcx.parent_hir_node(expr.hir_id); - let (def_id, call_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent + let (def_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent && let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind { let def_id = typeck.type_dependent_def_id(parent_expr.hir_id); - (def_id, Some(parent_expr.hir_id), args, 1) + (def_id, args, 1) } else if let hir::Node::Expr(parent_expr) = parent && let hir::ExprKind::Call(call, args) = parent_expr.kind && let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind() { - (Some(*def_id), Some(call.hir_id), args, 0) + (Some(*def_id), args, 0) } else { - (None, None, &[][..], 0) + (None, &[][..], 0) }; let ty = place.ty(self.body, self.infcx.tcx).ty; @@ -459,11 +459,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // If the moved place is used generically by the callee and a reference to it // would still satisfy any bounds on its type, suggest borrowing. if let Some(¶m) = arg_param - && let Some(generic_args) = call_id.and_then(|id| typeck.node_args_opt(id)) + && let hir::Node::Expr(call_expr) = parent && let Some(ref_mutability) = self.suggest_borrow_generic_arg( err, + typeck, + call_expr, def_id, - generic_args, param, moved_place, pos + offset, @@ -627,8 +628,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { fn suggest_borrow_generic_arg( &self, err: &mut Diag<'_>, + typeck: &ty::TypeckResults<'tcx>, + call_expr: &hir::Expr<'tcx>, callee_did: DefId, - generic_args: ty::GenericArgsRef<'tcx>, param: ty::ParamTy, moved_place: PlaceRef<'tcx>, moved_arg_pos: usize, @@ -639,6 +641,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let sig = tcx.fn_sig(callee_did).instantiate_identity().skip_binder(); let clauses = tcx.predicates_of(callee_did); + let generic_args = match call_expr.kind { + // For method calls, generic arguments are attached to the call node. + hir::ExprKind::MethodCall(..) => typeck.node_args_opt(call_expr.hir_id)?, + // For normal calls, generic arguments are in the callee's type. + // This diagnostic is only run for `FnDef` callees. + hir::ExprKind::Call(callee, _) + if let &ty::FnDef(_, args) = typeck.node_type(callee.hir_id).kind() => + { + args + } + _ => return None, + }; + // First, is there at least one method on one of `param`'s trait bounds? // This keeps us from suggesting borrowing the argument to `mem::drop`, e.g. if !clauses.instantiate_identity(tcx).predicates.iter().any(|clause| { diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 56fdaf1c724a..5642cdf87fde 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -6,7 +6,9 @@ use rustc_abi::{FieldIdx, VariantIdx}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, listify}; use rustc_hir::def::{CtorKind, Namespace}; -use rustc_hir::{self as hir, CoroutineKind, LangItem}; +use rustc_hir::{ + self as hir, CoroutineKind, GenericBound, LangItem, WhereBoundPredicate, WherePredicateKind, +}; use rustc_index::{IndexSlice, IndexVec}; use rustc_infer::infer::{BoundRegionConversionTime, NllRegionVariableOrigin}; use rustc_infer::traits::SelectionError; @@ -51,7 +53,7 @@ mod conflict_errors; mod explain_borrow; mod move_errors; mod mutability_errors; -mod opaque_suggestions; +mod opaque_types; mod region_errors; pub(crate) use bound_region_errors::{ToUniverseInfo, UniverseInfo}; @@ -613,7 +615,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// Return the name of the provided `Ty` (that must be a reference) with a synthesized lifetime /// name where required. pub(super) fn get_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { - let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); + let mut p = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); // We need to add synthesized lifetimes where appropriate. We do // this by hooking into the pretty printer and telling it to label the @@ -624,19 +626,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { | ty::RePlaceholder(ty::PlaceholderRegion { bound: ty::BoundRegion { kind: br, .. }, .. - }) => printer.region_highlight_mode.highlighting_bound_region(br, counter), + }) => p.region_highlight_mode.highlighting_bound_region(br, counter), _ => {} } } - ty.print(&mut printer).unwrap(); - printer.into_buffer() + ty.print(&mut p).unwrap(); + p.into_buffer() } /// Returns the name of the provided `Ty` (that must be a reference)'s region with a /// synthesized lifetime name where required. pub(super) fn get_region_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { - let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); + let mut p = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); let region = if let ty::Ref(region, ..) = ty.kind() { match region.kind() { @@ -644,7 +646,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { | ty::RePlaceholder(ty::PlaceholderRegion { bound: ty::BoundRegion { kind: br, .. }, .. - }) => printer.region_highlight_mode.highlighting_bound_region(br, counter), + }) => p.region_highlight_mode.highlighting_bound_region(br, counter), _ => {} } region @@ -652,31 +654,72 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { bug!("ty for annotation of borrow region is not a reference"); }; - region.print(&mut printer).unwrap(); - printer.into_buffer() + region.print(&mut p).unwrap(); + p.into_buffer() } /// Add a note to region errors and borrow explanations when higher-ranked regions in predicates /// implicitly introduce an "outlives `'static`" constraint. + /// + /// This is very similar to `fn suggest_static_lifetime_for_gat_from_hrtb` which handles this + /// note for failed type tests instead of outlives errors. fn add_placeholder_from_predicate_note( &self, - err: &mut Diag<'_, G>, + diag: &mut Diag<'_, G>, path: &[OutlivesConstraint<'tcx>], ) { - let predicate_span = path.iter().find_map(|constraint| { + let tcx = self.infcx.tcx; + let Some((gat_hir_id, generics)) = path.iter().find_map(|constraint| { let outlived = constraint.sub; if let Some(origin) = self.regioncx.definitions.get(outlived) - && let NllRegionVariableOrigin::Placeholder(_) = origin.origin - && let ConstraintCategory::Predicate(span) = constraint.category + && let NllRegionVariableOrigin::Placeholder(placeholder) = origin.origin + && let Some(id) = placeholder.bound.kind.get_id() + && let Some(placeholder_id) = id.as_local() + && let gat_hir_id = tcx.local_def_id_to_hir_id(placeholder_id) + && let Some(generics_impl) = + tcx.parent_hir_node(tcx.parent_hir_id(gat_hir_id)).generics() { - Some(span) + Some((gat_hir_id, generics_impl)) } else { None } - }); + }) else { + return; + }; - if let Some(span) = predicate_span { - err.span_note(span, "due to current limitations in the borrow checker, this implies a `'static` lifetime"); + // Look for the where-bound which introduces the placeholder. + // As we're using the HIR, we need to handle both `for<'a> T: Trait<'a>` + // and `T: for<'a> Trait`<'a>. + for pred in generics.predicates { + let WherePredicateKind::BoundPredicate(WhereBoundPredicate { + bound_generic_params, + bounds, + .. + }) = pred.kind + else { + continue; + }; + if bound_generic_params + .iter() + .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) + .is_some() + { + diag.span_note(pred.span, fluent::borrowck_limitations_implies_static); + return; + } + for bound in bounds.iter() { + if let GenericBound::Trait(bound) = bound { + if bound + .bound_generic_params + .iter() + .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) + .is_some() + { + diag.span_note(bound.span, fluent::borrowck_limitations_implies_static); + return; + } + } + } } } diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 1067f1e40ef6..0b3151fd8b82 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -9,7 +9,8 @@ use rustc_middle::bug; use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex}; -use rustc_span::{BytePos, ExpnKind, MacroKind, Span}; +use rustc_span::def_id::DefId; +use rustc_span::{BytePos, DUMMY_SP, ExpnKind, MacroKind, Span}; use rustc_trait_selection::error_reporting::traits::FindExprBySpan; use rustc_trait_selection::infer::InferCtxtExt; use tracing::debug; @@ -507,12 +508,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ); let closure_span = tcx.def_span(def_id); + self.cannot_move_out_of(span, &place_description) .with_span_label(upvar_span, "captured outer variable") .with_span_label( closure_span, format!("captured by this `{closure_kind}` closure"), ) + .with_span_help( + self.get_closure_bound_clause_span(*def_id), + "`Fn` and `FnMut` closures require captured values to be able to be \ + consumed multiple times, but `FnOnce` closures may consume them only once", + ) } _ => { let source = self.borrowed_content_source(deref_base); @@ -561,6 +568,47 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { err } + fn get_closure_bound_clause_span(&self, def_id: DefId) -> Span { + let tcx = self.infcx.tcx; + let typeck_result = tcx.typeck(self.mir_def_id()); + // Check whether the closure is an argument to a call, if so, + // get the instantiated where-bounds of that call. + let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local()); + let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return DUMMY_SP }; + + let predicates = match parent.kind { + hir::ExprKind::Call(callee, _) => { + let Some(ty) = typeck_result.node_type_opt(callee.hir_id) else { return DUMMY_SP }; + let ty::FnDef(fn_def_id, args) = ty.kind() else { return DUMMY_SP }; + tcx.predicates_of(fn_def_id).instantiate(tcx, args) + } + hir::ExprKind::MethodCall(..) => { + let Some((_, method)) = typeck_result.type_dependent_def(parent.hir_id) else { + return DUMMY_SP; + }; + let args = typeck_result.node_args(parent.hir_id); + tcx.predicates_of(method).instantiate(tcx, args) + } + _ => return DUMMY_SP, + }; + + // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`. + for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) { + if let Some(clause) = pred.as_trait_clause() + && let ty::Closure(clause_closure_def_id, _) = clause.self_ty().skip_binder().kind() + && *clause_closure_def_id == def_id + && (tcx.lang_items().fn_mut_trait() == Some(clause.def_id()) + || tcx.lang_items().fn_trait() == Some(clause.def_id())) + { + // Found `` + // We point at the `Fn()` or `FnMut()` bound that coerced the closure, which + // could be changed to `FnOnce()` to avoid the move error. + return *span; + } + } + DUMMY_SP + } + fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diag<'_>, span: Span) { match error { GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 5d9416b59fce..ea264c8064ad 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -3,6 +3,7 @@ use core::ops::ControlFlow; +use either::Either; use hir::{ExprKind, Param}; use rustc_abi::FieldIdx; use rustc_errors::{Applicability, Diag}; @@ -12,15 +13,16 @@ use rustc_middle::bug; use rustc_middle::hir::place::PlaceBase; use rustc_middle::mir::visit::PlaceContext; use rustc_middle::mir::{ - self, BindingForm, Local, LocalDecl, LocalInfo, LocalKind, Location, Mutability, Place, - PlaceRef, ProjectionElem, + self, BindingForm, Body, BorrowKind, Local, LocalDecl, LocalInfo, LocalKind, Location, + Mutability, Operand, Place, PlaceRef, ProjectionElem, RawPtrKind, Rvalue, Statement, + StatementKind, TerminatorKind, }; use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt, Upcast}; use rustc_span::{BytePos, DesugaringKind, Span, Symbol, kw, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits; -use tracing::debug; +use tracing::{debug, trace}; use crate::diagnostics::BorrowedContentSource; use crate::{MirBorrowckCtxt, session_diagnostics}; @@ -31,6 +33,33 @@ pub(crate) enum AccessKind { Mutate, } +/// Finds all statements that assign directly to local (i.e., X = ...) and returns their +/// locations. +fn find_assignments(body: &Body<'_>, local: Local) -> Vec { + use rustc_middle::mir::visit::Visitor; + + struct FindLocalAssignmentVisitor { + needle: Local, + locations: Vec, + } + + impl<'tcx> Visitor<'tcx> for FindLocalAssignmentVisitor { + fn visit_local(&mut self, local: Local, place_context: PlaceContext, location: Location) { + if self.needle != local { + return; + } + + if place_context.is_place_assignment() { + self.locations.push(location); + } + } + } + + let mut visitor = FindLocalAssignmentVisitor { needle: local, locations: vec![] }; + visitor.visit_body(body); + visitor.locations +} + impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn report_mutability_error( &mut self, @@ -384,7 +413,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } - // Also suggest adding mut for upvars + // Also suggest adding mut for upvars. PlaceRef { local, projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], @@ -438,9 +467,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } - // complete hack to approximate old AST-borrowck - // diagnostic: if the span starts with a mutable borrow of - // a local variable, then just suggest the user remove it. + // Complete hack to approximate old AST-borrowck diagnostic: if the span starts + // with a mutable borrow of a local variable, then just suggest the user remove it. PlaceRef { local: _, projection: [] } if self .infcx @@ -677,12 +705,13 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// - is the trait from the local crate? If not, we can't suggest changing signatures /// - `Span` of the argument in the trait definition fn is_error_in_trait(&self, local: Local) -> (bool, bool, Option) { + let tcx = self.infcx.tcx; if self.body.local_kind(local) != LocalKind::Arg { return (false, false, None); } let my_def = self.body.source.def_id(); let Some(td) = - self.infcx.tcx.impl_of_assoc(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x)) + tcx.trait_impl_of_assoc(my_def).and_then(|id| self.infcx.tcx.trait_id_of_impl(id)) else { return (false, false, None); }; @@ -768,7 +797,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ); } - // point to span of upvar making closure call require mutable borrow + // Point to span of upvar making closure call that requires a mutable borrow fn show_mutating_upvar( &self, tcx: TyCtxt<'_>, @@ -824,7 +853,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } else { bug!("not an upvar") }; - // sometimes we deliberately don't store the name of a place when coming from a macro in + // Sometimes we deliberately don't store the name of a place when coming from a macro in // another crate. We generally want to limit those diagnostics a little, to hide // implementation details (such as those from pin!() or format!()). In that case show a // slightly different error message, or none at all if something else happened. In other @@ -935,8 +964,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let def_id = tcx.hir_enclosing_body_owner(fn_call_id); let mut look_at_return = true; - // If the HIR node is a function or method call gets the def ID - // of the called function or method and the span and args of the call expr + // If the HIR node is a function or method call, get the DefId + // of the callee function or method, the span, and args of the call expr let get_call_details = || { let hir::Node::Expr(hir::Expr { hir_id, kind, .. }) = node else { return None; @@ -1050,7 +1079,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let mut cur_expr = expr; while let ExprKind::MethodCall(path_segment, recv, _, _) = cur_expr.kind { if path_segment.ident.name == sym::iter { - // check `_ty` has `iter_mut` method + // Check that the type has an `iter_mut` method. let res = self .infcx .tcx @@ -1080,38 +1109,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } - /// Finds all statements that assign directly to local (i.e., X = ...) and returns their - /// locations. - fn find_assignments(&self, local: Local) -> Vec { - use rustc_middle::mir::visit::Visitor; - - struct FindLocalAssignmentVisitor { - needle: Local, - locations: Vec, - } - - impl<'tcx> Visitor<'tcx> for FindLocalAssignmentVisitor { - fn visit_local( - &mut self, - local: Local, - place_context: PlaceContext, - location: Location, - ) { - if self.needle != local { - return; - } - - if place_context.is_place_assignment() { - self.locations.push(location); - } - } - } - - let mut visitor = FindLocalAssignmentVisitor { needle: local, locations: vec![] }; - visitor.visit_body(self.body); - visitor.locations - } - fn suggest_make_local_mut(&self, err: &mut Diag<'_>, local: Local, name: Symbol) { let local_decl = &self.body.local_decls[local]; @@ -1121,7 +1118,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let (is_trait_sig, is_local, local_trait) = self.is_error_in_trait(local); if is_trait_sig && !is_local { - // Do not suggest to change the signature when the trait comes from another crate. + // Do not suggest changing the signature when the trait comes from another crate. err.span_label( local_decl.source_info.span, format!("this is an immutable {pointer_desc}"), @@ -1130,11 +1127,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } let decl_span = local_decl.source_info.span; - let amp_mut_sugg = match *local_decl.local_info() { + let (amp_mut_sugg, local_var_ty_info) = match *local_decl.local_info() { LocalInfo::User(mir::BindingForm::ImplicitSelf(_)) => { let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span); let additional = local_trait.map(|span| suggest_ampmut_self(self.infcx.tcx, span)); - Some(AmpMutSugg { has_sugg: true, span, suggestion, additional }) + (AmpMutSugg::Type { span, suggestion, additional }, None) } LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { @@ -1142,79 +1139,54 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { opt_ty_info, .. })) => { - // check if the RHS is from desugaring + // Check if the RHS is from desugaring. + let first_assignment = find_assignments(&self.body, local).first().copied(); + let first_assignment_stmt = first_assignment + .and_then(|loc| self.body[loc.block].statements.get(loc.statement_index)); + trace!(?first_assignment_stmt); let opt_assignment_rhs_span = - self.find_assignments(local).first().map(|&location| { - if let Some(mir::Statement { - source_info: _, - kind: - mir::StatementKind::Assign(box ( - _, - mir::Rvalue::Use(mir::Operand::Copy(place)), - )), - .. - }) = self.body[location.block].statements.get(location.statement_index) - { - self.body.local_decls[place.local].source_info.span - } else { - self.body.source_info(location).span - } - }); - match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) { - // on for loops, RHS points to the iterator part - Some(DesugaringKind::ForLoop) => { - let span = opt_assignment_rhs_span.unwrap(); - self.suggest_similar_mut_method_for_for_loop(err, span); + first_assignment.map(|loc| self.body.source_info(loc).span); + let mut source_span = opt_assignment_rhs_span; + if let Some(mir::Statement { + source_info: _, + kind: + mir::StatementKind::Assign(box (_, mir::Rvalue::Use(mir::Operand::Copy(place)))), + .. + }) = first_assignment_stmt + { + let local_span = self.body.local_decls[place.local].source_info.span; + // `&self` in async functions have a `desugaring_kind`, but the local we assign + // it with does not, so use the local_span for our checks later. + source_span = Some(local_span); + if let Some(DesugaringKind::ForLoop) = local_span.desugaring_kind() { + // On for loops, RHS points to the iterator part. + self.suggest_similar_mut_method_for_for_loop(err, local_span); err.span_label( - span, + local_span, format!("this iterator yields `{pointer_sigil}` {pointer_desc}s",), ); - None - } - // don't create labels for compiler-generated spans - Some(_) => None, - // don't create labels for the span not from user's code - None if opt_assignment_rhs_span - .is_some_and(|span| self.infcx.tcx.sess.source_map().is_imported(span)) => - { - None - } - None => { - if name != kw::SelfLower { - suggest_ampmut( - self.infcx.tcx, - local_decl.ty, - decl_span, - opt_assignment_rhs_span, - opt_ty_info, - ) - } else { - match local_decl.local_info() { - LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { - opt_ty_info: None, - .. - })) => { - let (span, sugg) = - suggest_ampmut_self(self.infcx.tcx, decl_span); - Some(AmpMutSugg { - has_sugg: true, - span, - suggestion: sugg, - additional: None, - }) - } - // explicit self (eg `self: &'a Self`) - _ => suggest_ampmut( - self.infcx.tcx, - local_decl.ty, - decl_span, - opt_assignment_rhs_span, - opt_ty_info, - ), - } - } + return; } } + + // Don't create labels for compiler-generated spans or spans not from users' code. + if source_span.is_some_and(|s| { + s.desugaring_kind().is_some() || self.infcx.tcx.sess.source_map().is_imported(s) + }) { + return; + } + + // This could be because we're in an `async fn`. + if name == kw::SelfLower && opt_ty_info.is_none() { + let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span); + (AmpMutSugg::Type { span, suggestion, additional: None }, None) + } else if let Some(sugg) = + suggest_ampmut(self.infcx, self.body(), first_assignment_stmt) + { + (sugg, opt_ty_info) + } else { + return; + } } LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm { @@ -1222,181 +1194,238 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .. })) => { let pattern_span: Span = local_decl.source_info.span; - suggest_ref_mut(self.infcx.tcx, pattern_span).map(|span| AmpMutSugg { - has_sugg: true, - span, - suggestion: "mut ".to_owned(), - additional: None, - }) + let Some(span) = suggest_ref_mut(self.infcx.tcx, pattern_span) else { + return; + }; + (AmpMutSugg::Type { span, suggestion: "mut ".to_owned(), additional: None }, None) } _ => unreachable!(), }; - match amp_mut_sugg { - Some(AmpMutSugg { - has_sugg: true, - span: err_help_span, - suggestion: suggested_code, - additional, - }) => { - let mut sugg = vec![(err_help_span, suggested_code)]; - if let Some(s) = additional { - sugg.push(s); - } + let mut suggest = |suggs: Vec<_>, applicability, extra| { + if suggs.iter().any(|(span, _)| self.infcx.tcx.sess.source_map().is_imported(*span)) { + return; + } - if sugg.iter().all(|(span, _)| !self.infcx.tcx.sess.source_map().is_imported(*span)) - { - err.multipart_suggestion_verbose( - format!( - "consider changing this to be a mutable {pointer_desc}{}", - if is_trait_sig { - " in the `impl` method and the `trait` definition" - } else { - "" - } - ), - sugg, - Applicability::MachineApplicable, - ); + err.multipart_suggestion_verbose( + format!( + "consider changing this to be a mutable {pointer_desc}{}{extra}", + if is_trait_sig { + " in the `impl` method and the `trait` definition" + } else { + "" + } + ), + suggs, + applicability, + ); + }; + + let (mut sugg, add_type_annotation_if_not_exists) = match amp_mut_sugg { + AmpMutSugg::Type { span, suggestion, additional } => { + let mut sugg = vec![(span, suggestion)]; + sugg.extend(additional); + suggest(sugg, Applicability::MachineApplicable, ""); + return; + } + AmpMutSugg::MapGetMut { span, suggestion } => { + if self.infcx.tcx.sess.source_map().is_imported(span) { + return; } + err.multipart_suggestion_verbose( + "consider using `get_mut`", + vec![(span, suggestion)], + Applicability::MaybeIncorrect, + ); + return; } - Some(AmpMutSugg { - has_sugg: false, span: err_label_span, suggestion: message, .. - }) => { - let def_id = self.body.source.def_id(); - let hir_id = if let Some(local_def_id) = def_id.as_local() - && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id) - { - BindingFinder { span: err_label_span }.visit_body(&body).break_value() - } else { - None - }; + AmpMutSugg::Expr { span, suggestion } => { + // `Expr` suggestions should change type annotations if they already exist (probably immut), + // but do not add new type annotations. + (vec![(span, suggestion)], false) + } + AmpMutSugg::ChangeBinding => (vec![], true), + }; - if let Some(hir_id) = hir_id - && let hir::Node::LetStmt(local) = self.infcx.tcx.hir_node(hir_id) - { - let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap()); - if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait() - && let Some(expr) = local.init - && let ty = tables.node_type_opt(expr.hir_id) - && let Some(ty) = ty - && let ty::Ref(..) = ty.kind() + // Find a binding's type to make mutable. + let (binding_exists, span) = match local_var_ty_info { + // If this is a variable binding with an explicit type, + // then we will suggest changing it to be mutable. + // This is `Applicability::MachineApplicable`. + Some(ty_span) => (true, ty_span), + + // Otherwise, we'll suggest *adding* an annotated type, we'll suggest + // the RHS's type for that. + // This is `Applicability::HasPlaceholders`. + None => (false, decl_span), + }; + + if !binding_exists && !add_type_annotation_if_not_exists { + suggest(sugg, Applicability::MachineApplicable, ""); + return; + } + + // If the binding already exists and is a reference with an explicit + // lifetime, then we can suggest adding ` mut`. This is special-cased from + // the path without an explicit lifetime. + let (sugg_span, sugg_str, suggest_now) = if let Ok(src) = self.infcx.tcx.sess.source_map().span_to_snippet(span) + && src.starts_with("&'") + // Note that `&' a T` is invalid so this is correct. + && let Some(ws_pos) = src.find(char::is_whitespace) + { + let span = span.with_lo(span.lo() + BytePos(ws_pos as u32)).shrink_to_lo(); + (span, " mut".to_owned(), true) + // If there is already a binding, we modify it to be `mut`. + } else if binding_exists { + // Shrink the span to just after the `&` in `&variable`. + let span = span.with_lo(span.lo() + BytePos(1)).shrink_to_lo(); + (span, "mut ".to_owned(), true) + } else { + // Otherwise, suggest that the user annotates the binding; We provide the + // type of the local. + let ty = local_decl.ty.builtin_deref(true).unwrap(); + + (span, format!("{}mut {}", if local_decl.ty.is_ref() { "&" } else { "*" }, ty), false) + }; + + if suggest_now { + // Suggest changing `&x` to `&mut x` and changing `&T` to `&mut T` at the same time. + let has_change = !sugg.is_empty(); + sugg.push((sugg_span, sugg_str)); + suggest( + sugg, + Applicability::MachineApplicable, + // FIXME(fee1-dead) this somehow doesn't fire + if has_change { " and changing the binding's type" } else { "" }, + ); + return; + } else if !sugg.is_empty() { + suggest(sugg, Applicability::MachineApplicable, ""); + return; + } + + let def_id = self.body.source.def_id(); + let hir_id = if let Some(local_def_id) = def_id.as_local() + && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id) + { + BindingFinder { span: sugg_span }.visit_body(&body).break_value() + } else { + None + }; + let node = hir_id.map(|hir_id| self.infcx.tcx.hir_node(hir_id)); + + let Some(hir::Node::LetStmt(local)) = node else { + err.span_label( + sugg_span, + format!("consider changing this binding's type to be: `{sugg_str}`"), + ); + return; + }; + + let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap()); + if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait() + && let Some(expr) = local.init + && let ty = tables.node_type_opt(expr.hir_id) + && let Some(ty) = ty + && let ty::Ref(..) = ty.kind() + { + match self + .infcx + .type_implements_trait_shallow(clone_trait, ty.peel_refs(), self.infcx.param_env) + .as_deref() + { + Some([]) => { + // FIXME: This error message isn't useful, since we're just + // vaguely suggesting to clone a value that already + // implements `Clone`. + // + // A correct suggestion here would take into account the fact + // that inference may be affected by missing types on bindings, + // etc., to improve "tests/ui/borrowck/issue-91206.stderr", for + // example. + } + None => { + if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind + && segment.ident.name == sym::clone { - match self - .infcx - .type_implements_trait_shallow( - clone_trait, - ty.peel_refs(), - self.infcx.param_env, - ) - .as_deref() - { - Some([]) => { - // FIXME: This error message isn't useful, since we're just - // vaguely suggesting to clone a value that already - // implements `Clone`. - // - // A correct suggestion here would take into account the fact - // that inference may be affected by missing types on bindings, - // etc., to improve "tests/ui/borrowck/issue-91206.stderr", for - // example. - } - None => { - if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = - expr.kind - && segment.ident.name == sym::clone - { - err.span_help( - span, - format!( - "`{}` doesn't implement `Clone`, so this call clones \ + err.span_help( + span, + format!( + "`{}` doesn't implement `Clone`, so this call clones \ the reference `{ty}`", - ty.peel_refs(), - ), - ); - } - // The type doesn't implement Clone. - let trait_ref = ty::Binder::dummy(ty::TraitRef::new( - self.infcx.tcx, - clone_trait, - [ty.peel_refs()], - )); - let obligation = traits::Obligation::new( - self.infcx.tcx, - traits::ObligationCause::dummy(), - self.infcx.param_env, - trait_ref, - ); - self.infcx.err_ctxt().suggest_derive( - &obligation, - err, - trait_ref.upcast(self.infcx.tcx), - ); - } - Some(errors) => { - if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = - expr.kind - && segment.ident.name == sym::clone - { - err.span_help( - span, - format!( - "`{}` doesn't implement `Clone` because its \ + ty.peel_refs(), + ), + ); + } + // The type doesn't implement Clone. + let trait_ref = ty::Binder::dummy(ty::TraitRef::new( + self.infcx.tcx, + clone_trait, + [ty.peel_refs()], + )); + let obligation = traits::Obligation::new( + self.infcx.tcx, + traits::ObligationCause::dummy(), + self.infcx.param_env, + trait_ref, + ); + self.infcx.err_ctxt().suggest_derive( + &obligation, + err, + trait_ref.upcast(self.infcx.tcx), + ); + } + Some(errors) => { + if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind + && segment.ident.name == sym::clone + { + err.span_help( + span, + format!( + "`{}` doesn't implement `Clone` because its \ implementations trait bounds could not be met, so \ this call clones the reference `{ty}`", - ty.peel_refs(), - ), - ); - err.note(format!( - "the following trait bounds weren't met: {}", - errors - .iter() - .map(|e| e.obligation.predicate.to_string()) - .collect::>() - .join("\n"), - )); - } - // The type doesn't implement Clone because of unmet obligations. - for error in errors { - if let traits::FulfillmentErrorCode::Select( - traits::SelectionError::Unimplemented, - ) = error.code - && let ty::PredicateKind::Clause(ty::ClauseKind::Trait( - pred, - )) = error.obligation.predicate.kind().skip_binder() - { - self.infcx.err_ctxt().suggest_derive( - &error.obligation, - err, - error.obligation.predicate.kind().rebind(pred), - ); - } - } - } - } + ty.peel_refs(), + ), + ); + err.note(format!( + "the following trait bounds weren't met: {}", + errors + .iter() + .map(|e| e.obligation.predicate.to_string()) + .collect::>() + .join("\n"), + )); } - let (changing, span, sugg) = match local.ty { - Some(ty) => ("changing", ty.span, message), - None => { - ("specifying", local.pat.span.shrink_to_hi(), format!(": {message}")) + // The type doesn't implement Clone because of unmet obligations. + for error in errors { + if let traits::FulfillmentErrorCode::Select( + traits::SelectionError::Unimplemented, + ) = error.code + && let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = + error.obligation.predicate.kind().skip_binder() + { + self.infcx.err_ctxt().suggest_derive( + &error.obligation, + err, + error.obligation.predicate.kind().rebind(pred), + ); } - }; - err.span_suggestion_verbose( - span, - format!("consider {changing} this binding's type"), - sugg, - Applicability::HasPlaceholders, - ); - } else { - err.span_label( - err_label_span, - format!("consider changing this binding's type to be: `{message}`"), - ); + } } } - None => {} } + let (changing, span, sugg) = match local.ty { + Some(ty) => ("changing", ty.span, sugg_str), + None => ("specifying", local.pat.span.shrink_to_hi(), format!(": {sugg_str}")), + }; + err.span_suggestion_verbose( + span, + format!("consider {changing} this binding's type"), + sugg, + Applicability::HasPlaceholders, + ); } } @@ -1463,11 +1492,25 @@ fn suggest_ampmut_self(tcx: TyCtxt<'_>, span: Span) -> (Span, String) { } } -struct AmpMutSugg { - has_sugg: bool, - span: Span, - suggestion: String, - additional: Option<(Span, String)>, +enum AmpMutSugg { + /// Type suggestion. Changes `&self` to `&mut self`, `x: &T` to `x: &mut T`, + /// `ref x` to `ref mut x`, etc. + Type { + span: Span, + suggestion: String, + additional: Option<(Span, String)>, + }, + /// Suggestion for expressions, `&x` to `&mut x`, `&x[i]` to `&mut x[i]`, etc. + Expr { + span: Span, + suggestion: String, + }, + /// Suggests `.get_mut` in the case of `&map[&key]` for Hash/BTreeMap. + MapGetMut { + span: Span, + suggestion: String, + }, + ChangeBinding, } // When we want to suggest a user change a local variable to be a `&mut`, there @@ -1486,110 +1529,111 @@ struct AmpMutSugg { // This implementation attempts to emulate AST-borrowck prioritization // by trying (3.), then (2.) and finally falling back on (1.). fn suggest_ampmut<'tcx>( - tcx: TyCtxt<'tcx>, - decl_ty: Ty<'tcx>, - decl_span: Span, - opt_assignment_rhs_span: Option, - opt_ty_info: Option, + infcx: &crate::BorrowckInferCtxt<'tcx>, + body: &Body<'tcx>, + opt_assignment_rhs_stmt: Option<&Statement<'tcx>>, ) -> Option { - // if there is a RHS and it starts with a `&` from it, then check if it is + let tcx = infcx.tcx; + // If there is a RHS and it starts with a `&` from it, then check if it is // mutable, and if not, put suggest putting `mut ` to make it mutable. - // we don't have to worry about lifetime annotations here because they are + // We don't have to worry about lifetime annotations here because they are // not valid when taking a reference. For example, the following is not valid Rust: // // let x: &i32 = &'a 5; // ^^ lifetime annotation not allowed // - if let Some(rhs_span) = opt_assignment_rhs_span - && let Ok(rhs_str) = tcx.sess.source_map().span_to_snippet(rhs_span) - && let Some(rhs_str_no_amp) = rhs_str.strip_prefix('&') + if let Some(rhs_stmt) = opt_assignment_rhs_stmt + && let StatementKind::Assign(box (lhs, rvalue)) = &rhs_stmt.kind + && let mut rhs_span = rhs_stmt.source_info.span + && let Ok(mut rhs_str) = tcx.sess.source_map().span_to_snippet(rhs_span) { - // Suggest changing `&raw const` to `&raw mut` if applicable. - if rhs_str_no_amp.trim_start().strip_prefix("raw const").is_some() { - let const_idx = rhs_str.find("const").unwrap() as u32; - let const_span = rhs_span - .with_lo(rhs_span.lo() + BytePos(const_idx)) - .with_hi(rhs_span.lo() + BytePos(const_idx + "const".len() as u32)); - - return Some(AmpMutSugg { - has_sugg: true, - span: const_span, - suggestion: "mut".to_owned(), - additional: None, - }); + let mut rvalue = rvalue; + + // Take some special care when handling `let _x = &*_y`: + // We want to know if this is part of an overloaded index, so `let x = &a[0]`, + // or whether this is a usertype ascription (`let _x: &T = y`). + if let Rvalue::Ref(_, BorrowKind::Shared, place) = rvalue + && place.projection.len() == 1 + && place.projection[0] == ProjectionElem::Deref + && let Some(assign) = find_assignments(&body, place.local).first() + { + // If this is a usertype ascription (`let _x: &T = _y`) then pierce through it as either we want + // to suggest `&mut` on the expression (handled here) or we return `None` and let the caller + // suggest `&mut` on the type if the expression seems fine (e.g. `let _x: &T = &mut _y`). + if let Some(user_ty_projs) = body.local_decls[lhs.local].user_ty.as_ref() + && let [user_ty_proj] = user_ty_projs.contents.as_slice() + && user_ty_proj.projs.is_empty() + && let Either::Left(rhs_stmt_new) = body.stmt_at(*assign) + && let StatementKind::Assign(box (_, rvalue_new)) = &rhs_stmt_new.kind + && let rhs_span_new = rhs_stmt_new.source_info.span + && let Ok(rhs_str_new) = tcx.sess.source_map().span_to_snippet(rhs_span) + { + (rvalue, rhs_span, rhs_str) = (rvalue_new, rhs_span_new, rhs_str_new); + } + + if let Either::Right(call) = body.stmt_at(*assign) + && let TerminatorKind::Call { + func: Operand::Constant(box const_operand), args, .. + } = &call.kind + && let ty::FnDef(method_def_id, method_args) = *const_operand.ty().kind() + && let Some(trait_) = tcx.trait_of_assoc(method_def_id) + && tcx.is_lang_item(trait_, hir::LangItem::Index) + { + let trait_ref = ty::TraitRef::from_assoc( + tcx, + tcx.require_lang_item(hir::LangItem::IndexMut, rhs_span), + method_args, + ); + // The type only implements `Index` but not `IndexMut`, we must not suggest `&mut`. + if !infcx + .type_implements_trait(trait_ref.def_id, trait_ref.args, infcx.param_env) + .must_apply_considering_regions() + { + // Suggest `get_mut` if type is a `BTreeMap` or `HashMap`. + if let ty::Adt(def, _) = trait_ref.self_ty().kind() + && [sym::BTreeMap, sym::HashMap] + .into_iter() + .any(|s| tcx.is_diagnostic_item(s, def.did())) + && let [map, key] = &**args + && let Ok(map) = tcx.sess.source_map().span_to_snippet(map.span) + && let Ok(key) = tcx.sess.source_map().span_to_snippet(key.span) + { + let span = rhs_span; + let suggestion = format!("{map}.get_mut({key}).unwrap()"); + return Some(AmpMutSugg::MapGetMut { span, suggestion }); + } + return None; + } + } } - // Figure out if rhs already is `&mut`. - let is_mut = if let Some(rest) = rhs_str_no_amp.trim_start().strip_prefix("mut") { - match rest.chars().next() { - // e.g. `&mut x` - Some(c) if c.is_whitespace() => true, - // e.g. `&mut(x)` - Some('(') => true, - // e.g. `&mut{x}` - Some('{') => true, - // e.g. `&mutablevar` - _ => false, + let sugg = match rvalue { + Rvalue::Ref(_, BorrowKind::Shared, _) if let Some(ref_idx) = rhs_str.find('&') => { + // Shrink the span to just after the `&` in `&variable`. + Some(( + rhs_span.with_lo(rhs_span.lo() + BytePos(ref_idx as u32 + 1)).shrink_to_lo(), + "mut ".to_owned(), + )) } - } else { - false + Rvalue::RawPtr(RawPtrKind::Const, _) if let Some(const_idx) = rhs_str.find("const") => { + // Suggest changing `&raw const` to `&raw mut` if applicable. + let const_idx = const_idx as u32; + Some(( + rhs_span + .with_lo(rhs_span.lo() + BytePos(const_idx)) + .with_hi(rhs_span.lo() + BytePos(const_idx + "const".len() as u32)), + "mut".to_owned(), + )) + } + _ => None, }; - // if the reference is already mutable then there is nothing we can do - // here. - if !is_mut { - // shrink the span to just after the `&` in `&variable` - let span = rhs_span.with_lo(rhs_span.lo() + BytePos(1)).shrink_to_lo(); - - // FIXME(Ezrashaw): returning is bad because we still might want to - // update the annotated type, see #106857. - return Some(AmpMutSugg { - has_sugg: true, - span, - suggestion: "mut ".to_owned(), - additional: None, - }); + + if let Some((span, suggestion)) = sugg { + return Some(AmpMutSugg::Expr { span, suggestion }); } } - let (binding_exists, span) = match opt_ty_info { - // if this is a variable binding with an explicit type, - // then we will suggest changing it to be mutable. - // this is `Applicability::MachineApplicable`. - Some(ty_span) => (true, ty_span), - - // otherwise, we'll suggest *adding* an annotated type, we'll suggest - // the RHS's type for that. - // this is `Applicability::HasPlaceholders`. - None => (false, decl_span), - }; - - // if the binding already exists and is a reference with an explicit - // lifetime, then we can suggest adding ` mut`. this is special-cased from - // the path without an explicit lifetime. - if let Ok(src) = tcx.sess.source_map().span_to_snippet(span) - && src.starts_with("&'") - // note that `& 'a T` is invalid so this is correct. - && let Some(ws_pos) = src.find(char::is_whitespace) - { - let span = span.with_lo(span.lo() + BytePos(ws_pos as u32)).shrink_to_lo(); - Some(AmpMutSugg { has_sugg: true, span, suggestion: " mut".to_owned(), additional: None }) - // if there is already a binding, we modify it to be `mut` - } else if binding_exists { - // shrink the span to just after the `&` in `&variable` - let span = span.with_lo(span.lo() + BytePos(1)).shrink_to_lo(); - Some(AmpMutSugg { has_sugg: true, span, suggestion: "mut ".to_owned(), additional: None }) - } else { - // otherwise, suggest that the user annotates the binding; we provide the - // type of the local. - let ty = decl_ty.builtin_deref(true).unwrap(); - - Some(AmpMutSugg { - has_sugg: false, - span, - suggestion: format!("{}mut {}", if decl_ty.is_ref() { "&" } else { "*" }, ty), - additional: None, - }) - } + Some(AmpMutSugg::ChangeBinding) } /// If the type is a `Coroutine`, `Closure`, or `CoroutineClosure` diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs deleted file mode 100644 index 7192a889adcb..000000000000 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs +++ /dev/null @@ -1,243 +0,0 @@ -#![allow(rustc::diagnostic_outside_of_impl)] -#![allow(rustc::untranslatable_diagnostic)] - -use std::ops::ControlFlow; - -use either::Either; -use itertools::Itertools as _; -use rustc_data_structures::fx::FxIndexSet; -use rustc_errors::{Diag, Subdiagnostic}; -use rustc_hir as hir; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::{self, ConstraintCategory, Location}; -use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, -}; -use rustc_trait_selection::errors::impl_trait_overcapture_suggestion; - -use crate::MirBorrowckCtxt; -use crate::borrow_set::BorrowData; -use crate::consumers::RegionInferenceContext; -use crate::type_check::Locations; - -impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { - /// Try to note when an opaque is involved in a borrowck error and that - /// opaque captures lifetimes due to edition 2024. - // FIXME: This code is otherwise somewhat general, and could easily be adapted - // to explain why other things overcapture... like async fn and RPITITs. - pub(crate) fn note_due_to_edition_2024_opaque_capture_rules( - &self, - borrow: &BorrowData<'tcx>, - diag: &mut Diag<'_>, - ) { - // We look at all the locals. Why locals? Because it's the best thing - // I could think of that's correlated with the *instantiated* higher-ranked - // binder for calls, since we don't really store those anywhere else. - for ty in self.body.local_decls.iter().map(|local| local.ty) { - if !ty.has_opaque_types() { - continue; - } - - let tcx = self.infcx.tcx; - let ControlFlow::Break((opaque_def_id, offending_region_idx, location)) = ty - .visit_with(&mut FindOpaqueRegion { - regioncx: &self.regioncx, - tcx, - borrow_region: borrow.region, - }) - else { - continue; - }; - - // If an opaque explicitly captures a lifetime, then no need to point it out. - // FIXME: We should be using a better heuristic for `use<>`. - if tcx.rendered_precise_capturing_args(opaque_def_id).is_some() { - continue; - } - - // If one of the opaque's bounds mentions the region, then no need to - // point it out, since it would've been captured on edition 2021 as well. - // - // Also, while we're at it, collect all the lifetimes that the opaque - // *does* mention. We'll use that for the `+ use<'a>` suggestion below. - let mut visitor = CheckExplicitRegionMentionAndCollectGenerics { - tcx, - generics: tcx.generics_of(opaque_def_id), - offending_region_idx, - seen_opaques: [opaque_def_id].into_iter().collect(), - seen_lifetimes: Default::default(), - }; - if tcx - .explicit_item_bounds(opaque_def_id) - .skip_binder() - .visit_with(&mut visitor) - .is_break() - { - continue; - } - - // If we successfully located a terminator, then point it out - // and provide a suggestion if it's local. - match self.body.stmt_at(location) { - Either::Right(mir::Terminator { source_info, .. }) => { - diag.span_note( - source_info.span, - "this call may capture more lifetimes than intended, \ - because Rust 2024 has adjusted the `impl Trait` lifetime capture rules", - ); - let mut captured_args = visitor.seen_lifetimes; - // Add in all of the type and const params, too. - // Ordering here is kinda strange b/c we're walking backwards, - // but we're trying to provide *a* suggestion, not a nice one. - let mut next_generics = Some(visitor.generics); - let mut any_synthetic = false; - while let Some(generics) = next_generics { - for param in &generics.own_params { - if param.kind.is_ty_or_const() { - captured_args.insert(param.def_id); - } - if param.kind.is_synthetic() { - any_synthetic = true; - } - } - next_generics = generics.parent.map(|def_id| tcx.generics_of(def_id)); - } - - if let Some(opaque_def_id) = opaque_def_id.as_local() - && let hir::OpaqueTyOrigin::FnReturn { parent, .. } = - tcx.hir_expect_opaque_ty(opaque_def_id).origin - { - if let Some(sugg) = impl_trait_overcapture_suggestion( - tcx, - opaque_def_id, - parent, - captured_args, - ) { - sugg.add_to_diag(diag); - } - } else { - diag.span_help( - tcx.def_span(opaque_def_id), - format!( - "if you can modify this crate, add a precise \ - capturing bound to avoid overcapturing: `+ use<{}>`", - if any_synthetic { - "/* Args */".to_string() - } else { - captured_args - .into_iter() - .map(|def_id| tcx.item_name(def_id)) - .join(", ") - } - ), - ); - } - return; - } - Either::Left(_) => {} - } - } - } -} - -/// This visitor contains the bulk of the logic for this lint. -struct FindOpaqueRegion<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - regioncx: &'a RegionInferenceContext<'tcx>, - borrow_region: ty::RegionVid, -} - -impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { - type Result = ControlFlow<(DefId, usize, Location), ()>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - // If we find an opaque in a local ty, then for each of its captured regions, - // try to find a path between that captured regions and our borrow region... - if let ty::Alias(ty::Opaque, opaque) = *ty.kind() - && let hir::OpaqueTyOrigin::FnReturn { parent, in_trait_or_impl: None } = - self.tcx.opaque_ty_origin(opaque.def_id) - { - let variances = self.tcx.variances_of(opaque.def_id); - for (idx, (arg, variance)) in std::iter::zip(opaque.args, variances).enumerate() { - // Skip uncaptured args. - if *variance == ty::Bivariant { - continue; - } - // We only care about regions. - let Some(opaque_region) = arg.as_region() else { - continue; - }; - // Don't try to convert a late-bound region, which shouldn't exist anyways (yet). - if opaque_region.is_bound() { - continue; - } - let opaque_region_vid = self.regioncx.to_region_vid(opaque_region); - - // Find a path between the borrow region and our opaque capture. - if let Some((path, _)) = - self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| { - r == opaque_region_vid - }) - { - for constraint in path { - // If we find a call in this path, then check if it defines the opaque. - if let ConstraintCategory::CallArgument(Some(call_ty)) = constraint.category - && let ty::FnDef(call_def_id, _) = *call_ty.kind() - // This function defines the opaque :D - && call_def_id == parent - && let Locations::Single(location) = constraint.locations - { - return ControlFlow::Break((opaque.def_id, idx, location)); - } - } - } - } - } - - ty.super_visit_with(self) - } -} - -struct CheckExplicitRegionMentionAndCollectGenerics<'tcx> { - tcx: TyCtxt<'tcx>, - generics: &'tcx ty::Generics, - offending_region_idx: usize, - seen_opaques: FxIndexSet, - seen_lifetimes: FxIndexSet, -} - -impl<'tcx> TypeVisitor> for CheckExplicitRegionMentionAndCollectGenerics<'tcx> { - type Result = ControlFlow<(), ()>; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - match *ty.kind() { - ty::Alias(ty::Opaque, opaque) => { - if self.seen_opaques.insert(opaque.def_id) { - for (bound, _) in self - .tcx - .explicit_item_bounds(opaque.def_id) - .iter_instantiated_copied(self.tcx, opaque.args) - { - bound.visit_with(self)?; - } - } - ControlFlow::Continue(()) - } - _ => ty.super_visit_with(self), - } - } - - fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result { - match r.kind() { - ty::ReEarlyParam(param) => { - if param.index as usize == self.offending_region_idx { - ControlFlow::Break(()) - } else { - self.seen_lifetimes.insert(self.generics.region_param(param, self.tcx).def_id); - ControlFlow::Continue(()) - } - } - _ => ControlFlow::Continue(()), - } - } -} diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs new file mode 100644 index 000000000000..f77f759035be --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -0,0 +1,312 @@ +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] + +use std::ops::ControlFlow; + +use either::Either; +use itertools::Itertools as _; +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Diag, Subdiagnostic}; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::{self, ConstraintCategory, Location}; +use rustc_middle::ty::{ + self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; +use rustc_span::Span; +use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic; +use rustc_trait_selection::errors::impl_trait_overcapture_suggestion; + +use crate::MirBorrowckCtxt; +use crate::borrow_set::BorrowData; +use crate::consumers::RegionInferenceContext; +use crate::region_infer::opaque_types::DeferredOpaqueTypeError; +use crate::type_check::Locations; + +impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { + pub(crate) fn report_opaque_type_errors(&mut self, errors: Vec>) { + if errors.is_empty() { + return; + } + + let infcx = self.infcx; + let mut guar = None; + let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = + None; + for error in errors { + guar = Some(match error { + DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx), + DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => { + infcx.dcx().emit_err(err) + } + DeferredOpaqueTypeError::UnexpectedHiddenRegion { + opaque_type_key, + hidden_type, + member_region, + } => { + let named_ty = + self.regioncx.name_regions_for_member_constraint(infcx.tcx, hidden_type.ty); + let named_key = self + .regioncx + .name_regions_for_member_constraint(infcx.tcx, opaque_type_key); + let named_region = + self.regioncx.name_regions_for_member_constraint(infcx.tcx, member_region); + let diag = unexpected_hidden_region_diagnostic( + infcx, + self.mir_def_id(), + hidden_type.span, + named_ty, + named_region, + named_key, + ); + if last_unexpected_hidden_region + != Some((hidden_type.span, named_ty, named_key)) + { + last_unexpected_hidden_region = + Some((hidden_type.span, named_ty, named_key)); + diag.emit() + } else { + diag.delay_as_bug() + } + } + DeferredOpaqueTypeError::NonDefiningUseInDefiningScope { + span, + opaque_type_key, + } => infcx.dcx().span_err( + span, + format!( + "non-defining use of `{}` in the defining scope", + Ty::new_opaque( + infcx.tcx, + opaque_type_key.def_id.to_def_id(), + opaque_type_key.args + ) + ), + ), + }); + } + let guar = guar.unwrap(); + self.root_cx.set_tainted_by_errors(guar); + self.infcx.set_tainted_by_errors(guar); + } + + /// Try to note when an opaque is involved in a borrowck error and that + /// opaque captures lifetimes due to edition 2024. + // FIXME: This code is otherwise somewhat general, and could easily be adapted + // to explain why other things overcapture... like async fn and RPITITs. + pub(crate) fn note_due_to_edition_2024_opaque_capture_rules( + &self, + borrow: &BorrowData<'tcx>, + diag: &mut Diag<'_>, + ) { + // We look at all the locals. Why locals? Because it's the best thing + // I could think of that's correlated with the *instantiated* higher-ranked + // binder for calls, since we don't really store those anywhere else. + for ty in self.body.local_decls.iter().map(|local| local.ty) { + if !ty.has_opaque_types() { + continue; + } + + let tcx = self.infcx.tcx; + let ControlFlow::Break((opaque_def_id, offending_region_idx, location)) = ty + .visit_with(&mut FindOpaqueRegion { + regioncx: &self.regioncx, + tcx, + borrow_region: borrow.region, + }) + else { + continue; + }; + + // If an opaque explicitly captures a lifetime, then no need to point it out. + // FIXME: We should be using a better heuristic for `use<>`. + if tcx.rendered_precise_capturing_args(opaque_def_id).is_some() { + continue; + } + + // If one of the opaque's bounds mentions the region, then no need to + // point it out, since it would've been captured on edition 2021 as well. + // + // Also, while we're at it, collect all the lifetimes that the opaque + // *does* mention. We'll use that for the `+ use<'a>` suggestion below. + let mut visitor = CheckExplicitRegionMentionAndCollectGenerics { + tcx, + generics: tcx.generics_of(opaque_def_id), + offending_region_idx, + seen_opaques: [opaque_def_id].into_iter().collect(), + seen_lifetimes: Default::default(), + }; + if tcx + .explicit_item_bounds(opaque_def_id) + .skip_binder() + .visit_with(&mut visitor) + .is_break() + { + continue; + } + + // If we successfully located a terminator, then point it out + // and provide a suggestion if it's local. + match self.body.stmt_at(location) { + Either::Right(mir::Terminator { source_info, .. }) => { + diag.span_note( + source_info.span, + "this call may capture more lifetimes than intended, \ + because Rust 2024 has adjusted the `impl Trait` lifetime capture rules", + ); + let mut captured_args = visitor.seen_lifetimes; + // Add in all of the type and const params, too. + // Ordering here is kinda strange b/c we're walking backwards, + // but we're trying to provide *a* suggestion, not a nice one. + let mut next_generics = Some(visitor.generics); + let mut any_synthetic = false; + while let Some(generics) = next_generics { + for param in &generics.own_params { + if param.kind.is_ty_or_const() { + captured_args.insert(param.def_id); + } + if param.kind.is_synthetic() { + any_synthetic = true; + } + } + next_generics = generics.parent.map(|def_id| tcx.generics_of(def_id)); + } + + if let Some(opaque_def_id) = opaque_def_id.as_local() + && let hir::OpaqueTyOrigin::FnReturn { parent, .. } = + tcx.hir_expect_opaque_ty(opaque_def_id).origin + { + if let Some(sugg) = impl_trait_overcapture_suggestion( + tcx, + opaque_def_id, + parent, + captured_args, + ) { + sugg.add_to_diag(diag); + } + } else { + diag.span_help( + tcx.def_span(opaque_def_id), + format!( + "if you can modify this crate, add a precise \ + capturing bound to avoid overcapturing: `+ use<{}>`", + if any_synthetic { + "/* Args */".to_string() + } else { + captured_args + .into_iter() + .map(|def_id| tcx.item_name(def_id)) + .join(", ") + } + ), + ); + } + return; + } + Either::Left(_) => {} + } + } + } +} + +/// This visitor contains the bulk of the logic for this lint. +struct FindOpaqueRegion<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + regioncx: &'a RegionInferenceContext<'tcx>, + borrow_region: ty::RegionVid, +} + +impl<'tcx> TypeVisitor> for FindOpaqueRegion<'_, 'tcx> { + type Result = ControlFlow<(DefId, usize, Location), ()>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + // If we find an opaque in a local ty, then for each of its captured regions, + // try to find a path between that captured regions and our borrow region... + if let ty::Alias(ty::Opaque, opaque) = *ty.kind() + && let hir::OpaqueTyOrigin::FnReturn { parent, in_trait_or_impl: None } = + self.tcx.opaque_ty_origin(opaque.def_id) + { + let variances = self.tcx.variances_of(opaque.def_id); + for (idx, (arg, variance)) in std::iter::zip(opaque.args, variances).enumerate() { + // Skip uncaptured args. + if *variance == ty::Bivariant { + continue; + } + // We only care about regions. + let Some(opaque_region) = arg.as_region() else { + continue; + }; + // Don't try to convert a late-bound region, which shouldn't exist anyways (yet). + if opaque_region.is_bound() { + continue; + } + let opaque_region_vid = self.regioncx.to_region_vid(opaque_region); + + // Find a path between the borrow region and our opaque capture. + if let Some((path, _)) = + self.regioncx.find_constraint_path_between_regions(self.borrow_region, |r| { + r == opaque_region_vid + }) + { + for constraint in path { + // If we find a call in this path, then check if it defines the opaque. + if let ConstraintCategory::CallArgument(Some(call_ty)) = constraint.category + && let ty::FnDef(call_def_id, _) = *call_ty.kind() + // This function defines the opaque :D + && call_def_id == parent + && let Locations::Single(location) = constraint.locations + { + return ControlFlow::Break((opaque.def_id, idx, location)); + } + } + } + } + } + + ty.super_visit_with(self) + } +} + +struct CheckExplicitRegionMentionAndCollectGenerics<'tcx> { + tcx: TyCtxt<'tcx>, + generics: &'tcx ty::Generics, + offending_region_idx: usize, + seen_opaques: FxIndexSet, + seen_lifetimes: FxIndexSet, +} + +impl<'tcx> TypeVisitor> for CheckExplicitRegionMentionAndCollectGenerics<'tcx> { + type Result = ControlFlow<(), ()>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + match *ty.kind() { + ty::Alias(ty::Opaque, opaque) => { + if self.seen_opaques.insert(opaque.def_id) { + for (bound, _) in self + .tcx + .explicit_item_bounds(opaque.def_id) + .iter_instantiated_copied(self.tcx, opaque.args) + { + bound.visit_with(self)?; + } + } + ControlFlow::Continue(()) + } + _ => ty.super_visit_with(self), + } + } + + fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result { + match r.kind() { + ty::ReEarlyParam(param) => { + if param.index as usize == self.offending_region_idx { + ControlFlow::Break(()) + } else { + self.seen_lifetimes.insert(self.generics.region_param(param, self.tcx).def_id); + ControlFlow::Continue(()) + } + } + _ => ControlFlow::Continue(()), + } + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index b130cf8ed276..c7d2267e5f70 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -23,7 +23,6 @@ use rustc_trait_selection::error_reporting::infer::nice_region_error::{ self, HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor, find_anon_type, find_param_with_region, suggest_adding_lifetime_params, }; -use rustc_trait_selection::error_reporting::infer::region::unexpected_hidden_region_diagnostic; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{Obligation, ObligationCtxt}; use tracing::{debug, instrument, trace}; @@ -92,9 +91,6 @@ impl<'tcx> RegionErrors<'tcx> { ) -> impl Iterator, ErrorGuaranteed)> { self.0.into_iter() } - pub(crate) fn has_errors(&self) -> Option { - self.0.get(0).map(|x| x.1) - } } impl std::fmt::Debug for RegionErrors<'_> { @@ -108,18 +104,6 @@ pub(crate) enum RegionErrorKind<'tcx> { /// A generic bound failure for a type test (`T: 'a`). TypeTestError { type_test: TypeTest<'tcx> }, - /// An unexpected hidden region for an opaque type. - UnexpectedHiddenRegion { - /// The span for the member constraint. - span: Span, - /// The hidden type. - hidden_ty: Ty<'tcx>, - /// The opaque type. - key: ty::OpaqueTypeKey<'tcx>, - /// The unexpected region. - member_region: ty::Region<'tcx>, - }, - /// Higher-ranked subtyping error. BoundUniversalRegionError { /// The placeholder free region. @@ -218,7 +202,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { diag: &mut Diag<'_>, lower_bound: RegionVid, ) { - let mut suggestions = vec![]; let tcx = self.infcx.tcx; // find generic associated types in the given region 'lower_bound' @@ -240,9 +223,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { .collect::>(); debug!(?gat_id_and_generics); - // find higher-ranked trait bounds bounded to the generic associated types + // Look for the where-bound which introduces the placeholder. + // As we're using the HIR, we need to handle both `for<'a> T: Trait<'a>` + // and `T: for<'a> Trait`<'a>. let mut hrtb_bounds = vec![]; - gat_id_and_generics.iter().flatten().for_each(|(gat_hir_id, generics)| { + gat_id_and_generics.iter().flatten().for_each(|&(gat_hir_id, generics)| { for pred in generics.predicates { let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) = pred.kind @@ -251,17 +236,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }; if bound_generic_params .iter() - .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == *gat_hir_id) + .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) .is_some() { for bound in *bounds { hrtb_bounds.push(bound); } + } else { + for bound in *bounds { + if let Trait(trait_bound) = bound { + if trait_bound + .bound_generic_params + .iter() + .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id) + .is_some() + { + hrtb_bounds.push(bound); + return; + } + } + } } } }); debug!(?hrtb_bounds); + let mut suggestions = vec![]; hrtb_bounds.iter().for_each(|bound| { let Trait(PolyTraitRef { trait_ref, span: trait_span, .. }) = bound else { return; @@ -310,11 +310,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn report_region_errors(&mut self, nll_errors: RegionErrors<'tcx>) { // Iterate through all the errors, producing a diagnostic for each one. The diagnostics are // buffered in the `MirBorrowckCtxt`. - let mut outlives_suggestion = OutlivesSuggestionBuilder::default(); - let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = - None; - for (nll_error, _) in nll_errors.into_iter() { match nll_error { RegionErrorKind::TypeTestError { type_test } => { @@ -365,30 +361,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } } - RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => { - let named_ty = - self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, hidden_ty); - let named_key = - self.regioncx.name_regions_for_member_constraint(self.infcx.tcx, key); - let named_region = self - .regioncx - .name_regions_for_member_constraint(self.infcx.tcx, member_region); - let diag = unexpected_hidden_region_diagnostic( - self.infcx, - self.mir_def_id(), - span, - named_ty, - named_region, - named_key, - ); - if last_unexpected_hidden_region != Some((span, named_ty, named_key)) { - self.buffer_error(diag); - last_unexpected_hidden_region = Some((span, named_ty, named_key)); - } else { - diag.delay_as_bug(); - } - } - RegionErrorKind::BoundUniversalRegionError { longer_fr, placeholder, diff --git a/compiler/rustc_borrowck/src/handle_placeholders.rs b/compiler/rustc_borrowck/src/handle_placeholders.rs index aaaf2f45c869..4661906fbeb5 100644 --- a/compiler/rustc_borrowck/src/handle_placeholders.rs +++ b/compiler/rustc_borrowck/src/handle_placeholders.rs @@ -15,7 +15,6 @@ use tracing::debug; use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet}; use crate::consumers::OutlivesConstraint; use crate::diagnostics::UniverseInfo; -use crate::member_constraints::MemberConstraintSet; use crate::region_infer::values::{LivenessValues, PlaceholderIndices}; use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest}; use crate::ty::VarianceDiagInfo; @@ -30,7 +29,6 @@ pub(crate) struct LoweredConstraints<'tcx> { pub(crate) constraint_sccs: Sccs, pub(crate) definitions: Frozen>>, pub(crate) scc_annotations: IndexVec, - pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, pub(crate) outlives_constraints: Frozen>, pub(crate) type_tests: Vec>, pub(crate) liveness_constraints: LivenessValues, @@ -103,6 +101,10 @@ impl RegionTracker { self.max_nameable_universe } + pub(crate) fn max_placeholder_universe_reached(self) -> UniverseIndex { + self.max_placeholder_universe_reached + } + fn merge_min_max_seen(&mut self, other: &Self) { self.max_placeholder_universe_reached = std::cmp::max( self.max_placeholder_universe_reached, @@ -143,9 +145,10 @@ impl scc::Annotation for RegionTracker { /// Determines if the region variable definitions contain /// placeholders, and compute them for later use. -fn region_definitions<'tcx>( - universal_regions: &UniversalRegions<'tcx>, +// FIXME: This is also used by opaque type handling. Move it to a separate file. +pub(super) fn region_definitions<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, + universal_regions: &UniversalRegions<'tcx>, ) -> (Frozen>>, bool) { let var_infos = infcx.get_region_var_infos(); // Create a RegionDefinition for each inference variable. This happens here because @@ -157,7 +160,7 @@ fn region_definitions<'tcx>( for info in var_infos.iter() { let origin = match info.origin { RegionVariableOrigin::Nll(origin) => origin, - _ => NllRegionVariableOrigin::Existential { from_forall: false }, + _ => NllRegionVariableOrigin::Existential { name: None }, }; let definition = RegionDefinition { origin, universe: info.universe, external_name: None }; @@ -209,29 +212,17 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( infcx: &BorrowckInferCtxt<'tcx>, ) -> LoweredConstraints<'tcx> { let universal_regions = &universal_region_relations.universal_regions; - let (definitions, has_placeholders) = region_definitions(universal_regions, infcx); + let (definitions, has_placeholders) = region_definitions(infcx, universal_regions); let MirTypeckRegionConstraints { placeholder_indices, placeholder_index_to_region: _, liveness_constraints, mut outlives_constraints, - mut member_constraints, universe_causes, type_tests, } = constraints; - if let Some(guar) = universal_regions.tainted_by_errors() { - debug!("Universal regions tainted by errors; removing constraints!"); - // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all - // outlives bounds that we may end up checking. - outlives_constraints = Default::default(); - member_constraints = Default::default(); - - // Also taint the entire scope. - infcx.set_tainted_by_errors(guar); - } - let fr_static = universal_regions.fr_static; let compute_sccs = |constraints: &OutlivesConstraintSet<'tcx>, @@ -253,7 +244,6 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( return LoweredConstraints { type_tests, - member_constraints, constraint_sccs, scc_annotations: scc_annotations.scc_to_annotation, definitions, @@ -290,7 +280,6 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>( constraint_sccs, definitions, scc_annotations, - member_constraints, outlives_constraints: Frozen::freeze(outlives_constraints), type_tests, liveness_constraints, diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 321b18c9b78b..a081e0dee5d4 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -19,6 +19,7 @@ use std::borrow::Cow; use std::cell::{OnceCell, RefCell}; use std::marker::PhantomData; use std::ops::{ControlFlow, Deref}; +use std::rc::Rc; use borrow_set::LocalsStateAtExit; use root_cx::BorrowCheckRootCtxt; @@ -44,6 +45,7 @@ use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces} use rustc_mir_dataflow::move_paths::{ InitIndex, InitLocation, LookupResult, MoveData, MovePathIndex, }; +use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, Results, ResultsVisitor, visit_results}; use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT}; use rustc_span::{ErrorGuaranteed, Span, Symbol}; @@ -60,11 +62,14 @@ use crate::path_utils::*; use crate::place_ext::PlaceExt; use crate::places_conflict::{PlaceConflictBias, places_conflict}; use crate::polonius::PoloniusDiagnosticsContext; -use crate::polonius::legacy::{PoloniusLocationTable, PoloniusOutput}; +use crate::polonius::legacy::{ + PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, +}; use crate::prefixes::PrefixSet; use crate::region_infer::RegionInferenceContext; use crate::renumber::RegionCtxt; use crate::session_diagnostics::VarNeedNotMut; +use crate::type_check::MirTypeckResults; mod borrow_set; mod borrowck_errors; @@ -73,7 +78,6 @@ mod dataflow; mod def_use; mod diagnostics; mod handle_placeholders; -mod member_constraints; mod nll; mod path_utils; mod place_ext; @@ -321,7 +325,46 @@ fn do_mir_borrowck<'tcx>( let locals_are_invalidated_at_exit = tcx.hir_body_owner_kind(def).is_fn_or_closure(); let borrow_set = BorrowSet::build(tcx, body, locals_are_invalidated_at_exit, &move_data); - // Compute non-lexical lifetimes. + let location_map = Rc::new(DenseLocationMap::new(body)); + + let polonius_input = root_cx.consumer.as_ref().map_or(false, |c| c.polonius_input()) + || infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); + let mut polonius_facts = + (polonius_input || PoloniusFacts::enabled(infcx.tcx)).then_some(PoloniusFacts::default()); + + // Run the MIR type-checker. + let MirTypeckResults { + mut constraints, + universal_region_relations, + region_bound_pairs, + known_type_outlives_obligations, + polonius_context, + } = type_check::type_check( + root_cx, + &infcx, + body, + &promoted, + universal_regions, + &location_table, + &borrow_set, + &mut polonius_facts, + &move_data, + Rc::clone(&location_map), + ); + + let opaque_type_errors = region_infer::opaque_types::handle_opaque_type_uses( + root_cx, + &infcx, + &body, + &universal_region_relations, + ®ion_bound_pairs, + &known_type_outlives_obligations, + &location_map, + &mut constraints, + ); + + // Compute non-lexical lifetimes using the constraints computed + // by typechecking the MIR body. let nll::NllOutput { regioncx, polonius_input, @@ -332,12 +375,15 @@ fn do_mir_borrowck<'tcx>( } = nll::compute_regions( root_cx, &infcx, - universal_regions, body, - &promoted, &location_table, &move_data, &borrow_set, + location_map, + universal_region_relations, + constraints, + polonius_facts, + polonius_context, ); // Dump MIR results into a file, if that is enabled. This lets us @@ -434,7 +480,11 @@ fn do_mir_borrowck<'tcx>( }; // Compute and report region errors, if any. - mbcx.report_region_errors(nll_errors); + if nll_errors.is_empty() { + mbcx.report_opaque_type_errors(opaque_type_errors); + } else { + mbcx.report_region_errors(nll_errors); + } let (mut flow_analysis, flow_entry_states) = get_flow_results(tcx, body, &move_data, &borrow_set, ®ioncx); diff --git a/compiler/rustc_borrowck/src/member_constraints.rs b/compiler/rustc_borrowck/src/member_constraints.rs deleted file mode 100644 index bdd0f6fe11e0..000000000000 --- a/compiler/rustc_borrowck/src/member_constraints.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::hash::Hash; -use std::ops::Index; - -use rustc_data_structures::fx::FxIndexMap; -use rustc_index::{IndexSlice, IndexVec}; -use rustc_middle::ty::{self, Ty}; -use rustc_span::Span; -use tracing::instrument; - -/// Compactly stores a set of `R0 member of [R1...Rn]` constraints, -/// indexed by the region `R0`. -#[derive(Debug)] -pub(crate) struct MemberConstraintSet<'tcx, R> -where - R: Copy + Eq, -{ - /// Stores the first "member" constraint for a given `R0`. This is an - /// index into the `constraints` vector below. - first_constraints: FxIndexMap, - - /// Stores the data about each `R0 member of [R1..Rn]` constraint. - /// These are organized into a linked list, so each constraint - /// contains the index of the next constraint with the same `R0`. - constraints: IndexVec>, - - /// Stores the `R1..Rn` regions for *all* sets. For any given - /// constraint, we keep two indices so that we can pull out a - /// slice. - choice_regions: Vec, -} - -/// Represents a `R0 member of [R1..Rn]` constraint -#[derive(Debug)] -pub(crate) struct MemberConstraint<'tcx> { - next_constraint: Option, - - /// The span where the hidden type was instantiated. - pub(crate) definition_span: Span, - - /// The hidden type in which `R0` appears. (Used in error reporting.) - pub(crate) hidden_ty: Ty<'tcx>, - - pub(crate) key: ty::OpaqueTypeKey<'tcx>, - - /// The region `R0`. - pub(crate) member_region_vid: ty::RegionVid, - - /// Index of `R1` in `choice_regions` vector from `MemberConstraintSet`. - start_index: usize, - - /// Index of `Rn` in `choice_regions` vector from `MemberConstraintSet`. - end_index: usize, -} - -rustc_index::newtype_index! { - #[debug_format = "MemberConstraintIndex({})"] - pub(crate) struct NllMemberConstraintIndex {} -} - -impl Default for MemberConstraintSet<'_, ty::RegionVid> { - fn default() -> Self { - Self { - first_constraints: Default::default(), - constraints: Default::default(), - choice_regions: Default::default(), - } - } -} - -impl<'tcx> MemberConstraintSet<'tcx, ty::RegionVid> { - pub(crate) fn is_empty(&self) -> bool { - self.constraints.is_empty() - } - - /// Pushes a member constraint into the set. - #[instrument(level = "debug", skip(self))] - pub(crate) fn add_member_constraint( - &mut self, - key: ty::OpaqueTypeKey<'tcx>, - hidden_ty: Ty<'tcx>, - definition_span: Span, - member_region_vid: ty::RegionVid, - choice_regions: &[ty::RegionVid], - ) { - let next_constraint = self.first_constraints.get(&member_region_vid).cloned(); - let start_index = self.choice_regions.len(); - self.choice_regions.extend(choice_regions); - let end_index = self.choice_regions.len(); - let constraint_index = self.constraints.push(MemberConstraint { - next_constraint, - member_region_vid, - definition_span, - hidden_ty, - key, - start_index, - end_index, - }); - self.first_constraints.insert(member_region_vid, constraint_index); - } -} - -impl<'tcx, R1> MemberConstraintSet<'tcx, R1> -where - R1: Copy + Hash + Eq, -{ - /// Remap the "member region" key using `map_fn`, producing a new - /// member constraint set. This is used in the NLL code to map from - /// the original `RegionVid` to an scc index. In some cases, we - /// may have multiple `R1` values mapping to the same `R2` key -- that - /// is ok, the two sets will be merged. - pub(crate) fn into_mapped( - self, - mut map_fn: impl FnMut(R1) -> R2, - ) -> MemberConstraintSet<'tcx, R2> - where - R2: Copy + Hash + Eq, - { - // We can re-use most of the original data, just tweaking the - // linked list links a bit. - // - // For example if we had two keys `Ra` and `Rb` that both now - // wind up mapped to the same key `S`, we would append the - // linked list for `Ra` onto the end of the linked list for - // `Rb` (or vice versa) -- this basically just requires - // rewriting the final link from one list to point at the other - // other (see `append_list`). - - let MemberConstraintSet { first_constraints, mut constraints, choice_regions } = self; - - let mut first_constraints2 = FxIndexMap::default(); - first_constraints2.reserve(first_constraints.len()); - - for (r1, start1) in first_constraints { - let r2 = map_fn(r1); - if let Some(&start2) = first_constraints2.get(&r2) { - append_list(&mut constraints, start1, start2); - } - first_constraints2.insert(r2, start1); - } - - MemberConstraintSet { first_constraints: first_constraints2, constraints, choice_regions } - } -} - -impl<'tcx, R> MemberConstraintSet<'tcx, R> -where - R: Copy + Hash + Eq, -{ - pub(crate) fn all_indices(&self) -> impl Iterator { - self.constraints.indices() - } - - /// Iterate down the constraint indices associated with a given - /// peek-region. You can then use `choice_regions` and other - /// methods to access data. - pub(crate) fn indices( - &self, - member_region_vid: R, - ) -> impl Iterator { - let mut next = self.first_constraints.get(&member_region_vid).cloned(); - std::iter::from_fn(move || -> Option { - if let Some(current) = next { - next = self.constraints[current].next_constraint; - Some(current) - } else { - None - } - }) - } - - /// Returns the "choice regions" for a given member - /// constraint. This is the `R1..Rn` from a constraint like: - /// - /// ```text - /// R0 member of [R1..Rn] - /// ``` - pub(crate) fn choice_regions(&self, pci: NllMemberConstraintIndex) -> &[ty::RegionVid] { - let MemberConstraint { start_index, end_index, .. } = &self.constraints[pci]; - &self.choice_regions[*start_index..*end_index] - } -} - -impl<'tcx, R> Index for MemberConstraintSet<'tcx, R> -where - R: Copy + Eq, -{ - type Output = MemberConstraint<'tcx>; - - fn index(&self, i: NllMemberConstraintIndex) -> &MemberConstraint<'tcx> { - &self.constraints[i] - } -} - -/// Given a linked list starting at `source_list` and another linked -/// list starting at `target_list`, modify `target_list` so that it is -/// followed by `source_list`. -/// -/// Before: -/// -/// ```text -/// target_list: A -> B -> C -> (None) -/// source_list: D -> E -> F -> (None) -/// ``` -/// -/// After: -/// -/// ```text -/// target_list: A -> B -> C -> D -> E -> F -> (None) -/// ``` -fn append_list( - constraints: &mut IndexSlice>, - target_list: NllMemberConstraintIndex, - source_list: NllMemberConstraintIndex, -) { - let mut p = target_list; - loop { - let r = &mut constraints[p]; - match r.next_constraint { - Some(q) => p = q, - None => { - r.next_constraint = Some(source_list); - return; - } - } - } -} diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 41f67e78930f..8608a8a3a66f 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -5,7 +5,8 @@ use std::path::PathBuf; use std::rc::Rc; use std::str::FromStr; -use polonius_engine::{Algorithm, Output}; +use polonius_engine::{Algorithm, AllFacts, Output}; +use rustc_data_structures::frozen::Frozen; use rustc_index::IndexSlice; use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options}; use rustc_middle::mir::{Body, PassWhere, Promoted, create_dump_file, dump_enabled, dump_mir}; @@ -18,14 +19,16 @@ use rustc_span::sym; use tracing::{debug, instrument}; use crate::borrow_set::BorrowSet; +use crate::consumers::RustcFacts; use crate::diagnostics::RegionErrors; use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints; -use crate::polonius::PoloniusDiagnosticsContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; +use crate::polonius::{PoloniusContext, PoloniusDiagnosticsContext}; use crate::region_infer::RegionInferenceContext; -use crate::type_check::{self, MirTypeckResults}; +use crate::type_check::MirTypeckRegionConstraints; +use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; use crate::{ BorrowCheckRootCtxt, BorrowckInferCtxt, ClosureOutlivesSubject, ClosureRegionRequirements, @@ -76,41 +79,18 @@ pub(crate) fn replace_regions_in_mir<'tcx>( pub(crate) fn compute_regions<'tcx>( root_cx: &mut BorrowCheckRootCtxt<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, - universal_regions: UniversalRegions<'tcx>, body: &Body<'tcx>, - promoted: &IndexSlice>, location_table: &PoloniusLocationTable, move_data: &MoveData<'tcx>, borrow_set: &BorrowSet<'tcx>, + location_map: Rc, + universal_region_relations: Frozen>, + constraints: MirTypeckRegionConstraints<'tcx>, + mut polonius_facts: Option>, + polonius_context: Option, ) -> NllOutput<'tcx> { - let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); - let polonius_input = root_cx.consumer.as_ref().map_or(false, |c| c.polonius_input()) - || is_polonius_legacy_enabled; let polonius_output = root_cx.consumer.as_ref().map_or(false, |c| c.polonius_output()) - || is_polonius_legacy_enabled; - let mut polonius_facts = - (polonius_input || PoloniusFacts::enabled(infcx.tcx)).then_some(PoloniusFacts::default()); - - let location_map = Rc::new(DenseLocationMap::new(body)); - - // Run the MIR type-checker. - let MirTypeckResults { - constraints, - universal_region_relations, - opaque_type_values, - polonius_context, - } = type_check::type_check( - root_cx, - infcx, - body, - promoted, - universal_regions, - location_table, - borrow_set, - &mut polonius_facts, - move_data, - Rc::clone(&location_map), - ); + || infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints( constraints, @@ -168,13 +148,6 @@ pub(crate) fn compute_regions<'tcx>( let (closure_region_requirements, nll_errors) = regioncx.solve(infcx, body, polonius_output.clone()); - if let Some(guar) = nll_errors.has_errors() { - // Suppress unhelpful extra errors in `infer_opaque_types`. - infcx.set_tainted_by_errors(guar); - } - - regioncx.infer_opaque_types(root_cx, infcx, opaque_type_values); - NllOutput { regioncx, polonius_input: polonius_facts.map(Box::new), diff --git a/compiler/rustc_borrowck/src/polonius/constraints.rs b/compiler/rustc_borrowck/src/polonius/constraints.rs index 50f59dd0dee6..525957578595 100644 --- a/compiler/rustc_borrowck/src/polonius/constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/constraints.rs @@ -7,9 +7,7 @@ use rustc_mir_dataflow::points::PointIndex; /// /// This models two sources of constraints: /// - constraints that traverse the subsets between regions at a given point, `a@p: b@p`. These -/// depend on typeck constraints generated via assignments, calls, etc. (In practice there are -/// subtleties where a statement's effect only starts being visible at the successor point, via -/// the "result" of that statement). +/// depend on typeck constraints generated via assignments, calls, etc. /// - constraints that traverse the CFG via the same region, `a@p: a@q`, where `p` is a predecessor /// of `q`. These depend on the liveness of the regions at these points, as well as their /// variance. diff --git a/compiler/rustc_borrowck/src/polonius/legacy/facts.rs b/compiler/rustc_borrowck/src/polonius/legacy/facts.rs index 64389b11a651..1f8177477e68 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/facts.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/facts.rs @@ -184,22 +184,6 @@ where } } -impl FactRow for (A, B, C, D) -where - A: FactCell, - B: FactCell, - C: FactCell, - D: FactCell, -{ - fn write( - &self, - out: &mut dyn Write, - location_table: &PoloniusLocationTable, - ) -> Result<(), Box> { - write_row(out, location_table, &[&self.0, &self.1, &self.2, &self.3]) - } -} - fn write_row( out: &mut dyn Write, location_table: &PoloniusLocationTable, diff --git a/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs b/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs index 6ab09f731c07..2ba72180d66a 100644 --- a/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/liveness_constraints.rs @@ -105,22 +105,14 @@ fn propagate_loans_between_points( }); } - let Some(current_live_regions) = live_regions.row(current_point) else { - // There are no constraints to add: there are no live regions at the current point. - return; - }; let Some(next_live_regions) = live_regions.row(next_point) else { // There are no constraints to add: there are no live regions at the next point. return; }; for region in next_live_regions.iter() { - if !current_live_regions.contains(region) { - continue; - } - - // `region` is indeed live at both points, add a constraint between them, according to - // variance. + // `region` could be live at the current point, and is live at the next point: add a + // constraint between them, according to variance. if let Some(&direction) = live_region_variances.get(®ion) { add_liveness_constraint( region, diff --git a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs index 5cd265e0db92..bdc3047e5ba0 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_liveness.rs @@ -1,27 +1,18 @@ -use std::collections::{BTreeMap, BTreeSet}; - use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; -use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::{ - Body, Local, Location, Place, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, -}; -use rustc_middle::ty::{RegionVid, TyCtxt}; +use rustc_middle::ty::RegionVid; use rustc_mir_dataflow::points::PointIndex; use super::{LiveLoans, LocalizedOutlivesConstraintSet}; +use crate::BorrowSet; use crate::constraints::OutlivesConstraint; -use crate::dataflow::BorrowIndex; use crate::region_infer::values::LivenessValues; use crate::type_check::Locations; -use crate::{BorrowSet, PlaceConflictBias, places_conflict}; -/// Compute loan reachability, stop at kills, and trace loan liveness throughout the CFG, by +/// Compute loan reachability to approximately trace loan liveness throughout the CFG, by /// traversing the full graph of constraints that combines: /// - the localized constraints (the physical edges), /// - with the constraints that hold at all points (the logical edges). pub(super) fn compute_loan_liveness<'tcx>( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, liveness: &LivenessValues, outlives_constraints: impl Iterator>, borrow_set: &BorrowSet<'tcx>, @@ -29,11 +20,6 @@ pub(super) fn compute_loan_liveness<'tcx>( ) -> LiveLoans { let mut live_loans = LiveLoans::new(borrow_set.len()); - // FIXME: it may be preferable for kills to be encoded in the edges themselves, to simplify and - // likely make traversal (and constraint generation) more efficient. We also display kills on - // edges when visualizing the constraint graph anyways. - let kills = collect_kills(body, tcx, borrow_set); - // Create the full graph with the physical edges we've localized earlier, and the logical edges // of constraints that hold at all points. let logical_constraints = @@ -59,15 +45,15 @@ pub(super) fn compute_loan_liveness<'tcx>( continue; } - // Record the loan as being live on entry to this point. - live_loans.insert(node.point, loan_idx); - - // Here, we have a conundrum. There's currently a weakness in our theory, in that - // we're using a single notion of reachability to represent what used to be _two_ - // different transitive closures. It didn't seem impactful when coming up with the - // single-graph and reachability through space (regions) + time (CFG) concepts, but in - // practice the combination of time-traveling with kills is more impactful than - // initially anticipated. + // Record the loan as being live on entry to this point if it reaches a live region + // there. + // + // This is an approximation of liveness (which is the thing we want), in that we're + // using a single notion of reachability to represent what used to be _two_ different + // transitive closures. It didn't seem impactful when coming up with the single-graph + // and reachability through space (regions) + time (CFG) concepts, but in practice the + // combination of time-traveling with kills is more impactful than initially + // anticipated. // // Kills should prevent a loan from reaching its successor points in the CFG, but not // while time-traveling: we're not actually at that CFG point, but looking for @@ -92,40 +78,20 @@ pub(super) fn compute_loan_liveness<'tcx>( // two-step traversal described above: only kills encountered on exit via a backward // edge are ignored. // - // In our test suite, there are a couple of cases where kills are encountered while - // time-traveling, however as far as we can tell, always in cases where they would be - // unreachable. We have reason to believe that this is a property of the single-graph - // approach (but haven't proved it yet): - // - reachable kills while time-traveling would also be encountered via regular - // traversal - // - it makes _some_ sense to ignore unreachable kills, but subtleties around dead code - // in general need to be better thought through (like they were for NLLs). - // - ignoring kills is a conservative approximation: the loan is still live and could - // cause false positive errors at another place access. Soundness issues in this - // domain should look more like the absence of reachability instead. - // - // This is enough in practice to pass tests, and therefore is what we have implemented - // for now. + // This version of the analysis, however, is enough in practice to pass the tests that + // we care about and NLLs reject, without regressions on crater, and is an actionable + // subset of the full analysis. It also naturally points to areas of improvement that we + // wish to explore later, namely handling kills appropriately during traversal, instead + // of continuing traversal to all the reachable nodes. // - // FIXME: all of the above. Analyze potential unsoundness, possibly in concert with a - // borrowck implementation in a-mir-formality, fuzzing, or manually crafting - // counter-examples. + // FIXME: analyze potential unsoundness, possibly in concert with a borrowck + // implementation in a-mir-formality, fuzzing, or manually crafting counter-examples. - // Continuing traversal will depend on whether the loan is killed at this point, and - // whether we're time-traveling. - let current_location = liveness.location_from_point(node.point); - let is_loan_killed = - kills.get(¤t_location).is_some_and(|kills| kills.contains(&loan_idx)); + if liveness.is_live_at(node.region, liveness.location_from_point(node.point)) { + live_loans.insert(node.point, loan_idx); + } for succ in graph.outgoing_edges(node) { - // If the loan is killed at this point, it is killed _on exit_. But only during - // forward traversal. - if is_loan_killed { - let destination = liveness.location_from_point(succ.point); - if current_location.is_predecessor_of(destination, body) { - continue; - } - } stack.push(succ); } } @@ -192,116 +158,3 @@ impl LocalizedConstraintGraph { physical_edges.chain(materialized_edges) } } - -/// Traverses the MIR and collects kills. -fn collect_kills<'tcx>( - body: &Body<'tcx>, - tcx: TyCtxt<'tcx>, - borrow_set: &BorrowSet<'tcx>, -) -> BTreeMap> { - let mut collector = KillsCollector { borrow_set, tcx, body, kills: BTreeMap::default() }; - for (block, data) in body.basic_blocks.iter_enumerated() { - collector.visit_basic_block_data(block, data); - } - collector.kills -} - -struct KillsCollector<'a, 'tcx> { - body: &'a Body<'tcx>, - tcx: TyCtxt<'tcx>, - borrow_set: &'a BorrowSet<'tcx>, - - /// The set of loans killed at each location. - kills: BTreeMap>, -} - -// This visitor has a similar structure to the `Borrows` dataflow computation with respect to kills, -// and the datalog polonius fact generation for the `loan_killed_at` relation. -impl<'tcx> KillsCollector<'_, 'tcx> { - /// Records the borrows on the specified place as `killed`. For example, when assigning to a - /// local, or on a call's return destination. - fn record_killed_borrows_for_place(&mut self, place: Place<'tcx>, location: Location) { - // For the reasons described in graph traversal, we also filter out kills - // unreachable from the loan's introduction point, as they would stop traversal when - // e.g. checking for reachability in the subset graph through invariance constraints - // higher up. - let filter_unreachable_kills = |loan| { - let introduction = self.borrow_set[loan].reserve_location; - let reachable = introduction.is_predecessor_of(location, self.body); - reachable - }; - - let other_borrows_of_local = self - .borrow_set - .local_map - .get(&place.local) - .into_iter() - .flat_map(|bs| bs.iter()) - .copied(); - - // If the borrowed place is a local with no projections, all other borrows of this - // local must conflict. This is purely an optimization so we don't have to call - // `places_conflict` for every borrow. - if place.projection.is_empty() { - if !self.body.local_decls[place.local].is_ref_to_static() { - self.kills - .entry(location) - .or_default() - .extend(other_borrows_of_local.filter(|&loan| filter_unreachable_kills(loan))); - } - return; - } - - // By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given - // pair of array indices are not equal, so that when `places_conflict` returns true, we - // will be assured that two places being compared definitely denotes the same sets of - // locations. - let definitely_conflicting_borrows = other_borrows_of_local - .filter(|&i| { - places_conflict( - self.tcx, - self.body, - self.borrow_set[i].borrowed_place, - place, - PlaceConflictBias::NoOverlap, - ) - }) - .filter(|&loan| filter_unreachable_kills(loan)); - - self.kills.entry(location).or_default().extend(definitely_conflicting_borrows); - } - - /// Records the borrows on the specified local as `killed`. - fn record_killed_borrows_for_local(&mut self, local: Local, location: Location) { - if let Some(borrow_indices) = self.borrow_set.local_map.get(&local) { - self.kills.entry(location).or_default().extend(borrow_indices.iter()); - } - } -} - -impl<'tcx> Visitor<'tcx> for KillsCollector<'_, 'tcx> { - fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { - // Make sure there are no remaining borrows for locals that have gone out of scope. - if let StatementKind::StorageDead(local) = statement.kind { - self.record_killed_borrows_for_local(local, location); - } - - self.super_statement(statement, location); - } - - fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { - // When we see `X = ...`, then kill borrows of `(*X).foo` and so forth. - self.record_killed_borrows_for_place(*place, location); - self.super_assign(place, rvalue, location); - } - - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { - // A `Call` terminator's return value can be a local which has borrows, so we need to record - // those as killed as well. - if let TerminatorKind::Call { destination, .. } = terminator.kind { - self.record_killed_borrows_for_place(destination, location); - } - - self.super_terminator(terminator, location); - } -} diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 142ef8ba28ef..a9092b1981e1 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -146,8 +146,8 @@ impl PoloniusContext { /// - converting NLL typeck constraints to be localized /// - encoding liveness constraints /// - /// Then, this graph is traversed, and combined with kills, reachability is recorded as loan - /// liveness, to be used by the loan scope and active loans computations. + /// Then, this graph is traversed, reachability is recorded as loan liveness, to be used by the + /// loan scope and active loans computations. /// /// The constraint data will be used to compute errors and diagnostics. pub(crate) fn compute_loan_liveness<'tcx>( @@ -182,8 +182,6 @@ impl PoloniusContext { // Now that we have a complete graph, we can compute reachability to trace the liveness of // loans for the next step in the chain, the NLL loan scope and active loans computations. let live_loans = compute_loan_liveness( - tcx, - body, regioncx.liveness_constraints(), regioncx.outlives_constraints(), borrow_set, diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs index 1289b1899eb3..e4e52962bf7f 100644 --- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs @@ -47,9 +47,7 @@ pub(super) fn convert_typeck_constraints<'tcx>( tcx, body, stmt, - liveness, &outlives_constraint, - location, point, universal_regions, ) @@ -78,9 +76,7 @@ fn localize_statement_constraint<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, stmt: &Statement<'tcx>, - liveness: &LivenessValues, outlives_constraint: &OutlivesConstraint<'tcx>, - current_location: Location, current_point: PointIndex, universal_regions: &UniversalRegions<'tcx>, ) -> LocalizedOutlivesConstraint { @@ -98,8 +94,8 @@ fn localize_statement_constraint<'tcx>( // - and that should be impossible in MIR // // When we have a more complete implementation in the future, tested with crater, etc, - // we can relax this to a debug assert instead, or remove it. - assert!( + // we can remove this assertion. It's a debug assert because it can be expensive. + debug_assert!( { let mut lhs_regions = FxHashSet::default(); tcx.for_each_free_region(lhs, |region| { @@ -119,16 +115,8 @@ fn localize_statement_constraint<'tcx>( "there should be no common regions between the LHS and RHS of an assignment" ); - // As mentioned earlier, we should be tracking these better upstream but: we want to - // relate the types on entry to the type of the place on exit. That is, outlives - // constraints on the RHS are on entry, and outlives constraints to/from the LHS are on - // exit (i.e. on entry to the successor location). let lhs_ty = body.local_decls[lhs.local].ty; - let successor_location = Location { - block: current_location.block, - statement_index: current_location.statement_index + 1, - }; - let successor_point = liveness.point_from_location(successor_location); + let successor_point = current_point; compute_constraint_direction( tcx, outlives_constraint, @@ -195,6 +183,7 @@ fn localize_terminator_constraint<'tcx>( } } } + /// For a given outlives constraint and CFG edge, returns the localized constraint with the /// appropriate `from`-`to` direction. This is computed according to whether the constraint flows to /// or from a free region in the given `value`, some kind of result for an effectful operation, like diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs index a3e29982e90f..b2f67c43125d 100644 --- a/compiler/rustc_borrowck/src/region_infer/graphviz.rs +++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs @@ -41,7 +41,22 @@ fn render_region_vid<'tcx>( "".to_string() }; - format!("{:?}{universe_str}{external_name_str}", rvid) + let extra_info = match regioncx.region_definition(rvid).origin { + NllRegionVariableOrigin::FreeRegion => "".to_string(), + NllRegionVariableOrigin::Placeholder(p) => match p.bound.kind { + ty::BoundRegionKind::Named(def_id) => { + format!(" (for<{}>)", tcx.item_name(def_id)) + } + ty::BoundRegionKind::ClosureEnv | ty::BoundRegionKind::Anon => " (for<'_>)".to_string(), + ty::BoundRegionKind::NamedAnon(_) => { + bug!("only used for pretty printing") + } + }, + NllRegionVariableOrigin::Existential { name: Some(name), .. } => format!(" (ex<{name}>)"), + NllRegionVariableOrigin::Existential { .. } => format!(" (ex<'_>)"), + }; + + format!("{:?}{universe_str}{external_name_str}{extra_info}", rvid) } impl<'tcx> RegionInferenceContext<'tcx> { diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 68f1637e07ee..3d95eb4663ac 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1,8 +1,6 @@ -use std::cell::OnceCell; use std::collections::VecDeque; use std::rc::Rc; -use rustc_data_structures::binary_search_util; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::scc::{self, Sccs}; @@ -24,15 +22,13 @@ use rustc_span::hygiene::DesugaringKind; use rustc_span::{DUMMY_SP, Span}; use tracing::{Level, debug, enabled, instrument, trace}; -use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph}; +use crate::constraints::graph::NormalConstraintGraph; use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet}; use crate::dataflow::BorrowIndex; use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo}; use crate::handle_placeholders::{LoweredConstraints, RegionTracker}; -use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex}; use crate::polonius::LiveLoans; use crate::polonius::legacy::PoloniusOutput; -use crate::region_infer::reverse_sccs::ReverseSccGraph; use crate::region_infer::values::{LivenessValues, RegionElement, RegionValues, ToElementIndex}; use crate::type_check::Locations; use crate::type_check::free_region_relations::UniversalRegionRelations; @@ -44,7 +40,7 @@ use crate::{ mod dump_mir; mod graphviz; -mod opaque_types; +pub(crate) mod opaque_types; mod reverse_sccs; pub(crate) mod values; @@ -120,20 +116,6 @@ pub struct RegionInferenceContext<'tcx> { scc_annotations: IndexVec, - /// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if - /// `B: A`. This is used to compute the universal regions that are required - /// to outlive a given SCC. - rev_scc_graph: OnceCell, - - /// The "R0 member of [R1..Rn]" constraints, indexed by SCC. - member_constraints: Rc>, - - /// Records the member constraints that we applied to each scc. - /// This is useful for error reporting. Once constraint - /// propagation is done, this vector is sorted according to - /// `member_region_scc`. - member_constraints_applied: Vec, - /// Map universe indexes to information on why we created it. universe_causes: FxIndexMap>, @@ -150,32 +132,6 @@ pub struct RegionInferenceContext<'tcx> { universal_region_relations: Frozen>, } -/// Each time that `apply_member_constraint` is successful, it appends -/// one of these structs to the `member_constraints_applied` field. -/// This is used in error reporting to trace out what happened. -/// -/// The way that `apply_member_constraint` works is that it effectively -/// adds a new lower bound to the SCC it is analyzing: so you wind up -/// with `'R: 'O` where `'R` is the pick-region and `'O` is the -/// minimal viable option. -#[derive(Debug)] -pub(crate) struct AppliedMemberConstraint { - /// The SCC that was affected. (The "member region".) - /// - /// The vector if `AppliedMemberConstraint` elements is kept sorted - /// by this field. - pub(crate) member_region_scc: ConstraintSccIndex, - - /// The "best option" that `apply_member_constraint` found -- this was - /// added as an "ad-hoc" lower-bound to `member_region_scc`. - pub(crate) min_choice: ty::RegionVid, - - /// The "member constraint index" -- we can find out details about - /// the constraint from - /// `set.member_constraints[member_constraint_index]`. - pub(crate) member_constraint_index: NllMemberConstraintIndex, -} - #[derive(Debug)] pub(crate) struct RegionDefinition<'tcx> { /// What kind of variable is this -- a free region? existential @@ -268,7 +224,6 @@ enum Trace<'a, 'tcx> { StartRegion, FromGraph(&'a OutlivesConstraint<'tcx>), FromStatic(RegionVid), - FromMember(RegionVid, RegionVid, Span), NotVisited, } @@ -363,7 +318,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { liveness_constraints, universe_causes, placeholder_indices, - member_constraints, } = lowered_constraints; debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); @@ -385,9 +339,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { scc_values.merge_liveness(scc, region, &liveness_constraints); } - let member_constraints = - Rc::new(member_constraints.into_mapped(|r| constraint_sccs.scc(r))); - let mut result = Self { definitions, liveness_constraints, @@ -395,9 +346,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { constraint_graph, constraint_sccs, scc_annotations, - rev_scc_graph: OnceCell::new(), - member_constraints, - member_constraints_applied: Vec::new(), universe_causes, scc_values, type_tests, @@ -417,7 +365,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// minimum values. /// /// For example: - /// ``` + /// ```ignore (illustrative) /// fn foo<'a, 'b>( /* ... */ ) where 'a: 'b { /* ... */ } /// ``` /// would initialize two variables like so: @@ -550,19 +498,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.placeholders_contained_in(scc) } - /// Once region solving has completed, this function will return the member constraints that - /// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`. - pub(crate) fn applied_member_constraints( - &self, - scc: ConstraintSccIndex, - ) -> &[AppliedMemberConstraint] { - binary_search_util::binary_search_slice( - &self.member_constraints_applied, - |applied| applied.member_region_scc, - &scc, - ) - } - /// Performs region inference and report errors if we see any /// unsatisfiable constraints. If this is a closure, returns the /// region requirements to propagate to our creator, if any. @@ -607,12 +542,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { debug!(?errors_buffer); - if errors_buffer.is_empty() { - self.check_member_constraints(infcx, &mut errors_buffer); - } - - debug!(?errors_buffer); - let outlives_requirements = outlives_requirements.unwrap_or_default(); if outlives_requirements.is_empty() { @@ -642,146 +571,15 @@ impl<'tcx> RegionInferenceContext<'tcx> { }); // To propagate constraints, we walk the DAG induced by the - // SCC. For each SCC, we visit its successors and compute + // SCC. For each SCC `A`, we visit its successors and compute // their values, then we union all those values to get our // own. - for scc in self.constraint_sccs.all_sccs() { - self.compute_value_for_scc(scc); - } - - // Sort the applied member constraints so we can binary search - // through them later. - self.member_constraints_applied.sort_by_key(|applied| applied.member_region_scc); - } - - /// Computes the value of the SCC `scc_a`, which has not yet been - /// computed, by unioning the values of its successors. - /// Assumes that all successors have been computed already - /// (which is assured by iterating over SCCs in dependency order). - #[instrument(skip(self), level = "debug")] - fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) { - // Walk each SCC `B` such that `A: B`... - for &scc_b in self.constraint_sccs.successors(scc_a) { - debug!(?scc_b); - self.scc_values.add_region(scc_a, scc_b); - } - - // Now take member constraints into account. - let member_constraints = Rc::clone(&self.member_constraints); - for m_c_i in member_constraints.indices(scc_a) { - self.apply_member_constraint(scc_a, m_c_i, member_constraints.choice_regions(m_c_i)); - } - - debug!(value = ?self.scc_values.region_value_str(scc_a)); - } - - /// Invoked for each `R0 member of [R1..Rn]` constraint. - /// - /// `scc` is the SCC containing R0, and `choice_regions` are the - /// `R1..Rn` regions -- they are always known to be universal - /// regions (and if that's not true, we just don't attempt to - /// enforce the constraint). - /// - /// The current value of `scc` at the time the method is invoked - /// is considered a *lower bound*. If possible, we will modify - /// the constraint to set it equal to one of the option regions. - /// If we make any changes, returns true, else false. - /// - /// This function only adds the member constraints to the region graph, - /// it does not check them. They are later checked in - /// `check_member_constraints` after the region graph has been computed. - #[instrument(skip(self, member_constraint_index), level = "debug")] - fn apply_member_constraint( - &mut self, - scc: ConstraintSccIndex, - member_constraint_index: NllMemberConstraintIndex, - choice_regions: &[ty::RegionVid], - ) { - // Create a mutable vector of the options. We'll try to winnow - // them down. - let mut choice_regions: Vec = choice_regions.to_vec(); - - // Convert to the SCC representative: sometimes we have inference - // variables in the member constraint that wind up equated with - // universal regions. The scc representative is the minimal numbered - // one from the corresponding scc so it will be the universal region - // if one exists. - for c_r in &mut choice_regions { - let scc = self.constraint_sccs.scc(*c_r); - *c_r = self.scc_representative(scc); - } - - // If the member region lives in a higher universe, we currently choose - // the most conservative option by leaving it unchanged. - if !self.max_nameable_universe(scc).is_root() { - return; - } - - // The existing value for `scc` is a lower-bound. This will - // consist of some set `{P} + {LB}` of points `{P}` and - // lower-bound free regions `{LB}`. As each choice region `O` - // is a free region, it will outlive the points. But we can - // only consider the option `O` if `O: LB`. - choice_regions.retain(|&o_r| { - self.scc_values - .universal_regions_outlived_by(scc) - .all(|lb| self.universal_region_relations.outlives(o_r, lb)) - }); - debug!(?choice_regions, "after lb"); - - // Now find all the *upper bounds* -- that is, each UB is a - // free region that must outlive the member region `R0` (`UB: - // R0`). Therefore, we need only keep an option `O` if `UB: O` - // for all UB. - let universal_region_relations = &self.universal_region_relations; - for ub in self.reverse_scc_graph().upper_bounds(scc) { - debug!(?ub); - choice_regions.retain(|&o_r| universal_region_relations.outlives(ub, o_r)); - } - debug!(?choice_regions, "after ub"); - - // At this point we can pick any member of `choice_regions` and would like to choose - // it to be a small as possible. To avoid potential non-determinism we will pick the - // smallest such choice. - // - // Because universal regions are only partially ordered (i.e, not every two regions are - // comparable), we will ignore any region that doesn't compare to all others when picking - // the minimum choice. - // - // For example, consider `choice_regions = ['static, 'a, 'b, 'c, 'd, 'e]`, where - // `'static: 'a, 'static: 'b, 'a: 'c, 'b: 'c, 'c: 'd, 'c: 'e`. - // `['d, 'e]` are ignored because they do not compare - the same goes for `['a, 'b]`. - let totally_ordered_subset = choice_regions.iter().copied().filter(|&r1| { - choice_regions.iter().all(|&r2| { - self.universal_region_relations.outlives(r1, r2) - || self.universal_region_relations.outlives(r2, r1) - }) - }); - // Now we're left with `['static, 'c]`. Pick `'c` as the minimum! - let Some(min_choice) = totally_ordered_subset.reduce(|r1, r2| { - let r1_outlives_r2 = self.universal_region_relations.outlives(r1, r2); - let r2_outlives_r1 = self.universal_region_relations.outlives(r2, r1); - match (r1_outlives_r2, r2_outlives_r1) { - (true, true) => r1.min(r2), - (true, false) => r2, - (false, true) => r1, - (false, false) => bug!("incomparable regions in total order"), + for scc_a in self.constraint_sccs.all_sccs() { + // Walk each SCC `B` such that `A: B`... + for &scc_b in self.constraint_sccs.successors(scc_a) { + debug!(?scc_b); + self.scc_values.add_region(scc_a, scc_b); } - }) else { - debug!("no unique minimum choice"); - return; - }; - - // As we require `'scc: 'min_choice`, we have definitely already computed - // its `scc_values` at this point. - let min_choice_scc = self.constraint_sccs.scc(min_choice); - debug!(?min_choice, ?min_choice_scc); - if self.scc_values.add_region(scc, min_choice_scc) { - self.member_constraints_applied.push(AppliedMemberConstraint { - member_region_scc: scc, - min_choice, - member_constraint_index, - }); } } @@ -1544,43 +1342,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { } } - #[instrument(level = "debug", skip(self, infcx, errors_buffer))] - fn check_member_constraints( - &self, - infcx: &InferCtxt<'tcx>, - errors_buffer: &mut RegionErrors<'tcx>, - ) { - let member_constraints = Rc::clone(&self.member_constraints); - for m_c_i in member_constraints.all_indices() { - debug!(?m_c_i); - let m_c = &member_constraints[m_c_i]; - let member_region_vid = m_c.member_region_vid; - debug!( - ?member_region_vid, - value = ?self.region_value_str(member_region_vid), - ); - let choice_regions = member_constraints.choice_regions(m_c_i); - debug!(?choice_regions); - - // Did the member region wind up equal to any of the option regions? - if let Some(o) = - choice_regions.iter().find(|&&o_r| self.eval_equal(o_r, m_c.member_region_vid)) - { - debug!("evaluated as equal to {:?}", o); - continue; - } - - // If not, report an error. - let member_region = ty::Region::new_var(infcx.tcx, member_region_vid); - errors_buffer.push(RegionErrorKind::UnexpectedHiddenRegion { - span: m_c.definition_span, - hidden_ty: m_c.hidden_ty, - key: m_c.key, - member_region, - }); - } - } - /// We have a constraint `fr1: fr2` that is not satisfied, where /// `fr2` represents some universal region. Here, `r` is some /// region where we know that `fr1: r` and this function has the @@ -1644,18 +1405,39 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// Walks the graph of constraints (where `'a: 'b` is considered - /// an edge `'a -> 'b`) to find all paths from `from_region` to - /// `to_region`. The paths are accumulated into the vector - /// `results`. The paths are stored as a series of - /// `ConstraintIndex` values -- in other words, a list of *edges*. + /// an edge `'a -> 'b`) to find a path from `from_region` to + /// `to_region`. /// /// Returns: a series of constraints as well as the region `R` /// that passed the target test. #[instrument(skip(self, target_test), ret)] - pub(crate) fn find_constraint_paths_between_regions( + pub(crate) fn find_constraint_path_between_regions( &self, from_region: RegionVid, target_test: impl Fn(RegionVid) -> bool, + ) -> Option<(Vec>, RegionVid)> { + self.find_constraint_path_between_regions_inner(true, from_region, &target_test).or_else( + || self.find_constraint_path_between_regions_inner(false, from_region, &target_test), + ) + } + + /// The constraints we get from equating the hidden type of each use of an opaque + /// with its final concrete type may end up getting preferred over other, potentially + /// longer constraint paths. + /// + /// Given that we compute the final concrete type by relying on this existing constraint + /// path, this can easily end up hiding the actual reason for why we require these regions + /// to be equal. + /// + /// To handle this, we first look at the path while ignoring these constraints and then + /// retry while considering them. This is not perfect, as the `from_region` may have already + /// been partially related to its argument region, so while we rely on a member constraint + /// to get a complete path, the most relevant step of that path already existed before then. + fn find_constraint_path_between_regions_inner( + &self, + ignore_opaque_type_constraints: bool, + from_region: RegionVid, + target_test: impl Fn(RegionVid) -> bool, ) -> Option<(Vec>, RegionVid)> { let mut context = IndexVec::from_elem(Trace::NotVisited, &self.definitions); context[from_region] = Trace::StartRegion; @@ -1670,7 +1452,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { while let Some(r) = deque.pop_front() { debug!( - "find_constraint_paths_between_regions: from_region={:?} r={:?} value={}", + "find_constraint_path_between_regions: from_region={:?} r={:?} value={}", from_region, r, self.region_value_str(r), @@ -1704,20 +1486,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { result.push(c); } - Trace::FromMember(sup, sub, span) => { - let c = OutlivesConstraint { - sup, - sub, - locations: Locations::All(span), - span, - category: ConstraintCategory::OpaqueType, - variance_info: ty::VarianceDiagInfo::default(), - from_closure: false, - }; - p = c.sup; - result.push(c); - } - Trace::StartRegion => { result.reverse(); return Some((result, r)); @@ -1756,23 +1524,21 @@ impl<'tcx> RegionInferenceContext<'tcx> { let edges = self.constraint_graph.outgoing_edges_from_graph(r, &self.constraints); // This loop can be hot. for constraint in edges { - if matches!(constraint.category, ConstraintCategory::IllegalUniverse) { - debug!("Ignoring illegal universe constraint: {constraint:?}"); - continue; + match constraint.category { + ConstraintCategory::IllegalUniverse => { + debug!("Ignoring illegal universe constraint: {constraint:?}"); + continue; + } + ConstraintCategory::OpaqueType if ignore_opaque_type_constraints => { + debug!("Ignoring member constraint: {constraint:?}"); + continue; + } + _ => {} } debug_assert_eq!(constraint.sup, r); handle_trace(constraint.sub, Trace::FromGraph(constraint)); } } - - // Member constraints can also give rise to `'r: 'x` edges that - // were not part of the graph initially, so watch out for those. - // (But they are extremely rare; this loop is very cold.) - for constraint in self.applied_member_constraints(self.constraint_sccs.scc(r)) { - let sub = constraint.min_choice; - let p_c = &self.member_constraints[constraint.member_constraint_index]; - handle_trace(sub, Trace::FromMember(r, sub, p_c.definition_span)); - } } None @@ -1783,7 +1549,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, location: Location) -> RegionVid { trace!(scc = ?self.constraint_sccs.scc(fr1)); trace!(universe = ?self.max_nameable_universe(self.constraint_sccs.scc(fr1))); - self.find_constraint_paths_between_regions(fr1, |r| { + self.find_constraint_path_between_regions(fr1, |r| { // First look for some `r` such that `fr1: r` and `r` is live at `location` trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r)); self.liveness_constraints.is_live_at(r, location) @@ -1793,9 +1559,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // `fr1: r` and `r` is a placeholder from some universe // `fr1` cannot name. This would force `fr1` to be // `'static`. - self.find_constraint_paths_between_regions(fr1, |r| { - self.cannot_name_placeholder(fr1, r) - }) + self.find_constraint_path_between_regions(fr1, |r| self.cannot_name_placeholder(fr1, r)) }) .or_else(|| { // If we fail to find THAT, it may be that `fr1` is a @@ -1808,9 +1572,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // must be able to name the universe of R2, because R2 will // be at least `'empty(Universe(R2))`, and `R1` must be at // larger than that. - self.find_constraint_paths_between_regions(fr1, |r| { - self.cannot_name_placeholder(r, fr1) - }) + self.find_constraint_path_between_regions(fr1, |r| self.cannot_name_placeholder(r, fr1)) }) .map(|(_path, r)| r) .unwrap() @@ -1866,9 +1628,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { ) -> (BlameConstraint<'tcx>, Vec>) { // Find all paths let (path, target_region) = self - .find_constraint_paths_between_regions(from_region, target_test) + .find_constraint_path_between_regions(from_region, target_test) .or_else(|| { - self.find_constraint_paths_between_regions(from_region, |r| { + self.find_constraint_path_between_regions(from_region, |r| { self.cannot_name_placeholder(from_region, r) }) }) @@ -1939,10 +1701,15 @@ impl<'tcx> RegionInferenceContext<'tcx> { // // and here we prefer to blame the source (the y = x statement). let blame_source = match from_region_origin { - NllRegionVariableOrigin::FreeRegion - | NllRegionVariableOrigin::Existential { from_forall: false } => true, - NllRegionVariableOrigin::Placeholder(_) - | NllRegionVariableOrigin::Existential { from_forall: true } => false, + NllRegionVariableOrigin::FreeRegion => true, + NllRegionVariableOrigin::Placeholder(_) => false, + // `'existential: 'whatever` never results in a region error by itself. + // We may always infer it to `'static` afterall. This means while an error + // path may go through an existential, these existentials are never the + // `from_region`. + NllRegionVariableOrigin::Existential { name: _ } => { + unreachable!("existentials can outlive everything") + } }; // To pick a constraint to blame, we organize constraints by how interesting we expect them @@ -2099,11 +1866,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self.constraint_sccs } - /// Access to the region graph, built from the outlives constraints. - pub(crate) fn region_graph(&self) -> RegionGraph<'_, 'tcx, graph::Normal> { - self.constraint_graph.region_graph(&self.constraints, self.universal_regions().fr_static) - } - /// Returns the representative `RegionVid` for a given SCC. /// See `RegionTracker` for how a region variable ID is chosen. /// diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs deleted file mode 100644 index 6270e6d9a60e..000000000000 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ /dev/null @@ -1,291 +0,0 @@ -use rustc_data_structures::fx::FxIndexMap; -use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin}; -use rustc_macros::extension; -use rustc_middle::ty::{ - self, DefiningScopeKind, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, - TypeVisitableExt, fold_regions, -}; -use rustc_span::Span; -use rustc_trait_selection::opaque_types::check_opaque_type_parameter_valid; -use tracing::{debug, instrument}; - -use super::RegionInferenceContext; -use crate::BorrowCheckRootCtxt; -use crate::session_diagnostics::LifetimeMismatchOpaqueParam; -use crate::universal_regions::RegionClassification; - -impl<'tcx> RegionInferenceContext<'tcx> { - /// Resolve any opaque types that were encountered while borrow checking - /// this item. This is then used to get the type in the `type_of` query. - /// - /// For example consider `fn f<'a>(x: &'a i32) -> impl Sized + 'a { x }`. - /// This is lowered to give HIR something like - /// - /// type f<'a>::_Return<'_x> = impl Sized + '_x; - /// fn f<'a>(x: &'a i32) -> f<'a>::_Return<'a> { x } - /// - /// When checking the return type record the type from the return and the - /// type used in the return value. In this case they might be `_Return<'1>` - /// and `&'2 i32` respectively. - /// - /// Once we to this method, we have completed region inference and want to - /// call `infer_opaque_definition_from_instantiation` to get the inferred - /// type of `_Return<'_x>`. `infer_opaque_definition_from_instantiation` - /// compares lifetimes directly, so we need to map the inference variables - /// back to concrete lifetimes: `'static`, `ReEarlyParam` or `ReLateParam`. - /// - /// First we map the regions in the generic parameters `_Return<'1>` to - /// their `external_name` giving `_Return<'a>`. This step is a bit involved. - /// See the [rustc-dev-guide chapter] for more info. - /// - /// Then we map all the lifetimes in the concrete type to an equal - /// universal region that occurs in the opaque type's args, in this case - /// this would result in `&'a i32`. We only consider regions in the args - /// in case there is an equal region that does not. For example, this should - /// be allowed: - /// `fn f<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a { x }` - /// - /// This will then allow `infer_opaque_definition_from_instantiation` to - /// determine that `_Return<'_x> = &'_x i32`. - /// - /// There's a slight complication around closures. Given - /// `fn f<'a: 'a>() { || {} }` the closure's type is something like - /// `f::<'a>::{{closure}}`. The region parameter from f is essentially - /// ignored by type checking so ends up being inferred to an empty region. - /// Calling `universal_upper_bound` for such a region gives `fr_fn_body`, - /// which has no `external_name` in which case we use `'{erased}` as the - /// region to pass to `infer_opaque_definition_from_instantiation`. - /// - /// [rustc-dev-guide chapter]: - /// https://rustc-dev-guide.rust-lang.org/opaque-types-region-infer-restrictions.html - #[instrument(level = "debug", skip(self, root_cx, infcx), ret)] - pub(crate) fn infer_opaque_types( - &self, - root_cx: &mut BorrowCheckRootCtxt<'tcx>, - infcx: &InferCtxt<'tcx>, - opaque_ty_decls: FxIndexMap, OpaqueHiddenType<'tcx>>, - ) { - let mut decls_modulo_regions: FxIndexMap, (OpaqueTypeKey<'tcx>, Span)> = - FxIndexMap::default(); - - for (opaque_type_key, concrete_type) in opaque_ty_decls { - debug!(?opaque_type_key, ?concrete_type); - - let mut arg_regions: Vec<(ty::RegionVid, ty::Region<'_>)> = - vec![(self.universal_regions().fr_static, infcx.tcx.lifetimes.re_static)]; - - let opaque_type_key = - opaque_type_key.fold_captured_lifetime_args(infcx.tcx, |region| { - // Use the SCC representative instead of directly using `region`. - // See [rustc-dev-guide chapter] § "Strict lifetime equality". - let scc = self.constraint_sccs.scc(region.as_var()); - let vid = self.scc_representative(scc); - let named = match self.definitions[vid].origin { - // Iterate over all universal regions in a consistent order and find the - // *first* equal region. This makes sure that equal lifetimes will have - // the same name and simplifies subsequent handling. - // See [rustc-dev-guide chapter] § "Semantic lifetime equality". - NllRegionVariableOrigin::FreeRegion => self - .universal_regions() - .universal_regions_iter() - .filter(|&ur| { - // See [rustc-dev-guide chapter] § "Closure restrictions". - !matches!( - self.universal_regions().region_classification(ur), - Some(RegionClassification::External) - ) - }) - .find(|&ur| self.universal_region_relations.equal(vid, ur)) - .map(|ur| self.definitions[ur].external_name.unwrap()), - NllRegionVariableOrigin::Placeholder(placeholder) => { - Some(ty::Region::new_placeholder(infcx.tcx, placeholder)) - } - NllRegionVariableOrigin::Existential { .. } => None, - } - .unwrap_or_else(|| { - ty::Region::new_error_with_message( - infcx.tcx, - concrete_type.span, - "opaque type with non-universal region args", - ) - }); - - arg_regions.push((vid, named)); - named - }); - debug!(?opaque_type_key, ?arg_regions); - - let concrete_type = fold_regions(infcx.tcx, concrete_type, |region, _| { - arg_regions - .iter() - .find(|&&(arg_vid, _)| self.eval_equal(region.as_var(), arg_vid)) - .map(|&(_, arg_named)| arg_named) - .unwrap_or(infcx.tcx.lifetimes.re_erased) - }); - debug!(?concrete_type); - - let ty = - infcx.infer_opaque_definition_from_instantiation(opaque_type_key, concrete_type); - - // Sometimes, when the hidden type is an inference variable, it can happen that - // the hidden type becomes the opaque type itself. In this case, this was an opaque - // usage of the opaque type and we can ignore it. This check is mirrored in typeck's - // writeback. - if !infcx.next_trait_solver() { - if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() - && alias_ty.def_id == opaque_type_key.def_id.to_def_id() - && alias_ty.args == opaque_type_key.args - { - continue; - } - } - - root_cx.add_concrete_opaque_type( - opaque_type_key.def_id, - OpaqueHiddenType { span: concrete_type.span, ty }, - ); - - // Check that all opaque types have the same region parameters if they have the same - // non-region parameters. This is necessary because within the new solver we perform - // various query operations modulo regions, and thus could unsoundly select some impls - // that don't hold. - if !ty.references_error() - && let Some((prev_decl_key, prev_span)) = decls_modulo_regions.insert( - infcx.tcx.erase_regions(opaque_type_key), - (opaque_type_key, concrete_type.span), - ) - && let Some((arg1, arg2)) = std::iter::zip( - prev_decl_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), - opaque_type_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), - ) - .find(|(arg1, arg2)| arg1 != arg2) - { - infcx.dcx().emit_err(LifetimeMismatchOpaqueParam { - arg: arg1, - prev: arg2, - span: prev_span, - prev_span: concrete_type.span, - }); - } - } - } - - /// Map the regions in the type to named regions. This is similar to what - /// `infer_opaque_types` does, but can infer any universal region, not only - /// ones from the args for the opaque type. It also doesn't double check - /// that the regions produced are in fact equal to the named region they are - /// replaced with. This is fine because this function is only to improve the - /// region names in error messages. - /// - /// This differs from `MirBorrowckCtxt::name_regions` since it is particularly - /// lax with mapping region vids that are *shorter* than a universal region to - /// that universal region. This is useful for member region constraints since - /// we want to suggest a universal region name to capture even if it's technically - /// not equal to the error region. - pub(crate) fn name_regions_for_member_constraint(&self, tcx: TyCtxt<'tcx>, ty: T) -> T - where - T: TypeFoldable>, - { - fold_regions(tcx, ty, |region, _| match region.kind() { - ty::ReVar(vid) => { - let scc = self.constraint_sccs.scc(vid); - - // Special handling of higher-ranked regions. - if !self.max_nameable_universe(scc).is_root() { - match self.scc_values.placeholders_contained_in(scc).enumerate().last() { - // If the region contains a single placeholder then they're equal. - Some((0, placeholder)) => { - return ty::Region::new_placeholder(tcx, placeholder); - } - - // Fallback: this will produce a cryptic error message. - _ => return region, - } - } - - // Find something that we can name - let upper_bound = self.approx_universal_upper_bound(vid); - if let Some(universal_region) = self.definitions[upper_bound].external_name { - return universal_region; - } - - // Nothing exact found, so we pick a named upper bound, if there's only one. - // If there's >1 universal region, then we probably are dealing w/ an intersection - // region which cannot be mapped back to a universal. - // FIXME: We could probably compute the LUB if there is one. - let scc = self.constraint_sccs.scc(vid); - let upper_bounds: Vec<_> = self - .reverse_scc_graph() - .upper_bounds(scc) - .filter_map(|vid| self.definitions[vid].external_name) - .filter(|r| !r.is_static()) - .collect(); - match &upper_bounds[..] { - [universal_region] => *universal_region, - _ => region, - } - } - _ => region, - }) - } -} - -#[extension(pub trait InferCtxtExt<'tcx>)] -impl<'tcx> InferCtxt<'tcx> { - /// Given the fully resolved, instantiated type for an opaque - /// type, i.e., the value of an inference variable like C1 or C2 - /// (*), computes the "definition type" for an opaque type - /// definition -- that is, the inferred value of `Foo1<'x>` or - /// `Foo2<'x>` that we would conceptually use in its definition: - /// ```ignore (illustrative) - /// type Foo1<'x> = impl Bar<'x> = AAA; // <-- this type AAA - /// type Foo2<'x> = impl Bar<'x> = BBB; // <-- or this type BBB - /// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. } - /// ``` - /// Note that these values are defined in terms of a distinct set of - /// generic parameters (`'x` instead of `'a`) from C1 or C2. The main - /// purpose of this function is to do that translation. - /// - /// (*) C1 and C2 were introduced in the comments on - /// `register_member_constraints`. Read that comment for more context. - /// - /// # Parameters - /// - /// - `def_id`, the `impl Trait` type - /// - `args`, the args used to instantiate this opaque type - /// - `instantiated_ty`, the inferred type C1 -- fully resolved, lifted version of - /// `opaque_defn.concrete_ty` - #[instrument(level = "debug", skip(self))] - fn infer_opaque_definition_from_instantiation( - &self, - opaque_type_key: OpaqueTypeKey<'tcx>, - instantiated_ty: OpaqueHiddenType<'tcx>, - ) -> Ty<'tcx> { - if let Some(e) = self.tainted_by_errors() { - return Ty::new_error(self.tcx, e); - } - - if let Err(err) = check_opaque_type_parameter_valid( - self, - opaque_type_key, - instantiated_ty.span, - DefiningScopeKind::MirBorrowck, - ) { - return Ty::new_error(self.tcx, err.report(self)); - } - - let definition_ty = instantiated_ty - .remap_generic_params_to_declaration_params( - opaque_type_key, - self.tcx, - DefiningScopeKind::MirBorrowck, - ) - .ty; - - if let Err(e) = definition_ty.error_reported() { - return Ty::new_error(self.tcx, e); - } - - definition_ty - } -} diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs new file mode 100644 index 000000000000..667fc440ac00 --- /dev/null +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/member_constraints.rs @@ -0,0 +1,194 @@ +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::def_id::DefId; +use rustc_middle::bug; +use rustc_middle::ty::{ + self, GenericArgsRef, Region, RegionVid, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitor, +}; +use tracing::{debug, instrument}; + +use super::DefiningUse; +use super::region_ctxt::RegionCtxt; +use crate::constraints::ConstraintSccIndex; + +pub(super) fn apply_member_constraints<'tcx>( + rcx: &mut RegionCtxt<'_, 'tcx>, + defining_uses: &[DefiningUse<'tcx>], +) { + // Start by collecting the member constraints of all defining uses. + // + // Applying member constraints can influence other member constraints, + // so we first collect and then apply them. + let mut member_constraints = Default::default(); + for defining_use in defining_uses { + let mut visitor = CollectMemberConstraintsVisitor { + rcx, + defining_use, + member_constraints: &mut member_constraints, + }; + defining_use.hidden_type.ty.visit_with(&mut visitor); + } + + // Now walk over the region graph, visiting the smallest regions first and then all + // regions which have to outlive that one. + // + // Whenever we encounter a member region, we mutate the value of this SCC. This is + // as if we'd introduce new outlives constraints. However, we discard these region + // values after we've inferred the hidden types of opaques and apply the region + // constraints by simply equating the actual hidden type with the inferred one. + debug!(?member_constraints); + for scc_a in rcx.constraint_sccs.all_sccs() { + debug!(?scc_a); + // Start by adding the region values required by outlives constraints. This + // matches how we compute the final region values in `fn compute_regions`. + // + // We need to do this here to get a lower bound when applying member constraints. + // This propagates the region values added by previous member constraints. + for &scc_b in rcx.constraint_sccs.successors(scc_a) { + debug!(?scc_b); + rcx.scc_values.add_region(scc_a, scc_b); + } + + for defining_use in member_constraints.get(&scc_a).into_iter().flatten() { + apply_member_constraint(rcx, scc_a, &defining_use.arg_regions); + } + } +} + +#[instrument(level = "debug", skip(rcx))] +fn apply_member_constraint<'tcx>( + rcx: &mut RegionCtxt<'_, 'tcx>, + member: ConstraintSccIndex, + arg_regions: &[RegionVid], +) { + // If the member region lives in a higher universe, we currently choose + // the most conservative option by leaving it unchanged. + if !rcx.max_placeholder_universe_reached(member).is_root() { + return; + } + + // The existing value of `'member` is a lower-bound. If its is already larger than + // some universal region, we cannot equate it with that region. Said differently, we + // ignore choice regions which are smaller than this member region. + let mut choice_regions = arg_regions + .iter() + .copied() + .map(|r| rcx.representative(r).rvid()) + .filter(|&choice_region| { + rcx.scc_values.universal_regions_outlived_by(member).all(|lower_bound| { + rcx.universal_region_relations.outlives(choice_region, lower_bound) + }) + }) + .collect::>(); + debug!(?choice_regions, "after enforcing lower-bound"); + + // Now find all the *upper bounds* -- that is, each UB is a + // free region that must outlive the member region `R0` (`UB: + // R0`). Therefore, we need only keep an option `O` if `UB: O` + // for all UB. + // + // If we have a requirement `'upper_bound: 'member`, equating `'member` + // with some region `'choice` means we now also require `'upper_bound: 'choice`. + // Avoid choice regions for which this does not hold. + for ub in rcx.rev_scc_graph.upper_bounds(member) { + choice_regions + .retain(|&choice_region| rcx.universal_region_relations.outlives(ub, choice_region)); + } + debug!(?choice_regions, "after enforcing upper-bound"); + + // At this point we can pick any member of `choice_regions` and would like to choose + // it to be a small as possible. To avoid potential non-determinism we will pick the + // smallest such choice. + // + // Because universal regions are only partially ordered (i.e, not every two regions are + // comparable), we will ignore any region that doesn't compare to all others when picking + // the minimum choice. + // + // For example, consider `choice_regions = ['static, 'a, 'b, 'c, 'd, 'e]`, where + // `'static: 'a, 'static: 'b, 'a: 'c, 'b: 'c, 'c: 'd, 'c: 'e`. + // `['d, 'e]` are ignored because they do not compare - the same goes for `['a, 'b]`. + let totally_ordered_subset = choice_regions.iter().copied().filter(|&r1| { + choice_regions.iter().all(|&r2| { + rcx.universal_region_relations.outlives(r1, r2) + || rcx.universal_region_relations.outlives(r2, r1) + }) + }); + // Now we're left with `['static, 'c]`. Pick `'c` as the minimum! + let Some(min_choice) = totally_ordered_subset.reduce(|r1, r2| { + let r1_outlives_r2 = rcx.universal_region_relations.outlives(r1, r2); + let r2_outlives_r1 = rcx.universal_region_relations.outlives(r2, r1); + match (r1_outlives_r2, r2_outlives_r1) { + (true, true) => r1.min(r2), + (true, false) => r2, + (false, true) => r1, + (false, false) => bug!("incomparable regions in total order"), + } + }) else { + debug!("no unique minimum choice"); + return; + }; + + debug!(?min_choice); + // Lift the member region to be at least as large as this `min_choice` by directly + // mutating the `scc_values` as we compute it. This acts as if we've added a + // `'member: 'min_choice` while not recomputing sccs. This means different sccs + // may now actually be equal. + let min_choice_scc = rcx.constraint_sccs.scc(min_choice); + rcx.scc_values.add_region(member, min_choice_scc); +} + +struct CollectMemberConstraintsVisitor<'a, 'b, 'tcx> { + rcx: &'a RegionCtxt<'a, 'tcx>, + defining_use: &'b DefiningUse<'tcx>, + member_constraints: &'a mut FxHashMap>>, +} +impl<'tcx> CollectMemberConstraintsVisitor<'_, '_, 'tcx> { + fn cx(&self) -> TyCtxt<'tcx> { + self.rcx.infcx.tcx + } + fn visit_closure_args(&mut self, def_id: DefId, args: GenericArgsRef<'tcx>) { + let generics = self.cx().generics_of(def_id); + for arg in args.iter().skip(generics.parent_count) { + arg.visit_with(self); + } + } +} +impl<'tcx> TypeVisitor> for CollectMemberConstraintsVisitor<'_, '_, 'tcx> { + fn visit_region(&mut self, r: Region<'tcx>) { + match r.kind() { + ty::ReBound(..) => return, + ty::ReVar(vid) => { + let scc = self.rcx.constraint_sccs.scc(vid); + self.member_constraints.entry(scc).or_default().push(self.defining_use); + } + _ => unreachable!(), + } + } + + fn visit_ty(&mut self, ty: Ty<'tcx>) { + if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { + return; + } + + match *ty.kind() { + ty::Closure(def_id, args) + | ty::CoroutineClosure(def_id, args) + | ty::Coroutine(def_id, args) => self.visit_closure_args(def_id, args), + + ty::Alias(kind, ty::AliasTy { def_id, args, .. }) + if let Some(variances) = self.cx().opt_alias_variances(kind, def_id) => + { + // Skip lifetime parameters that are not captured, since they do + // not need member constraints registered for them; we'll erase + // them (and hopefully in the future replace them with placeholders). + for (&v, arg) in std::iter::zip(variances, args.iter()) { + if v != ty::Bivariant { + arg.visit_with(self) + } + } + } + + _ => ty.super_visit_with(self), + } + } +} diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs new file mode 100644 index 000000000000..33c4879af980 --- /dev/null +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -0,0 +1,698 @@ +use std::iter; +use std::rc::Rc; + +use rustc_data_structures::frozen::Frozen; +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::outlives::env::RegionBoundPairs; +use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, OpaqueTypeStorageEntries}; +use rustc_infer::traits::ObligationCause; +use rustc_macros::extension; +use rustc_middle::mir::{Body, ConstraintCategory}; +use rustc_middle::ty::{ + self, DefiningScopeKind, FallibleTypeFolder, GenericArg, GenericArgsRef, OpaqueHiddenType, + OpaqueTypeKey, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, + TypeVisitableExt, fold_regions, +}; +use rustc_mir_dataflow::points::DenseLocationMap; +use rustc_span::Span; +use rustc_trait_selection::opaque_types::{ + InvalidOpaqueTypeArgs, check_opaque_type_parameter_valid, +}; +use rustc_trait_selection::solve::NoSolution; +use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp; +use tracing::{debug, instrument}; + +use super::reverse_sccs::ReverseSccGraph; +use crate::consumers::RegionInferenceContext; +use crate::session_diagnostics::LifetimeMismatchOpaqueParam; +use crate::type_check::canonical::fully_perform_op_raw; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::type_check::{Locations, MirTypeckRegionConstraints}; +use crate::universal_regions::{RegionClassification, UniversalRegions}; +use crate::{BorrowCheckRootCtxt, BorrowckInferCtxt}; + +mod member_constraints; +mod region_ctxt; + +use member_constraints::apply_member_constraints; +use region_ctxt::RegionCtxt; + +/// We defer errors from [fn handle_opaque_type_uses] and only report them +/// if there are no `RegionErrors`. If there are region errors, it's likely +/// that errors here are caused by them and don't need to be handled separately. +pub(crate) enum DeferredOpaqueTypeError<'tcx> { + InvalidOpaqueTypeArgs(InvalidOpaqueTypeArgs<'tcx>), + LifetimeMismatchOpaqueParam(LifetimeMismatchOpaqueParam<'tcx>), + UnexpectedHiddenRegion { + /// The opaque type. + opaque_type_key: OpaqueTypeKey<'tcx>, + /// The hidden type containing the member region. + hidden_type: OpaqueHiddenType<'tcx>, + /// The unexpected region. + member_region: Region<'tcx>, + }, + NonDefiningUseInDefiningScope { + span: Span, + opaque_type_key: OpaqueTypeKey<'tcx>, + }, +} + +/// This looks at all uses of opaque types in their defining scope inside +/// of this function. +/// +/// It first uses all defining uses to compute the actual concrete type of each +/// opaque type definition. +/// +/// We then apply this inferred type to actually check all uses of the opaque. +pub(crate) fn handle_opaque_type_uses<'tcx>( + root_cx: &mut BorrowCheckRootCtxt<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, + body: &Body<'tcx>, + universal_region_relations: &Frozen>, + region_bound_pairs: &RegionBoundPairs<'tcx>, + known_type_outlives_obligations: &[ty::PolyTypeOutlivesPredicate<'tcx>], + location_map: &Rc, + constraints: &mut MirTypeckRegionConstraints<'tcx>, +) -> Vec> { + let tcx = infcx.tcx; + let opaque_types = infcx.clone_opaque_types(); + if opaque_types.is_empty() { + return Vec::new(); + } + + // We need to eagerly map all regions to NLL vars here, as we need to make sure we've + // introduced nll vars for all used placeholders. + // + // We need to resolve inference vars as even though we're in MIR typeck, we may still + // encounter inference variables, e.g. when checking user types. + let opaque_types_storage_num_entries = infcx.inner.borrow_mut().opaque_types().num_entries(); + let opaque_types = opaque_types + .into_iter() + .map(|entry| { + fold_regions(tcx, infcx.resolve_vars_if_possible(entry), |r, _| { + let vid = if let ty::RePlaceholder(placeholder) = r.kind() { + constraints.placeholder_region(infcx, placeholder).as_var() + } else { + universal_region_relations.universal_regions.to_region_vid(r) + }; + Region::new_var(tcx, vid) + }) + }) + .collect::>(); + + debug!(?opaque_types); + + let errors = compute_concrete_opaque_types( + root_cx, + infcx, + constraints, + universal_region_relations, + Rc::clone(location_map), + &opaque_types, + ); + + if !errors.is_empty() { + return errors; + } + + let errors = apply_computed_concrete_opaque_types( + root_cx, + infcx, + body, + &universal_region_relations.universal_regions, + region_bound_pairs, + known_type_outlives_obligations, + constraints, + &opaque_types, + ); + + detect_opaque_types_added_while_handling_opaque_types(infcx, opaque_types_storage_num_entries); + + errors +} + +/// Maps an NLL var to a deterministically chosen equal universal region. +/// +/// See the corresponding [rustc-dev-guide chapter] for more details. This +/// ignores changes to the region values due to member constraints. Applying +/// member constraints does not impact the result of this function. +/// +/// [rustc-dev-guide chapter]: https://rustc-dev-guide.rust-lang.org/borrow_check/opaque-types-region-inference-restrictions.html +fn nll_var_to_universal_region<'tcx>( + rcx: &RegionCtxt<'_, 'tcx>, + r: RegionVid, +) -> Option> { + // Use the SCC representative instead of directly using `region`. + // See [rustc-dev-guide chapter] § "Strict lifetime equality". + let vid = rcx.representative(r).rvid(); + match rcx.definitions[vid].origin { + // Iterate over all universal regions in a consistent order and find the + // *first* equal region. This makes sure that equal lifetimes will have + // the same name and simplifies subsequent handling. + // See [rustc-dev-guide chapter] § "Semantic lifetime equality". + NllRegionVariableOrigin::FreeRegion => rcx + .universal_regions() + .universal_regions_iter() + .filter(|&ur| { + // See [rustc-dev-guide chapter] § "Closure restrictions". + !matches!( + rcx.universal_regions().region_classification(ur), + Some(RegionClassification::External) + ) + }) + .find(|&ur| rcx.universal_region_relations.equal(vid, ur)) + .map(|ur| rcx.definitions[ur].external_name.unwrap()), + NllRegionVariableOrigin::Placeholder(placeholder) => { + Some(ty::Region::new_placeholder(rcx.infcx.tcx, placeholder)) + } + // If `r` were equal to any universal region, its SCC representative + // would have been set to a free region. + NllRegionVariableOrigin::Existential { .. } => None, + } +} + +#[derive(Debug)] +struct DefiningUse<'tcx> { + /// The opaque type using non NLL vars. This uses the actual + /// free regions and placeholders. This is necessary + /// to interact with code outside of `rustc_borrowck`. + opaque_type_key: OpaqueTypeKey<'tcx>, + arg_regions: Vec, + hidden_type: OpaqueHiddenType<'tcx>, +} + +/// This computes the actual hidden types of the opaque types and maps them to their +/// definition sites. Outside of registering the computed concrete types this function +/// does not mutate the current borrowck state. +/// +/// While it may fail to infer the hidden type and return errors, we always apply +/// the computed concrete hidden type to all opaque type uses to check whether they +/// are correct. This is necessary to support non-defining uses of opaques in their +/// defining scope. +/// +/// It also means that this whole function is not really soundness critical as we +/// recheck all uses of the opaques regardless. +fn compute_concrete_opaque_types<'tcx>( + root_cx: &mut BorrowCheckRootCtxt<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, + constraints: &MirTypeckRegionConstraints<'tcx>, + universal_region_relations: &Frozen>, + location_map: Rc, + opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], +) -> Vec> { + let mut errors = Vec::new(); + // When computing the hidden type we need to track member constraints. + // We don't mutate the region graph used by `fn compute_regions` but instead + // manually track region information via a `RegionCtxt`. We discard this + // information at the end of this function. + let mut rcx = RegionCtxt::new(infcx, universal_region_relations, location_map, constraints); + + // We start by checking each use of an opaque type during type check and + // check whether the generic arguments of the opaque type are fully + // universal, if so, it's a defining use. + let defining_uses = collect_defining_uses(root_cx, &mut rcx, opaque_types, &mut errors); + + // We now compute and apply member constraints for all regions in the hidden + // types of each defining use. This mutates the region values of the `rcx` which + // is used when mapping the defining uses to the definition site. + apply_member_constraints(&mut rcx, &defining_uses); + + // After applying member constraints, we now check whether all member regions ended + // up equal to one of their choice regions and compute the actual concrete type of + // the opaque type definition. This is stored in the `root_cx`. + compute_concrete_types_from_defining_uses(root_cx, &rcx, &defining_uses, &mut errors); + errors +} + +#[instrument(level = "debug", skip_all, ret)] +fn collect_defining_uses<'tcx>( + root_cx: &mut BorrowCheckRootCtxt<'tcx>, + rcx: &mut RegionCtxt<'_, 'tcx>, + opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], + errors: &mut Vec>, +) -> Vec> { + let infcx = rcx.infcx; + let mut defining_uses = vec![]; + for &(opaque_type_key, hidden_type) in opaque_types { + let non_nll_opaque_type_key = opaque_type_key.fold_captured_lifetime_args(infcx.tcx, |r| { + nll_var_to_universal_region(&rcx, r.as_var()).unwrap_or(r) + }); + if let Err(err) = check_opaque_type_parameter_valid( + infcx, + non_nll_opaque_type_key, + hidden_type.span, + DefiningScopeKind::MirBorrowck, + ) { + // A non-defining use. This is a hard error on stable and gets ignored + // with `TypingMode::Borrowck`. + if infcx.tcx.use_typing_mode_borrowck() { + match err { + InvalidOpaqueTypeArgs::AlreadyReported(guar) => root_cx + .add_concrete_opaque_type( + opaque_type_key.def_id, + OpaqueHiddenType::new_error(infcx.tcx, guar), + ), + _ => debug!(?non_nll_opaque_type_key, ?err, "ignoring non-defining use"), + } + } else { + errors.push(DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err)); + } + continue; + } + + // We use the original `opaque_type_key` to compute the `arg_regions`. + let arg_regions = iter::once(rcx.universal_regions().fr_static) + .chain( + opaque_type_key + .iter_captured_args(infcx.tcx) + .filter_map(|(_, arg)| arg.as_region()) + .map(Region::as_var), + ) + .collect(); + defining_uses.push(DefiningUse { + opaque_type_key: non_nll_opaque_type_key, + arg_regions, + hidden_type, + }); + } + + defining_uses +} + +fn compute_concrete_types_from_defining_uses<'tcx>( + root_cx: &mut BorrowCheckRootCtxt<'tcx>, + rcx: &RegionCtxt<'_, 'tcx>, + defining_uses: &[DefiningUse<'tcx>], + errors: &mut Vec>, +) { + let infcx = rcx.infcx; + let tcx = infcx.tcx; + let mut decls_modulo_regions: FxIndexMap, (OpaqueTypeKey<'tcx>, Span)> = + FxIndexMap::default(); + for &DefiningUse { opaque_type_key, ref arg_regions, hidden_type } in defining_uses { + // After applying member constraints, we now map all regions in the hidden type + // to the `arg_regions` of this defining use. In case a region in the hidden type + // ended up not being equal to any such region, we error. + let hidden_type = + match hidden_type.try_fold_with(&mut ToArgRegionsFolder::new(rcx, arg_regions)) { + Ok(hidden_type) => hidden_type, + Err(r) => { + errors.push(DeferredOpaqueTypeError::UnexpectedHiddenRegion { + hidden_type, + opaque_type_key, + member_region: ty::Region::new_var(tcx, r), + }); + let guar = tcx.dcx().span_delayed_bug( + hidden_type.span, + "opaque type with non-universal region args", + ); + ty::OpaqueHiddenType::new_error(tcx, guar) + } + }; + + // Now that we mapped the member regions to their final value, + // map the arguments of the opaque type key back to the parameters + // of the opaque type definition. + let ty = infcx + .infer_opaque_definition_from_instantiation(opaque_type_key, hidden_type) + .unwrap_or_else(|_| { + Ty::new_error_with_message( + rcx.infcx.tcx, + hidden_type.span, + "deferred invalid opaque type args", + ) + }); + + // Sometimes, when the hidden type is an inference variable, it can happen that + // the hidden type becomes the opaque type itself. In this case, this was an opaque + // usage of the opaque type and we can ignore it. This check is mirrored in typeck's + // writeback. + if !rcx.infcx.tcx.use_typing_mode_borrowck() { + if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() + && alias_ty.def_id == opaque_type_key.def_id.to_def_id() + && alias_ty.args == opaque_type_key.args + { + continue; + } + } + + // Check that all opaque types have the same region parameters if they have the same + // non-region parameters. This is necessary because within the new solver we perform + // various query operations modulo regions, and thus could unsoundly select some impls + // that don't hold. + // + // FIXME(-Znext-solver): This isn't necessary after all. We can remove this check again. + if let Some((prev_decl_key, prev_span)) = decls_modulo_regions.insert( + rcx.infcx.tcx.erase_regions(opaque_type_key), + (opaque_type_key, hidden_type.span), + ) && let Some((arg1, arg2)) = std::iter::zip( + prev_decl_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), + opaque_type_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), + ) + .find(|(arg1, arg2)| arg1 != arg2) + { + errors.push(DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam( + LifetimeMismatchOpaqueParam { + arg: arg1, + prev: arg2, + span: prev_span, + prev_span: hidden_type.span, + }, + )); + } + root_cx.add_concrete_opaque_type( + opaque_type_key.def_id, + OpaqueHiddenType { span: hidden_type.span, ty }, + ); + } +} + +/// A folder to map the regions in the hidden type to their corresponding `arg_regions`. +/// +/// This folder has to differentiate between member regions and other regions in the hidden +/// type. Member regions have to be equal to one of the `arg_regions` while other regions simply +/// get treated as an existential region in the opaque if they are not. Existential +/// regions are currently represented using `'erased`. +struct ToArgRegionsFolder<'a, 'tcx> { + rcx: &'a RegionCtxt<'a, 'tcx>, + // When folding closure args or bivariant alias arguments, we simply + // ignore non-member regions. However, we still need to map member + // regions to their arg region even if its in a closure argument. + // + // See tests/ui/type-alias-impl-trait/closure_wf_outlives.rs for an example. + erase_unknown_regions: bool, + arg_regions: &'a [RegionVid], +} + +impl<'a, 'tcx> ToArgRegionsFolder<'a, 'tcx> { + fn new( + rcx: &'a RegionCtxt<'a, 'tcx>, + arg_regions: &'a [RegionVid], + ) -> ToArgRegionsFolder<'a, 'tcx> { + ToArgRegionsFolder { rcx, erase_unknown_regions: false, arg_regions } + } + + fn fold_non_member_arg(&mut self, arg: GenericArg<'tcx>) -> GenericArg<'tcx> { + let prev = self.erase_unknown_regions; + self.erase_unknown_regions = true; + let res = arg.try_fold_with(self).unwrap(); + self.erase_unknown_regions = prev; + res + } + + fn fold_closure_args( + &mut self, + def_id: DefId, + args: GenericArgsRef<'tcx>, + ) -> Result, RegionVid> { + let generics = self.cx().generics_of(def_id); + self.cx().mk_args_from_iter(args.iter().enumerate().map(|(index, arg)| { + if index < generics.parent_count { + Ok(self.fold_non_member_arg(arg)) + } else { + arg.try_fold_with(self) + } + })) + } +} +impl<'tcx> FallibleTypeFolder> for ToArgRegionsFolder<'_, 'tcx> { + type Error = RegionVid; + fn cx(&self) -> TyCtxt<'tcx> { + self.rcx.infcx.tcx + } + + fn try_fold_region(&mut self, r: Region<'tcx>) -> Result, RegionVid> { + match r.kind() { + // ignore bound regions, keep visiting + ty::ReBound(_, _) => Ok(r), + _ => { + let r = r.as_var(); + if let Some(arg_region) = self + .arg_regions + .iter() + .copied() + .find(|&arg_vid| self.rcx.eval_equal(r, arg_vid)) + .and_then(|r| nll_var_to_universal_region(self.rcx, r)) + { + Ok(arg_region) + } else if self.erase_unknown_regions { + Ok(self.cx().lifetimes.re_erased) + } else { + Err(r) + } + } + } + } + + fn try_fold_ty(&mut self, ty: Ty<'tcx>) -> Result, RegionVid> { + if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { + return Ok(ty); + } + + let tcx = self.cx(); + Ok(match *ty.kind() { + ty::Closure(def_id, args) => { + Ty::new_closure(tcx, def_id, self.fold_closure_args(def_id, args)?) + } + + ty::CoroutineClosure(def_id, args) => { + Ty::new_coroutine_closure(tcx, def_id, self.fold_closure_args(def_id, args)?) + } + + ty::Coroutine(def_id, args) => { + Ty::new_coroutine(tcx, def_id, self.fold_closure_args(def_id, args)?) + } + + ty::Alias(kind, ty::AliasTy { def_id, args, .. }) + if let Some(variances) = tcx.opt_alias_variances(kind, def_id) => + { + let args = tcx.mk_args_from_iter(std::iter::zip(variances, args.iter()).map( + |(&v, s)| { + if v == ty::Bivariant { + Ok(self.fold_non_member_arg(s)) + } else { + s.try_fold_with(self) + } + }, + ))?; + ty::AliasTy::new_from_args(tcx, def_id, args).to_ty(tcx) + } + + _ => ty.try_super_fold_with(self)?, + }) + } +} + +/// This function is what actually applies member constraints to the borrowck +/// state. It is also responsible to check all uses of the opaques in their +/// defining scope. +/// +/// It does this by equating the hidden type of each use with the instantiated final +/// hidden type of the opaque. +fn apply_computed_concrete_opaque_types<'tcx>( + root_cx: &mut BorrowCheckRootCtxt<'tcx>, + infcx: &BorrowckInferCtxt<'tcx>, + body: &Body<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + region_bound_pairs: &RegionBoundPairs<'tcx>, + known_type_outlives_obligations: &[ty::PolyTypeOutlivesPredicate<'tcx>], + constraints: &mut MirTypeckRegionConstraints<'tcx>, + opaque_types: &[(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)], +) -> Vec> { + let tcx = infcx.tcx; + let mut errors = Vec::new(); + for &(key, hidden_type) in opaque_types { + let Some(expected) = root_cx.get_concrete_opaque_type(key.def_id) else { + assert!(tcx.use_typing_mode_borrowck(), "non-defining use in defining scope"); + errors.push(DeferredOpaqueTypeError::NonDefiningUseInDefiningScope { + span: hidden_type.span, + opaque_type_key: key, + }); + let guar = tcx.dcx().span_delayed_bug( + hidden_type.span, + "non-defining use in the defining scope with no defining uses", + ); + root_cx.add_concrete_opaque_type(key.def_id, OpaqueHiddenType::new_error(tcx, guar)); + continue; + }; + + // We erase all non-member region of the opaque and need to treat these as existentials. + let expected = ty::fold_regions(tcx, expected.instantiate(tcx, key.args), |re, _dbi| { + match re.kind() { + ty::ReErased => infcx.next_nll_region_var( + NllRegionVariableOrigin::Existential { name: None }, + || crate::RegionCtxt::Existential(None), + ), + _ => re, + } + }); + + // We now simply equate the expected with the actual hidden type. + let locations = Locations::All(hidden_type.span); + if let Err(guar) = fully_perform_op_raw( + infcx, + body, + universal_regions, + region_bound_pairs, + known_type_outlives_obligations, + constraints, + locations, + ConstraintCategory::OpaqueType, + CustomTypeOp::new( + |ocx| { + let cause = ObligationCause::misc( + hidden_type.span, + body.source.def_id().expect_local(), + ); + // We need to normalize both types in the old solver before equatingt them. + let actual_ty = ocx.normalize(&cause, infcx.param_env, hidden_type.ty); + let expected_ty = ocx.normalize(&cause, infcx.param_env, expected.ty); + ocx.eq(&cause, infcx.param_env, actual_ty, expected_ty).map_err(|_| NoSolution) + }, + "equating opaque types", + ), + ) { + root_cx.add_concrete_opaque_type(key.def_id, OpaqueHiddenType::new_error(tcx, guar)); + } + } + errors +} + +/// In theory `apply_concrete_opaque_types` could introduce new uses of opaque types. +/// We do not check these new uses so this could be unsound. +/// +/// We detect any new uses and simply delay a bug if they occur. If this results in +/// an ICE we can properly handle this, but we haven't encountered any such test yet. +/// +/// See the related comment in `FnCtxt::detect_opaque_types_added_during_writeback`. +fn detect_opaque_types_added_while_handling_opaque_types<'tcx>( + infcx: &InferCtxt<'tcx>, + opaque_types_storage_num_entries: OpaqueTypeStorageEntries, +) { + for (key, hidden_type) in infcx + .inner + .borrow_mut() + .opaque_types() + .opaque_types_added_since(opaque_types_storage_num_entries) + { + let opaque_type_string = infcx.tcx.def_path_str(key.def_id); + let msg = format!("unexpected cyclic definition of `{opaque_type_string}`"); + infcx.dcx().span_delayed_bug(hidden_type.span, msg); + } + + let _ = infcx.take_opaque_types(); +} + +impl<'tcx> RegionInferenceContext<'tcx> { + /// Map the regions in the type to named regions. This is similar to what + /// `infer_opaque_types` does, but can infer any universal region, not only + /// ones from the args for the opaque type. It also doesn't double check + /// that the regions produced are in fact equal to the named region they are + /// replaced with. This is fine because this function is only to improve the + /// region names in error messages. + /// + /// This differs from `MirBorrowckCtxt::name_regions` since it is particularly + /// lax with mapping region vids that are *shorter* than a universal region to + /// that universal region. This is useful for member region constraints since + /// we want to suggest a universal region name to capture even if it's technically + /// not equal to the error region. + pub(crate) fn name_regions_for_member_constraint(&self, tcx: TyCtxt<'tcx>, ty: T) -> T + where + T: TypeFoldable>, + { + fold_regions(tcx, ty, |region, _| match region.kind() { + ty::ReVar(vid) => { + let scc = self.constraint_sccs.scc(vid); + + // Special handling of higher-ranked regions. + if !self.max_nameable_universe(scc).is_root() { + match self.scc_values.placeholders_contained_in(scc).enumerate().last() { + // If the region contains a single placeholder then they're equal. + Some((0, placeholder)) => { + return ty::Region::new_placeholder(tcx, placeholder); + } + + // Fallback: this will produce a cryptic error message. + _ => return region, + } + } + + // Find something that we can name + let upper_bound = self.approx_universal_upper_bound(vid); + if let Some(universal_region) = self.definitions[upper_bound].external_name { + return universal_region; + } + + // Nothing exact found, so we pick a named upper bound, if there's only one. + // If there's >1 universal region, then we probably are dealing w/ an intersection + // region which cannot be mapped back to a universal. + // FIXME: We could probably compute the LUB if there is one. + let scc = self.constraint_sccs.scc(vid); + let rev_scc_graph = + ReverseSccGraph::compute(&self.constraint_sccs, self.universal_regions()); + let upper_bounds: Vec<_> = rev_scc_graph + .upper_bounds(scc) + .filter_map(|vid| self.definitions[vid].external_name) + .filter(|r| !r.is_static()) + .collect(); + match &upper_bounds[..] { + [universal_region] => *universal_region, + _ => region, + } + } + _ => region, + }) + } +} + +#[extension(pub trait InferCtxtExt<'tcx>)] +impl<'tcx> InferCtxt<'tcx> { + /// Given the fully resolved, instantiated type for an opaque + /// type, i.e., the value of an inference variable like C1 or C2 + /// (*), computes the "definition type" for an opaque type + /// definition -- that is, the inferred value of `Foo1<'x>` or + /// `Foo2<'x>` that we would conceptually use in its definition: + /// ```ignore (illustrative) + /// type Foo1<'x> = impl Bar<'x> = AAA; // <-- this type AAA + /// type Foo2<'x> = impl Bar<'x> = BBB; // <-- or this type BBB + /// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. } + /// ``` + /// Note that these values are defined in terms of a distinct set of + /// generic parameters (`'x` instead of `'a`) from C1 or C2. The main + /// purpose of this function is to do that translation. + /// + /// (*) C1 and C2 were introduced in the comments on + /// `register_member_constraints`. Read that comment for more context. + /// + /// # Parameters + /// + /// - `def_id`, the `impl Trait` type + /// - `args`, the args used to instantiate this opaque type + /// - `instantiated_ty`, the inferred type C1 -- fully resolved, lifted version of + /// `opaque_defn.concrete_ty` + #[instrument(level = "debug", skip(self))] + fn infer_opaque_definition_from_instantiation( + &self, + opaque_type_key: OpaqueTypeKey<'tcx>, + instantiated_ty: OpaqueHiddenType<'tcx>, + ) -> Result, InvalidOpaqueTypeArgs<'tcx>> { + check_opaque_type_parameter_valid( + self, + opaque_type_key, + instantiated_ty.span, + DefiningScopeKind::MirBorrowck, + )?; + + let definition_ty = instantiated_ty + .remap_generic_params_to_declaration_params( + opaque_type_key, + self.tcx, + DefiningScopeKind::MirBorrowck, + ) + .ty; + + definition_ty.error_reported()?; + Ok(definition_ty) + } +} diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs new file mode 100644 index 000000000000..88326e4eebfc --- /dev/null +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/region_ctxt.rs @@ -0,0 +1,114 @@ +use std::rc::Rc; + +use rustc_data_structures::frozen::Frozen; +use rustc_index::IndexVec; +use rustc_infer::infer::NllRegionVariableOrigin; +use rustc_middle::ty::{RegionVid, UniverseIndex}; +use rustc_mir_dataflow::points::DenseLocationMap; + +use crate::BorrowckInferCtxt; +use crate::constraints::ConstraintSccIndex; +use crate::handle_placeholders::{SccAnnotations, region_definitions}; +use crate::region_infer::reverse_sccs::ReverseSccGraph; +use crate::region_infer::values::RegionValues; +use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker, Representative}; +use crate::type_check::MirTypeckRegionConstraints; +use crate::type_check::free_region_relations::UniversalRegionRelations; +use crate::universal_regions::UniversalRegions; + +/// A slimmed down version of [crate::region_infer::RegionInferenceContext] used +/// only by opaque type handling. +pub(super) struct RegionCtxt<'a, 'tcx> { + pub(super) infcx: &'a BorrowckInferCtxt<'tcx>, + pub(super) definitions: Frozen>>, + pub(super) universal_region_relations: &'a UniversalRegionRelations<'tcx>, + pub(super) constraint_sccs: ConstraintSccs, + pub(super) scc_annotations: IndexVec, + pub(super) rev_scc_graph: ReverseSccGraph, + pub(super) scc_values: RegionValues, +} + +impl<'a, 'tcx> RegionCtxt<'a, 'tcx> { + /// Creates a new `RegionCtxt` used to compute defining opaque type uses. + /// + /// This does not yet propagate region values. This is instead done lazily + /// when applying member constraints. + pub(super) fn new( + infcx: &'a BorrowckInferCtxt<'tcx>, + universal_region_relations: &'a Frozen>, + location_map: Rc, + constraints: &MirTypeckRegionConstraints<'tcx>, + ) -> RegionCtxt<'a, 'tcx> { + let universal_regions = &universal_region_relations.universal_regions; + let (definitions, _has_placeholders) = region_definitions(infcx, universal_regions); + let mut scc_annotations = SccAnnotations::init(&definitions); + let constraint_sccs = ConstraintSccs::new_with_annotation( + &constraints + .outlives_constraints + .graph(definitions.len()) + .region_graph(&constraints.outlives_constraints, universal_regions.fr_static), + &mut scc_annotations, + ); + let scc_annotations = scc_annotations.scc_to_annotation; + + // Unlike the `RegionInferenceContext`, we only care about free regions + // and fully ignore liveness and placeholders. + let placeholder_indices = Default::default(); + let mut scc_values = + RegionValues::new(location_map, universal_regions.len(), placeholder_indices); + for variable in definitions.indices() { + let scc = constraint_sccs.scc(variable); + match definitions[variable].origin { + NllRegionVariableOrigin::FreeRegion => { + scc_values.add_element(scc, variable); + } + _ => {} + } + } + + let rev_scc_graph = ReverseSccGraph::compute(&constraint_sccs, universal_regions); + RegionCtxt { + infcx, + definitions, + universal_region_relations, + constraint_sccs, + scc_annotations, + rev_scc_graph, + scc_values, + } + } + + pub(super) fn representative(&self, vid: RegionVid) -> Representative { + let scc = self.constraint_sccs.scc(vid); + self.scc_annotations[scc].representative + } + + pub(crate) fn max_placeholder_universe_reached( + &self, + scc: ConstraintSccIndex, + ) -> UniverseIndex { + self.scc_annotations[scc].max_placeholder_universe_reached() + } + + pub(super) fn universal_regions(&self) -> &UniversalRegions<'tcx> { + &self.universal_region_relations.universal_regions + } + + pub(super) fn eval_equal(&self, r1_vid: RegionVid, r2_vid: RegionVid) -> bool { + let r1 = self.constraint_sccs.scc(r1_vid); + let r2 = self.constraint_sccs.scc(r2_vid); + + if r1 == r2 { + return true; + } + + let universal_outlives = |sub, sup| { + self.scc_values.universal_regions_outlived_by(sub).all(|r1| { + self.scc_values + .universal_regions_outlived_by(sup) + .any(|r2| self.universal_region_relations.outlives(r2, r1)) + }) + }; + universal_outlives(r1, r2) && universal_outlives(r2, r1) + } +} diff --git a/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs index 604265f89408..e8da85eccef1 100644 --- a/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs +++ b/compiler/rustc_borrowck/src/region_infer/reverse_sccs.rs @@ -5,7 +5,6 @@ use rustc_data_structures::graph; use rustc_data_structures::graph::vec_graph::VecGraph; use rustc_middle::ty::RegionVid; -use crate::RegionInferenceContext; use crate::constraints::ConstraintSccIndex; use crate::region_infer::ConstraintSccs; use crate::universal_regions::UniversalRegions; @@ -57,12 +56,3 @@ impl ReverseSccGraph { .filter(move |r| duplicates.insert(*r)) } } - -impl RegionInferenceContext<'_> { - /// Return the reverse graph of the region SCCs, initialising it if needed. - pub(super) fn reverse_scc_graph(&self) -> &ReverseSccGraph { - self.rev_scc_graph.get_or_init(|| { - ReverseSccGraph::compute(&self.constraint_sccs, self.universal_regions()) - }) - } -} diff --git a/compiler/rustc_borrowck/src/renumber.rs b/compiler/rustc_borrowck/src/renumber.rs index ff92b4168a86..fa3064afee81 100644 --- a/compiler/rustc_borrowck/src/renumber.rs +++ b/compiler/rustc_borrowck/src/renumber.rs @@ -66,7 +66,7 @@ impl<'a, 'tcx> RegionRenumberer<'a, 'tcx> { T: TypeFoldable>, F: Fn() -> RegionCtxt, { - let origin = NllRegionVariableOrigin::Existential { from_forall: false }; + let origin = NllRegionVariableOrigin::Existential { name: None }; fold_regions(self.infcx.tcx, value, |_region, _depth| { self.infcx.next_nll_region_var(origin, || region_ctxt_fn()) }) diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs index 9b1d12aede51..40c0448cf0ba 100644 --- a/compiler/rustc_borrowck/src/root_cx.rs +++ b/compiler/rustc_borrowck/src/root_cx.rs @@ -2,7 +2,7 @@ use rustc_abi::FieldIdx; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::LocalDefId; use rustc_middle::bug; -use rustc_middle::ty::{OpaqueHiddenType, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{EarlyBinder, OpaqueHiddenType, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::ErrorGuaranteed; use smallvec::SmallVec; @@ -19,7 +19,7 @@ pub(super) struct BorrowCheckRootCtxt<'tcx> { tainted_by_errors: Option, /// This should be `None` during normal compilation. See [`crate::consumers`] for more /// information on how this is used. - pub(crate) consumer: Option>, + pub consumer: Option>, } impl<'tcx> BorrowCheckRootCtxt<'tcx> { @@ -67,6 +67,13 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> { } } + pub(super) fn get_concrete_opaque_type( + &mut self, + def_id: LocalDefId, + ) -> Option>> { + self.concrete_opaque_types.0.get(&def_id).map(|ty| EarlyBinder::bind(*ty)) + } + pub(super) fn set_tainted_by_errors(&mut self, guar: ErrorGuaranteed) { self.tainted_by_errors = Some(guar); } diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index b3fa786a5177..a8a48248ffd8 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -2,8 +2,9 @@ use std::fmt; use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::canonical::Canonical; +use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_middle::bug; -use rustc_middle::mir::ConstraintCategory; +use rustc_middle::mir::{Body, ConstraintCategory}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, Upcast}; use rustc_span::Span; use rustc_span::def_id::DefId; @@ -14,7 +15,69 @@ use rustc_trait_selection::traits::query::type_op::{self, TypeOpOutput}; use tracing::{debug, instrument}; use super::{Locations, NormalizeLocation, TypeChecker}; +use crate::BorrowckInferCtxt; use crate::diagnostics::ToUniverseInfo; +use crate::type_check::{MirTypeckRegionConstraints, constraint_conversion}; +use crate::universal_regions::UniversalRegions; + +#[instrument(skip(infcx, constraints, op), level = "trace")] +pub(crate) fn fully_perform_op_raw<'tcx, R: fmt::Debug, Op>( + infcx: &BorrowckInferCtxt<'tcx>, + body: &Body<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + region_bound_pairs: &RegionBoundPairs<'tcx>, + known_type_outlives_obligations: &[ty::PolyTypeOutlivesPredicate<'tcx>], + constraints: &mut MirTypeckRegionConstraints<'tcx>, + locations: Locations, + category: ConstraintCategory<'tcx>, + op: Op, +) -> Result +where + Op: type_op::TypeOp<'tcx, Output = R>, + Op::ErrorInfo: ToUniverseInfo<'tcx>, +{ + let old_universe = infcx.universe(); + + let TypeOpOutput { output, constraints: query_constraints, error_info } = + op.fully_perform(infcx, locations.span(body))?; + if cfg!(debug_assertions) { + let data = infcx.take_and_reset_region_constraints(); + if !data.is_empty() { + panic!("leftover region constraints: {data:#?}"); + } + } + + debug!(?output, ?query_constraints); + + if let Some(data) = query_constraints { + constraint_conversion::ConstraintConversion::new( + infcx, + universal_regions, + region_bound_pairs, + infcx.param_env, + known_type_outlives_obligations, + locations, + locations.span(body), + category, + constraints, + ) + .convert_all(data); + } + + // If the query has created new universes and errors are going to be emitted, register the + // cause of these new universes for improved diagnostics. + let universe = infcx.universe(); + if old_universe != universe + && let Some(error_info) = error_info + { + let universe_info = error_info.to_universe_info(old_universe); + for u in (old_universe + 1)..=universe { + constraints.universe_causes.insert(u, universe_info.clone()); + } + } + + Ok(output) +} impl<'a, 'tcx> TypeChecker<'a, 'tcx> { /// Given some operation `op` that manipulates types, proves @@ -38,36 +101,17 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { Op: type_op::TypeOp<'tcx, Output = R>, Op::ErrorInfo: ToUniverseInfo<'tcx>, { - let old_universe = self.infcx.universe(); - - let TypeOpOutput { output, constraints, error_info } = - op.fully_perform(self.infcx, locations.span(self.body))?; - if cfg!(debug_assertions) { - let data = self.infcx.take_and_reset_region_constraints(); - if !data.is_empty() { - panic!("leftover region constraints: {data:#?}"); - } - } - - debug!(?output, ?constraints); - - if let Some(data) = constraints { - self.push_region_constraints(locations, category, data); - } - - // If the query has created new universes and errors are going to be emitted, register the - // cause of these new universes for improved diagnostics. - let universe = self.infcx.universe(); - if old_universe != universe - && let Some(error_info) = error_info - { - let universe_info = error_info.to_universe_info(old_universe); - for u in (old_universe + 1)..=universe { - self.constraints.universe_causes.insert(u, universe_info.clone()); - } - } - - Ok(output) + fully_perform_op_raw( + self.infcx, + self.body, + self.universal_regions, + self.region_bound_pairs, + self.known_type_outlives_obligations, + self.constraints, + locations, + category, + op, + ) } pub(super) fn instantiate_canonical( diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index f5fedbf95c1c..0e1dd5c701fe 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -26,8 +26,7 @@ use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::cast::CastTy; use rustc_middle::ty::{ self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, CoroutineArgsExt, - GenericArgsRef, OpaqueHiddenType, OpaqueTypeKey, RegionVid, Ty, TyCtxt, TypeVisitableExt, - UserArgs, UserTypeAnnotationIndex, fold_regions, + GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, UserArgs, UserTypeAnnotationIndex, fold_regions, }; use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::move_paths::MoveData; @@ -42,7 +41,6 @@ use tracing::{debug, instrument, trace}; use crate::borrow_set::BorrowSet; use crate::constraints::{OutlivesConstraint, OutlivesConstraintSet}; use crate::diagnostics::UniverseInfo; -use crate::member_constraints::MemberConstraintSet; use crate::polonius::legacy::{PoloniusFacts, PoloniusLocationTable}; use crate::polonius::{PoloniusContext, PoloniusLivenessContext}; use crate::region_infer::TypeTest; @@ -67,12 +65,11 @@ macro_rules! span_mirbug { }) } -mod canonical; +pub(crate) mod canonical; mod constraint_conversion; pub(crate) mod free_region_relations; mod input_output; pub(crate) mod liveness; -mod opaque_types; mod relate_tys; /// Type checks the given `mir` in the context of the inference @@ -114,7 +111,6 @@ pub(crate) fn type_check<'tcx>( placeholder_index_to_region: IndexVec::default(), liveness_constraints: LivenessValues::with_specific_points(Rc::clone(&location_map)), outlives_constraints: OutlivesConstraintSet::default(), - member_constraints: MemberConstraintSet::default(), type_tests: Vec::default(), universe_causes: FxIndexMap::default(), }; @@ -170,9 +166,6 @@ pub(crate) fn type_check<'tcx>( liveness::generate(&mut typeck, &location_map, move_data); - let opaque_type_values = - opaque_types::take_opaques_and_register_member_constraints(&mut typeck); - // We're done with typeck, we can finalize the polonius liveness context for region inference. let polonius_context = typeck.polonius_liveness.take().map(|liveness_context| { PoloniusContext::create_from_liveness( @@ -182,10 +175,21 @@ pub(crate) fn type_check<'tcx>( ) }); + // In case type check encountered an error region, we suppress unhelpful extra + // errors in by clearing out all outlives bounds that we may end up checking. + if let Some(guar) = universal_region_relations.universal_regions.encountered_re_error() { + debug!("encountered an error region; removing constraints!"); + constraints.outlives_constraints = Default::default(); + constraints.type_tests = Default::default(); + root_cx.set_tainted_by_errors(guar); + infcx.set_tainted_by_errors(guar); + } + MirTypeckResults { constraints, universal_region_relations, - opaque_type_values, + region_bound_pairs, + known_type_outlives_obligations, polonius_context, } } @@ -234,7 +238,8 @@ struct TypeChecker<'a, 'tcx> { pub(crate) struct MirTypeckResults<'tcx> { pub(crate) constraints: MirTypeckRegionConstraints<'tcx>, pub(crate) universal_region_relations: Frozen>, - pub(crate) opaque_type_values: FxIndexMap, OpaqueHiddenType<'tcx>>, + pub(crate) region_bound_pairs: Frozen>, + pub(crate) known_type_outlives_obligations: Frozen>>, pub(crate) polonius_context: Option, } @@ -266,8 +271,6 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> { pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>, - pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>, - pub(crate) universe_causes: FxIndexMap>, pub(crate) type_tests: Vec>, @@ -276,7 +279,7 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> { impl<'tcx> MirTypeckRegionConstraints<'tcx> { /// Creates a `Region` for a given `PlaceholderRegion`, or returns the /// region that corresponds to a previously created one. - fn placeholder_region( + pub(crate) fn placeholder_region( &mut self, infcx: &InferCtxt<'tcx>, placeholder: ty::PlaceholderRegion, @@ -369,14 +372,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self.body } - fn to_region_vid(&mut self, r: ty::Region<'tcx>) -> RegionVid { - if let ty::RePlaceholder(placeholder) = r.kind() { - self.constraints.placeholder_region(self.infcx, placeholder).as_var() - } else { - self.universal_regions.to_region_vid(r) - } - } - fn unsized_feature_enabled(&self) -> bool { self.tcx().features().unsized_fn_params() } @@ -769,9 +764,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } TerminatorKind::Call { func, args, .. } | TerminatorKind::TailCall { func, args, .. } => { - let call_source = match term.kind { - TerminatorKind::Call { call_source, .. } => call_source, - TerminatorKind::TailCall { .. } => CallSource::Normal, + let (call_source, destination, is_diverging) = match term.kind { + TerminatorKind::Call { call_source, destination, target, .. } => { + (call_source, destination, target.is_none()) + } + TerminatorKind::TailCall { .. } => { + (CallSource::Normal, RETURN_PLACE.into(), false) + } _ => unreachable!(), }; @@ -845,9 +844,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } - if let TerminatorKind::Call { destination, target, .. } = term.kind { - self.check_call_dest(term, &sig, destination, target, term_location); - } + self.check_call_dest(term, &sig, destination, is_diverging, term_location); // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We @@ -1760,10 +1757,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { locations, ); - assert!(!matches!( - tcx.impl_of_assoc(def_id).map(|imp| tcx.def_kind(imp)), - Some(DefKind::Impl { of_trait: true }) - )); + assert_eq!(tcx.trait_impl_of_assoc(def_id), None); self.prove_predicates( args.types().map(|ty| ty::ClauseKind::WellFormed(ty.into())), locations, @@ -1874,65 +1868,61 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { term: &Terminator<'tcx>, sig: &ty::FnSig<'tcx>, destination: Place<'tcx>, - target: Option, + is_diverging: bool, term_location: Location, ) { let tcx = self.tcx(); - match target { - Some(_) => { - let dest_ty = destination.ty(self.body, tcx).ty; - let dest_ty = self.normalize(dest_ty, term_location); - let category = match destination.as_local() { - Some(RETURN_PLACE) => { - if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) = - self.universal_regions.defining_ty - { - if tcx.is_static(def_id) { - ConstraintCategory::UseAsStatic - } else { - ConstraintCategory::UseAsConst - } + if is_diverging { + // The signature in this call can reference region variables, + // so erase them before calling a query. + let output_ty = self.tcx().erase_regions(sig.output()); + if !output_ty + .is_privately_uninhabited(self.tcx(), self.infcx.typing_env(self.infcx.param_env)) + { + span_mirbug!(self, term, "call to non-diverging function {:?} w/o dest", sig); + } + } else { + let dest_ty = destination.ty(self.body, tcx).ty; + let dest_ty = self.normalize(dest_ty, term_location); + let category = match destination.as_local() { + Some(RETURN_PLACE) => { + if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) = + self.universal_regions.defining_ty + { + if tcx.is_static(def_id) { + ConstraintCategory::UseAsStatic } else { - ConstraintCategory::Return(ReturnConstraint::Normal) + ConstraintCategory::UseAsConst } + } else { + ConstraintCategory::Return(ReturnConstraint::Normal) } - Some(l) if !self.body.local_decls[l].is_user_variable() => { - ConstraintCategory::Boring - } - // The return type of a call is interesting for diagnostics. - _ => ConstraintCategory::Assignment, - }; - - let locations = term_location.to_locations(); - - if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) { - span_mirbug!( - self, - term, - "call dest mismatch ({:?} <- {:?}): {:?}", - dest_ty, - sig.output(), - terr - ); } - - // When `unsized_fn_params` is not enabled, - // this check is done at `check_local`. - if self.unsized_feature_enabled() { - let span = term.source_info.span; - self.ensure_place_sized(dest_ty, span); + Some(l) if !self.body.local_decls[l].is_user_variable() => { + ConstraintCategory::Boring } + // The return type of a call is interesting for diagnostics. + _ => ConstraintCategory::Assignment, + }; + + let locations = term_location.to_locations(); + + if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) { + span_mirbug!( + self, + term, + "call dest mismatch ({:?} <- {:?}): {:?}", + dest_ty, + sig.output(), + terr + ); } - None => { - // The signature in this call can reference region variables, - // so erase them before calling a query. - let output_ty = self.tcx().erase_regions(sig.output()); - if !output_ty.is_privately_uninhabited( - self.tcx(), - self.infcx.typing_env(self.infcx.param_env), - ) { - span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig); - } + + // When `unsized_fn_params` is not enabled, + // this check is done at `check_local`. + if self.unsized_feature_enabled() { + let span = term.source_info.span; + self.ensure_place_sized(dest_ty, span); } } } diff --git a/compiler/rustc_borrowck/src/type_check/opaque_types.rs b/compiler/rustc_borrowck/src/type_check/opaque_types.rs deleted file mode 100644 index 5a422483eef4..000000000000 --- a/compiler/rustc_borrowck/src/type_check/opaque_types.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::iter; - -use rustc_data_structures::fx::FxIndexMap; -use rustc_middle::span_bug; -use rustc_middle::ty::{ - self, GenericArgKind, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, fold_regions, -}; -use tracing::{debug, trace}; - -use super::{MemberConstraintSet, TypeChecker}; - -/// Once we're done with typechecking the body, we take all the opaque types -/// defined by this function and add their 'member constraints'. -pub(super) fn take_opaques_and_register_member_constraints<'tcx>( - typeck: &mut TypeChecker<'_, 'tcx>, -) -> FxIndexMap, OpaqueHiddenType<'tcx>> { - let infcx = typeck.infcx; - // Annoying: to invoke `typeck.to_region_vid`, we need access to - // `typeck.constraints`, but we also want to be mutating - // `typeck.member_constraints`. For now, just swap out the value - // we want and replace at the end. - let mut member_constraints = std::mem::take(&mut typeck.constraints.member_constraints); - let opaque_types = infcx - .take_opaque_types() - .into_iter() - .map(|(opaque_type_key, hidden_type)| { - let hidden_type = infcx.resolve_vars_if_possible(hidden_type); - register_member_constraints( - typeck, - &mut member_constraints, - opaque_type_key, - hidden_type, - ); - trace!("finalized opaque type {:?} to {:#?}", opaque_type_key, hidden_type.ty.kind()); - if hidden_type.has_non_region_infer() { - span_bug!(hidden_type.span, "could not resolve {:?}", hidden_type.ty); - } - - // Convert all regions to nll vars. - let (opaque_type_key, hidden_type) = - fold_regions(infcx.tcx, (opaque_type_key, hidden_type), |r, _| { - ty::Region::new_var(infcx.tcx, typeck.to_region_vid(r)) - }); - - (opaque_type_key, hidden_type) - }) - .collect(); - assert!(typeck.constraints.member_constraints.is_empty()); - typeck.constraints.member_constraints = member_constraints; - opaque_types -} - -/// Given the map `opaque_types` containing the opaque -/// `impl Trait` types whose underlying, hidden types are being -/// inferred, this method adds constraints to the regions -/// appearing in those underlying hidden types to ensure that they -/// at least do not refer to random scopes within the current -/// function. These constraints are not (quite) sufficient to -/// guarantee that the regions are actually legal values; that -/// final condition is imposed after region inference is done. -/// -/// # The Problem -/// -/// Let's work through an example to explain how it works. Assume -/// the current function is as follows: -/// -/// ```text -/// fn foo<'a, 'b>(..) -> (impl Bar<'a>, impl Bar<'b>) -/// ``` -/// -/// Here, we have two `impl Trait` types whose values are being -/// inferred (the `impl Bar<'a>` and the `impl -/// Bar<'b>`). Conceptually, this is sugar for a setup where we -/// define underlying opaque types (`Foo1`, `Foo2`) and then, in -/// the return type of `foo`, we *reference* those definitions: -/// -/// ```text -/// type Foo1<'x> = impl Bar<'x>; -/// type Foo2<'x> = impl Bar<'x>; -/// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. } -/// // ^^^^ ^^ -/// // | | -/// // | args -/// // def_id -/// ``` -/// -/// As indicating in the comments above, each of those references -/// is (in the compiler) basically generic parameters (`args`) -/// applied to the type of a suitable `def_id` (which identifies -/// `Foo1` or `Foo2`). -/// -/// Now, at this point in compilation, what we have done is to -/// replace each of the references (`Foo1<'a>`, `Foo2<'b>`) with -/// fresh inference variables C1 and C2. We wish to use the values -/// of these variables to infer the underlying types of `Foo1` and -/// `Foo2`. That is, this gives rise to higher-order (pattern) unification -/// constraints like: -/// -/// ```text -/// for<'a> (Foo1<'a> = C1) -/// for<'b> (Foo1<'b> = C2) -/// ``` -/// -/// For these equation to be satisfiable, the types `C1` and `C2` -/// can only refer to a limited set of regions. For example, `C1` -/// can only refer to `'static` and `'a`, and `C2` can only refer -/// to `'static` and `'b`. The job of this function is to impose that -/// constraint. -/// -/// Up to this point, C1 and C2 are basically just random type -/// inference variables, and hence they may contain arbitrary -/// regions. In fact, it is fairly likely that they do! Consider -/// this possible definition of `foo`: -/// -/// ```text -/// fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> (impl Bar<'a>, impl Bar<'b>) { -/// (&*x, &*y) -/// } -/// ``` -/// -/// Here, the values for the concrete types of the two impl -/// traits will include inference variables: -/// -/// ```text -/// &'0 i32 -/// &'1 i32 -/// ``` -/// -/// Ordinarily, the subtyping rules would ensure that these are -/// sufficiently large. But since `impl Bar<'a>` isn't a specific -/// type per se, we don't get such constraints by default. This -/// is where this function comes into play. It adds extra -/// constraints to ensure that all the regions which appear in the -/// inferred type are regions that could validly appear. -/// -/// This is actually a bit of a tricky constraint in general. We -/// want to say that each variable (e.g., `'0`) can only take on -/// values that were supplied as arguments to the opaque type -/// (e.g., `'a` for `Foo1<'a>`) or `'static`, which is always in -/// scope. We don't have a constraint quite of this kind in the current -/// region checker. -/// -/// # The Solution -/// -/// We generally prefer to make `<=` constraints, since they -/// integrate best into the region solver. To do that, we find the -/// "minimum" of all the arguments that appear in the args: that -/// is, some region which is less than all the others. In the case -/// of `Foo1<'a>`, that would be `'a` (it's the only choice, after -/// all). Then we apply that as a least bound to the variables -/// (e.g., `'a <= '0`). -/// -/// In some cases, there is no minimum. Consider this example: -/// -/// ```text -/// fn baz<'a, 'b>() -> impl Trait<'a, 'b> { ... } -/// ``` -/// -/// Here we would report a more complex "in constraint", like `'r -/// in ['a, 'b, 'static]` (where `'r` is some region appearing in -/// the hidden type). -/// -/// # Constrain regions, not the hidden concrete type -/// -/// Note that generating constraints on each region `Rc` is *not* -/// the same as generating an outlives constraint on `Tc` itself. -/// For example, if we had a function like this: -/// -/// ``` -/// # #![feature(type_alias_impl_trait)] -/// # fn main() {} -/// # trait Foo<'a> {} -/// # impl<'a, T> Foo<'a> for (&'a u32, T) {} -/// fn foo<'a, T>(x: &'a u32, y: T) -> impl Foo<'a> { -/// (x, y) -/// } -/// -/// // Equivalent to: -/// # mod dummy { use super::*; -/// type FooReturn<'a, T> = impl Foo<'a>; -/// #[define_opaque(FooReturn)] -/// fn foo<'a, T>(x: &'a u32, y: T) -> FooReturn<'a, T> { -/// (x, y) -/// } -/// # } -/// ``` -/// -/// then the hidden type `Tc` would be `(&'0 u32, T)` (where `'0` -/// is an inference variable). If we generated a constraint that -/// `Tc: 'a`, then this would incorrectly require that `T: 'a` -- -/// but this is not necessary, because the opaque type we -/// create will be allowed to reference `T`. So we only generate a -/// constraint that `'0: 'a`. -fn register_member_constraints<'tcx>( - typeck: &mut TypeChecker<'_, 'tcx>, - member_constraints: &mut MemberConstraintSet<'tcx, ty::RegionVid>, - opaque_type_key: OpaqueTypeKey<'tcx>, - OpaqueHiddenType { span, ty: hidden_ty }: OpaqueHiddenType<'tcx>, -) { - let tcx = typeck.tcx(); - let hidden_ty = typeck.infcx.resolve_vars_if_possible(hidden_ty); - debug!(?hidden_ty); - - let variances = tcx.variances_of(opaque_type_key.def_id); - debug!(?variances); - - // For a case like `impl Foo<'a, 'b>`, we would generate a constraint - // `'r in ['a, 'b, 'static]` for each region `'r` that appears in the - // hidden type (i.e., it must be equal to `'a`, `'b`, or `'static`). - // - // `conflict1` and `conflict2` are the two region bounds that we - // detected which were unrelated. They are used for diagnostics. - - // Create the set of choice regions: each region in the hidden - // type can be equal to any of the region parameters of the - // opaque type definition. - let fr_static = typeck.universal_regions.fr_static; - let choice_regions: Vec<_> = opaque_type_key - .args - .iter() - .enumerate() - .filter(|(i, _)| variances[*i] == ty::Invariant) - .filter_map(|(_, arg)| match arg.kind() { - GenericArgKind::Lifetime(r) => Some(typeck.to_region_vid(r)), - GenericArgKind::Type(_) | GenericArgKind::Const(_) => None, - }) - .chain(iter::once(fr_static)) - .collect(); - - // FIXME(#42940): This should use the `FreeRegionsVisitor`, but that's - // not currently sound until we have existential regions. - hidden_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor { - tcx, - op: |r| { - member_constraints.add_member_constraint( - opaque_type_key, - hidden_ty, - span, - typeck.to_region_vid(r), - &choice_regions, - ) - }, - }); -} - -/// Visitor that requires that (almost) all regions in the type visited outlive -/// `least_region`. We cannot use `push_outlives_components` because regions in -/// closure signatures are not included in their outlives components. We need to -/// ensure all regions outlive the given bound so that we don't end up with, -/// say, `ReVar` appearing in a return type and causing ICEs when other -/// functions end up with region constraints involving regions from other -/// functions. -/// -/// We also cannot use `for_each_free_region` because for closures it includes -/// the regions parameters from the enclosing item. -/// -/// We ignore any type parameters because impl trait values are assumed to -/// capture all the in-scope type parameters. -struct ConstrainOpaqueTypeRegionVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { - tcx: TyCtxt<'tcx>, - op: OP, -} - -impl<'tcx, OP> TypeVisitor> for ConstrainOpaqueTypeRegionVisitor<'tcx, OP> -where - OP: FnMut(ty::Region<'tcx>), -{ - fn visit_region(&mut self, r: ty::Region<'tcx>) { - match r.kind() { - // ignore bound regions, keep visiting - ty::ReBound(_, _) => {} - _ => (self.op)(r), - } - } - - fn visit_ty(&mut self, ty: Ty<'tcx>) { - // We're only interested in types involving regions - if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { - return; - } - - match *ty.kind() { - ty::Closure(_, args) => { - // Skip lifetime parameters of the enclosing item(s) - - for upvar in args.as_closure().upvar_tys() { - upvar.visit_with(self); - } - args.as_closure().sig_as_fn_ptr_ty().visit_with(self); - } - - ty::CoroutineClosure(_, args) => { - // Skip lifetime parameters of the enclosing item(s) - - for upvar in args.as_coroutine_closure().upvar_tys() { - upvar.visit_with(self); - } - - args.as_coroutine_closure().signature_parts_ty().visit_with(self); - } - - ty::Coroutine(_, args) => { - // Skip lifetime parameters of the enclosing item(s) - // Also skip the witness type, because that has no free regions. - - for upvar in args.as_coroutine().upvar_tys() { - upvar.visit_with(self); - } - args.as_coroutine().return_ty().visit_with(self); - args.as_coroutine().yield_ty().visit_with(self); - args.as_coroutine().resume_ty().visit_with(self); - } - - ty::Alias(kind, ty::AliasTy { def_id, args, .. }) - if let Some(variances) = self.tcx.opt_alias_variances(kind, def_id) => - { - // Skip lifetime parameters that are not captured, since they do - // not need member constraints registered for them; we'll erase - // them (and hopefully in the future replace them with placeholders). - for (v, s) in std::iter::zip(variances, args.iter()) { - if *v != ty::Bivariant { - s.visit_with(self); - } - } - } - - _ => { - ty.super_visit_with(self); - } - } - } -} diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index e023300f1c28..7ac2dff12f75 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -124,8 +124,13 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { // by using `ty_vid rel B` and then finally and end by equating `ty_vid` to // the opaque. let mut enable_subtyping = |ty, opaque_is_expected| { - let ty_vid = infcx.next_ty_var_id_in_universe(self.span(), ty::UniverseIndex::ROOT); - + // We create the fresh inference variable in the highest universe. + // In theory we could limit it to the highest universe in the args of + // the opaque but that isn't really worth the effort. + // + // We'll make sure that the opaque type can actually name everything + // in its hidden type later on. + let ty_vid = infcx.next_ty_vid(self.span()); let variance = if opaque_is_expected { self.ambient_variance } else { @@ -187,7 +192,7 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { types: &mut |_bound_ty: ty::BoundTy| { unreachable!("we only replace regions in nll_relate, not types") }, - consts: &mut |_bound_var: ty::BoundVar| { + consts: &mut |_bound_const: ty::BoundConst| { unreachable!("we only replace regions in nll_relate, not consts") }, }; @@ -216,7 +221,7 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { *ex_reg_var } else { let ex_reg_var = - self.next_existential_region_var(true, br.kind.get_name(infcx.infcx.tcx)); + self.next_existential_region_var(br.kind.get_name(infcx.infcx.tcx)); debug!(?ex_reg_var); reg_map.insert(br, ex_reg_var); @@ -226,7 +231,7 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { types: &mut |_bound_ty: ty::BoundTy| { unreachable!("we only replace regions in nll_relate, not types") }, - consts: &mut |_bound_var: ty::BoundVar| { + consts: &mut |_bound_const: ty::BoundConst| { unreachable!("we only replace regions in nll_relate, not consts") }, }; @@ -244,17 +249,9 @@ impl<'a, 'b, 'tcx> NllTypeRelating<'a, 'b, 'tcx> { } #[instrument(skip(self), level = "debug")] - fn next_existential_region_var( - &mut self, - from_forall: bool, - name: Option, - ) -> ty::Region<'tcx> { - let origin = NllRegionVariableOrigin::Existential { from_forall }; - - let reg_var = - self.type_checker.infcx.next_nll_region_var(origin, || RegionCtxt::Existential(name)); - - reg_var + fn next_existential_region_var(&mut self, name: Option) -> ty::Region<'tcx> { + let origin = NllRegionVariableOrigin::Existential { name }; + self.type_checker.infcx.next_nll_region_var(origin, || RegionCtxt::Existential(name)) } #[instrument(skip(self), level = "debug")] diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 240c9a5223be..296a27355333 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -217,7 +217,7 @@ struct UniversalRegionIndices<'tcx> { /// Whether we've encountered an error region. If we have, cancel all /// outlives errors, as they are likely bogus. - pub tainted_by_errors: Cell>, + pub encountered_re_error: Cell>, } #[derive(Debug, PartialEq)] @@ -442,8 +442,8 @@ impl<'tcx> UniversalRegions<'tcx> { self.fr_fn_body } - pub(crate) fn tainted_by_errors(&self) -> Option { - self.indices.tainted_by_errors.get() + pub(crate) fn encountered_re_error(&self) -> Option { + self.indices.encountered_re_error.get() } } @@ -706,7 +706,7 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { UniversalRegionIndices { indices: global_mapping.chain(arg_mapping).collect(), fr_static, - tainted_by_errors: Cell::new(None), + encountered_re_error: Cell::new(None), } } @@ -916,7 +916,7 @@ impl<'tcx> UniversalRegionIndices<'tcx> { match r.kind() { ty::ReVar(..) => r.as_var(), ty::ReError(guar) => { - self.tainted_by_errors.set(Some(guar)); + self.encountered_re_error.set(Some(guar)); // We use the `'static` `RegionVid` because `ReError` doesn't actually exist in the // `UniversalRegionIndices`. This is fine because 1) it is a fallback only used if // errors are being emitted and 2) it leaves the happy path unaffected. diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index ae186d744c40..358c0d3db460 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -222,6 +222,15 @@ builtin_macros_format_unused_args = multiple unused formatting arguments builtin_macros_format_use_positional = consider using a positional formatting argument instead +builtin_macros_derive_from_wrong_target = `#[derive(From)]` used on {$kind} + +builtin_macros_derive_from_wrong_field_count = `#[derive(From)]` used on a struct with {$multiple_fields -> + [true] multiple fields + *[false] no fields +} + +builtin_macros_derive_from_usage_note = `#[derive(From)]` can only be used on structs with exactly one field + builtin_macros_multiple_default_attrs = multiple `#[default]` attributes .note = only one `#[default]` attribute is needed .label = `#[default]` used here @@ -259,8 +268,6 @@ builtin_macros_only_one_argument = {$name} takes 1 argument builtin_macros_proc_macro = `proc-macro` crate types currently cannot export any items other than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, or `#[proc_macro_attribute]` -builtin_macros_proc_macro_attribute_only_be_used_on_bare_functions = the `#[{$path}]` attribute may only be used on bare functions - builtin_macros_proc_macro_attribute_only_usable_with_crate_type = the `#[{$path}]` attribute is only usable with crates of the `proc-macro` crate type builtin_macros_requires_cfg_pattern = diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs index e75bc944d7ec..35ef6be095e9 100644 --- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::{ self as ast, Fn, FnHeader, FnSig, Generics, ItemKind, Safety, Stmt, StmtKind, TyKind, }; @@ -46,7 +45,7 @@ pub(crate) fn expand( let const_body = ecx.expr_block(ecx.block(span, stmts)); let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body); let const_item = if is_stmt { - Annotatable::Stmt(P(ecx.stmt_item(span, const_item))) + Annotatable::Stmt(Box::new(ecx.stmt_item(span, const_item))) } else { Annotatable::Item(const_item) }; diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 1fb998172226..86b8e1ff8dbb 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -1,5 +1,4 @@ use lint::BuiltinLintDiag; -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; @@ -19,7 +18,7 @@ use crate::{errors, fluent_generated as fluent}; /// Validated assembly arguments, ready for macro expansion. struct ValidatedAsmArgs { - pub templates: Vec>, + pub templates: Vec>, pub operands: Vec<(ast::InlineAsmOperand, Span)>, named_args: FxIndexMap, reg_args: GrowableBitSet, @@ -600,9 +599,9 @@ pub(super) fn expand_asm<'cx>( return ExpandResult::Retry(()); }; let expr = match mac { - Ok(inline_asm) => P(ast::Expr { + Ok(inline_asm) => Box::new(ast::Expr { id: ast::DUMMY_NODE_ID, - kind: ast::ExprKind::InlineAsm(P(inline_asm)), + kind: ast::ExprKind::InlineAsm(Box::new(inline_asm)), span: sp, attrs: ast::AttrVec::new(), tokens: None, @@ -630,9 +629,9 @@ pub(super) fn expand_naked_asm<'cx>( return ExpandResult::Retry(()); }; let expr = match mac { - Ok(inline_asm) => P(ast::Expr { + Ok(inline_asm) => Box::new(ast::Expr { id: ast::DUMMY_NODE_ID, - kind: ast::ExprKind::InlineAsm(P(inline_asm)), + kind: ast::ExprKind::InlineAsm(Box::new(inline_asm)), span: sp, attrs: ast::AttrVec::new(), tokens: None, @@ -660,7 +659,7 @@ pub(super) fn expand_global_asm<'cx>( return ExpandResult::Retry(()); }; match mac { - Ok(inline_asm) => MacEager::items(smallvec![P(ast::Item { + Ok(inline_asm) => MacEager::items(smallvec![Box::new(ast::Item { attrs: ast::AttrVec::new(), id: ast::DUMMY_NODE_ID, kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)), diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs index c659b1cff59b..013258a1b4ef 100644 --- a/compiler/rustc_builtin_macros/src/assert.rs +++ b/compiler/rustc_builtin_macros/src/assert.rs @@ -1,9 +1,8 @@ mod context; -use rustc_ast::ptr::P; -use rustc_ast::token::Delimiter; +use rustc_ast::token::{self, Delimiter}; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; -use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token}; +use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment}; use rustc_ast_pretty::pprust; use rustc_errors::PResult; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; @@ -30,7 +29,7 @@ pub(crate) fn expand_assert<'cx>( // `core::panic` and `std::panic` are different macros, so we use call-site // context to pick up whichever is currently in scope. - let call_site_span = cx.with_call_site_ctxt(span); + let call_site_span = cx.with_call_site_ctxt(cond_expr.span); let panic_path = || { if use_panic_2021(span) { @@ -55,16 +54,16 @@ pub(crate) fn expand_assert<'cx>( let expr = if let Some(tokens) = custom_message { let then = cx.expr( call_site_span, - ExprKind::MacCall(P(MacCall { + ExprKind::MacCall(Box::new(MacCall { path: panic_path(), - args: P(DelimArgs { + args: Box::new(DelimArgs { dspan: DelimSpan::from_single(call_site_span), delim: Delimiter::Parenthesis, tokens, }), })), ); - expr_if_not(cx, call_site_span, cond_expr, then, None) + assert_cond_check(cx, call_site_span, cond_expr, then) } // If `generic_assert` is enabled, generates rich captured outputs // @@ -89,26 +88,33 @@ pub(crate) fn expand_assert<'cx>( )), )], ); - expr_if_not(cx, call_site_span, cond_expr, then, None) + assert_cond_check(cx, call_site_span, cond_expr, then) }; ExpandResult::Ready(MacEager::expr(expr)) } +/// `assert!($cond_expr, $custom_message)` struct Assert { - cond_expr: P, + cond_expr: Box, custom_message: Option, } -// if !{ ... } { ... } else { ... } -fn expr_if_not( - cx: &ExtCtxt<'_>, - span: Span, - cond: P, - then: P, - els: Option>, -) -> P { - cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els) +/// `match { true => {} _ => }` +fn assert_cond_check(cx: &ExtCtxt<'_>, span: Span, cond: Box, then: Box) -> Box { + // Instead of expanding to `if ! { }`, we expand to + // `match { true => {} _ => }`. + // This allows us to always complain about mismatched types instead of "cannot apply unary + // operator `!` to type `X`" when passing an invalid ``, while also allowing `` to + // be `&true`. + let els = cx.expr_block(cx.block(span, thin_vec![])); + let mut arms = thin_vec![]; + arms.push(cx.arm(span, cx.pat_lit(span, cx.expr_bool(span, true)), els)); + arms.push(cx.arm(span, cx.pat_wild(span), then)); + + // We wrap the `match` in a statement to limit the length of any borrows introduced in the + // condition. + cx.expr_block(cx.block(span, [cx.stmt_expr(cx.expr_match(span, cond, arms))].into())) } fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index ea7248ca5393..31855cbd4e6c 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, IdentIsRaw}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::{ @@ -70,7 +69,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// } /// } /// ``` - pub(super) fn build(mut self, mut cond_expr: P, panic_path: Path) -> P { + pub(super) fn build(mut self, mut cond_expr: Box, panic_path: Path) -> Box { let expr_str = pprust::expr_to_string(&cond_expr); self.manage_cond_expr(&mut cond_expr); let initial_imports = self.build_initial_imports(); @@ -129,7 +128,7 @@ impl<'cx, 'a> Context<'cx, 'a> { } /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely` - fn build_unlikely(&self, cond_expr: P) -> P { + fn build_unlikely(&self, cond_expr: Box) -> Box { let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]); self.cx.expr_call( self.span, @@ -145,7 +144,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// __capture0, /// ... /// ); - fn build_panic(&self, expr_str: &str, panic_path: Path) -> P { + fn build_panic(&self, expr_str: &str, panic_path: Path) -> Box { let escaped_expr_str = escape_to_fmt(expr_str); let initial = [ TokenTree::token_joint( @@ -176,9 +175,9 @@ impl<'cx, 'a> Context<'cx, 'a> { }); self.cx.expr( self.span, - ExprKind::MacCall(P(MacCall { + ExprKind::MacCall(Box::new(MacCall { path: panic_path, - args: P(DelimArgs { + args: Box::new(DelimArgs { dspan: DelimSpan::from_single(self.span), delim: Delimiter::Parenthesis, tokens: initial.into_iter().chain(captures).collect::(), @@ -190,7 +189,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// Recursive function called until `cond_expr` and `fmt_str` are fully modified. /// /// See [Self::manage_initial_capture] and [Self::manage_try_capture] - fn manage_cond_expr(&mut self, expr: &mut P) { + fn manage_cond_expr(&mut self, expr: &mut Box) { match &mut expr.kind { ExprKind::AddrOf(_, mutability, local_expr) => { self.with_is_consumed_management(matches!(mutability, Mutability::Mut), |this| { @@ -331,7 +330,7 @@ impl<'cx, 'a> Context<'cx, 'a> { /// /// `fmt_str`, the formatting string used for debugging, is constructed to show possible /// captured variables. - fn manage_initial_capture(&mut self, expr: &mut P, path_ident: Ident) { + fn manage_initial_capture(&mut self, expr: &mut Box, path_ident: Ident) { if self.paths.contains(&path_ident) { return; } else { @@ -360,7 +359,12 @@ impl<'cx, 'a> Context<'cx, 'a> { /// (&Wrapper(__local_bindN)).try_capture(&mut __captureN); /// __local_bindN /// } - fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P) { + fn manage_try_capture( + &mut self, + capture: Ident, + curr_capture_idx: usize, + expr: &mut Box, + ) { let local_bind_string = format!("__local_bind{curr_capture_idx}"); let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span); self.local_bind_decls.push(self.cx.stmt_let( @@ -441,20 +445,20 @@ fn escape_to_fmt(s: &str) -> String { rslt } -fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P { +fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: Box) -> Box { cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e)) } fn expr_method_call( cx: &ExtCtxt<'_>, seg: PathSegment, - receiver: P, - args: ThinVec>, + receiver: Box, + args: ThinVec>, span: Span, -) -> P { +) -> Box { cx.expr(span, ExprKind::MethodCall(Box::new(MethodCall { seg, receiver, args, span }))) } -fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P { +fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: Box) -> Box { cx.expr(sp, ExprKind::Paren(e)) } diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index c78447783327..48d0795af5ee 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -11,23 +11,23 @@ mod llvm_enzyme { AutoDiffAttrs, DiffActivity, DiffMode, valid_input_activity, valid_ret_activity, valid_ty_for_activity, }; - use rustc_ast::ptr::P; use rustc_ast::token::{Lit, LitKind, Token, TokenKind}; use rustc_ast::tokenstream::*; use rustc_ast::visit::AssocCtxt::*; use rustc_ast::{ - self as ast, AssocItemKind, BindingMode, ExprKind, FnRetTy, FnSig, Generics, ItemKind, - MetaItemInner, PatKind, QSelf, TyKind, Visibility, + self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemKind, BindingMode, + FnRetTy, FnSig, GenericArg, GenericArgs, GenericParamKind, Generics, ItemKind, + MetaItemInner, PatKind, Path, PathSegment, TyKind, Visibility, }; use rustc_expand::base::{Annotatable, ExtCtxt}; - use rustc_span::{Ident, Span, Symbol, kw, sym}; + use rustc_span::{Ident, Span, Symbol, sym}; use thin_vec::{ThinVec, thin_vec}; use tracing::{debug, trace}; use crate::errors; pub(crate) fn outer_normal_attr( - kind: &P, + kind: &Box, id: rustc_ast::AttrId, span: Span, ) -> rustc_ast::Attribute { @@ -73,7 +73,7 @@ mod llvm_enzyme { } // Get information about the function the macro is applied to - fn extract_item_info(iitem: &P) -> Option<(Visibility, FnSig, Ident, Generics)> { + fn extract_item_info(iitem: &Box) -> Option<(Visibility, FnSig, Ident, Generics)> { match &iitem.kind { ItemKind::Fn(box ast::Fn { sig, ident, generics, .. }) => { Some((iitem.vis.clone(), sig.clone(), ident.clone(), generics.clone())) @@ -180,11 +180,8 @@ mod llvm_enzyme { } /// We expand the autodiff macro to generate a new placeholder function which passes - /// type-checking and can be called by users. The function body of the placeholder function will - /// later be replaced on LLVM-IR level, so the design of the body is less important and for now - /// should just prevent early inlining and optimizations which alter the function signature. - /// The exact signature of the generated function depends on the configuration provided by the - /// user, but here is an example: + /// type-checking and can be called by users. The exact signature of the generated function + /// depends on the configuration provided by the user, but here is an example: /// /// ``` /// #[autodiff(cos_box, Reverse, Duplicated, Active)] @@ -195,19 +192,12 @@ mod llvm_enzyme { /// which becomes expanded to: /// ``` /// #[rustc_autodiff] - /// #[inline(never)] /// fn sin(x: &Box) -> f32 { /// f32::sin(**x) /// } /// #[rustc_autodiff(Reverse, Duplicated, Active)] - /// #[inline(never)] /// fn cos_box(x: &Box, dx: &mut Box, dret: f32) -> f32 { - /// unsafe { - /// asm!("NOP"); - /// }; - /// ::core::hint::black_box(sin(x)); - /// ::core::hint::black_box((dx, dret)); - /// ::core::hint::black_box(sin(x)) + /// std::intrinsics::autodiff(sin::<>, cos_box::<>, (x, dx, dret)) /// } /// ``` /// FIXME(ZuseZ4): Once autodiff is enabled by default, make this a doc comment which is checked @@ -228,16 +218,24 @@ mod llvm_enzyme { // first get information about the annotable item: visibility, signature, name and generic // parameters. // these will be used to generate the differentiated version of the function - let Some((vis, sig, primal, generics)) = (match &item { - Annotatable::Item(iitem) => extract_item_info(iitem), + let Some((vis, sig, primal, generics, impl_of_trait)) = (match &item { + Annotatable::Item(iitem) => { + extract_item_info(iitem).map(|(v, s, p, g)| (v, s, p, g, false)) + } Annotatable::Stmt(stmt) => match &stmt.kind { - ast::StmtKind::Item(iitem) => extract_item_info(iitem), + ast::StmtKind::Item(iitem) => { + extract_item_info(iitem).map(|(v, s, p, g)| (v, s, p, g, false)) + } _ => None, }, - Annotatable::AssocItem(assoc_item, Impl { .. }) => match &assoc_item.kind { - ast::AssocItemKind::Fn(box ast::Fn { sig, ident, generics, .. }) => { - Some((assoc_item.vis.clone(), sig.clone(), ident.clone(), generics.clone())) - } + Annotatable::AssocItem(assoc_item, Impl { of_trait }) => match &assoc_item.kind { + ast::AssocItemKind::Fn(box ast::Fn { sig, ident, generics, .. }) => Some(( + assoc_item.vis.clone(), + sig.clone(), + ident.clone(), + generics.clone(), + *of_trait, + )), _ => None, }, _ => None, @@ -255,7 +253,6 @@ mod llvm_enzyme { }; let has_ret = has_ret(&sig.decl.output); - let sig_span = ecx.with_call_site_ctxt(sig.span); // create TokenStream from vec elemtents: // meta_item doesn't have a .tokens field @@ -324,19 +321,23 @@ mod llvm_enzyme { } let span = ecx.with_def_site_ctxt(expand_span); - let n_active: u32 = x - .input_activity - .iter() - .filter(|a| **a == DiffActivity::Active || **a == DiffActivity::ActiveOnly) - .count() as u32; - let (d_sig, new_args, idents, errored) = gen_enzyme_decl(ecx, &sig, &x, span); - let d_body = gen_enzyme_body( - ecx, &x, n_active, &sig, &d_sig, primal, &new_args, span, sig_span, idents, errored, - &generics, + let d_sig = gen_enzyme_decl(ecx, &sig, &x, span); + + let d_body = ecx.block( + span, + thin_vec![call_autodiff( + ecx, + primal, + first_ident(&meta_item_vec[0]), + span, + &d_sig, + &generics, + impl_of_trait, + )], ); // The first element of it is the name of the function to be generated - let asdf = Box::new(ast::Fn { + let d_fn = Box::new(ast::Fn { defaultness: ast::Defaultness::Final, sig: d_sig, ident: first_ident(&meta_item_vec[0]), @@ -346,7 +347,7 @@ mod llvm_enzyme { define_opaque: None, }); let mut rustc_ad_attr = - P(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_autodiff))); + Box::new(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_autodiff))); let ts2: Vec = vec![TokenTree::Token( Token::new(TokenKind::Ident(sym::never, false.into()), span), @@ -363,13 +364,13 @@ mod llvm_enzyme { args: ast::AttrArgs::Delimited(never_arg), tokens: None, }; - let inline_never_attr = P(ast::NormalAttr { item: inline_item, tokens: None }); + let inline_never_attr = Box::new(ast::NormalAttr { item: inline_item, tokens: None }); let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let attr = outer_normal_attr(&rustc_ad_attr, new_id, span); let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let inline_never = outer_normal_attr(&inline_never_attr, new_id, span); - // We're avoid duplicating the attributes `#[rustc_autodiff]` and `#[inline(never)]`. + // We're avoid duplicating the attribute `#[rustc_autodiff]`. fn same_attribute(attr: &ast::AttrKind, item: &ast::AttrKind) -> bool { match (attr, item) { (ast::AttrKind::Normal(a), ast::AttrKind::Normal(b)) => { @@ -382,14 +383,16 @@ mod llvm_enzyme { } } + let mut has_inline_never = false; + // Don't add it multiple times: let orig_annotatable: Annotatable = match item { Annotatable::Item(ref mut iitem) => { if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) { iitem.attrs.push(attr); } - if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { - iitem.attrs.push(inline_never.clone()); + if iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { + has_inline_never = true; } Annotatable::Item(iitem.clone()) } @@ -397,8 +400,8 @@ mod llvm_enzyme { if !assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) { assoc_item.attrs.push(attr); } - if !assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { - assoc_item.attrs.push(inline_never.clone()); + if assoc_item.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { + has_inline_never = true; } Annotatable::AssocItem(assoc_item.clone(), i) } @@ -408,9 +411,8 @@ mod llvm_enzyme { if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &attr.kind)) { iitem.attrs.push(attr); } - if !iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) - { - iitem.attrs.push(inline_never.clone()); + if iitem.attrs.iter().any(|a| same_attribute(&a.kind, &inline_never.kind)) { + has_inline_never = true; } } _ => unreachable!("stmt kind checked previously"), @@ -429,12 +431,21 @@ mod llvm_enzyme { tokens: ts, }); + let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let d_attr = outer_normal_attr(&rustc_ad_attr, new_id, span); + + // If the source function has the `#[inline(never)]` attribute, we'll also add it to the diff function + let mut d_attrs = thin_vec![d_attr]; + + if has_inline_never { + d_attrs.push(inline_never); + } + let d_annotatable = match &item { Annotatable::AssocItem(_, _) => { - let assoc_item: AssocItemKind = ast::AssocItemKind::Fn(asdf); - let d_fn = P(ast::AssocItem { - attrs: thin_vec![d_attr, inline_never], + let assoc_item: AssocItemKind = ast::AssocItemKind::Fn(d_fn); + let d_fn = Box::new(ast::AssocItem { + attrs: d_attrs, id: ast::DUMMY_NODE_ID, span, vis, @@ -444,16 +455,16 @@ mod llvm_enzyme { Annotatable::AssocItem(d_fn, Impl { of_trait: false }) } Annotatable::Item(_) => { - let mut d_fn = ecx.item(span, thin_vec![d_attr, inline_never], ItemKind::Fn(asdf)); + let mut d_fn = ecx.item(span, d_attrs, ItemKind::Fn(d_fn)); d_fn.vis = vis; Annotatable::Item(d_fn) } Annotatable::Stmt(_) => { - let mut d_fn = ecx.item(span, thin_vec![d_attr, inline_never], ItemKind::Fn(asdf)); + let mut d_fn = ecx.item(span, d_attrs, ItemKind::Fn(d_fn)); d_fn.vis = vis; - Annotatable::Stmt(P(ast::Stmt { + Annotatable::Stmt(Box::new(ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Item(d_fn), span, @@ -485,282 +496,95 @@ mod llvm_enzyme { ty } - // Will generate a body of the type: + // Generate `autodiff` intrinsic call // ``` - // { - // unsafe { - // asm!("NOP"); - // } - // ::core::hint::black_box(primal(args)); - // ::core::hint::black_box((args, ret)); - // - // } + // std::intrinsics::autodiff(source, diff, (args)) // ``` - fn init_body_helper( + fn call_autodiff( ecx: &ExtCtxt<'_>, - span: Span, primal: Ident, - new_names: &[String], - sig_span: Span, - new_decl_span: Span, - idents: &[Ident], - errored: bool, + diff: Ident, + span: Span, + d_sig: &FnSig, generics: &Generics, - ) -> (P, P, P, P) { - let blackbox_path = ecx.std_path(&[sym::hint, sym::black_box]); - let noop = ast::InlineAsm { - asm_macro: ast::AsmMacro::Asm, - template: vec![ast::InlineAsmTemplatePiece::String("NOP".into())], - template_strs: Box::new([]), - operands: vec![], - clobber_abis: vec![], - options: ast::InlineAsmOptions::PURE | ast::InlineAsmOptions::NOMEM, - line_spans: vec![], - }; - let noop_expr = ecx.expr_asm(span, P(noop)); - let unsf = ast::BlockCheckMode::Unsafe(ast::UnsafeSource::CompilerGenerated); - let unsf_block = ast::Block { - stmts: thin_vec![ecx.stmt_semi(noop_expr)], - id: ast::DUMMY_NODE_ID, - tokens: None, - rules: unsf, + is_impl: bool, + ) -> rustc_ast::Stmt { + let primal_path_expr = gen_turbofish_expr(ecx, primal, generics, span, is_impl); + let diff_path_expr = gen_turbofish_expr(ecx, diff, generics, span, is_impl); + + let tuple_expr = ecx.expr_tuple( span, - }; - let unsf_expr = ecx.expr_block(P(unsf_block)); - let blackbox_call_expr = ecx.expr_path(ecx.path(span, blackbox_path)); - let primal_call = gen_primal_call(ecx, span, primal, idents, generics); - let black_box_primal_call = ecx.expr_call( - new_decl_span, - blackbox_call_expr.clone(), - thin_vec![primal_call.clone()], + d_sig + .decl + .inputs + .iter() + .map(|arg| match arg.pat.kind { + PatKind::Ident(_, ident, _) => ecx.expr_path(ecx.path_ident(span, ident)), + _ => todo!(), + }) + .collect::>() + .into(), ); - let tup_args = new_names - .iter() - .map(|arg| ecx.expr_path(ecx.path_ident(span, Ident::from_str(arg)))) - .collect(); - let black_box_remaining_args = ecx.expr_call( - sig_span, - blackbox_call_expr.clone(), - thin_vec![ecx.expr_tuple(sig_span, tup_args)], + let enzyme_path_idents = ecx.std_path(&[sym::intrinsics, sym::autodiff]); + let enzyme_path = ecx.path(span, enzyme_path_idents); + let call_expr = ecx.expr_call( + span, + ecx.expr_path(enzyme_path), + vec![primal_path_expr, diff_path_expr, tuple_expr].into(), ); - let mut body = ecx.block(span, ThinVec::new()); - body.stmts.push(ecx.stmt_semi(unsf_expr)); - - // This uses primal args which won't be available if we errored before - if !errored { - body.stmts.push(ecx.stmt_semi(black_box_primal_call.clone())); - } - body.stmts.push(ecx.stmt_semi(black_box_remaining_args)); - - (body, primal_call, black_box_primal_call, blackbox_call_expr) + ecx.stmt_expr(call_expr) } - /// We only want this function to type-check, since we will replace the body - /// later on llvm level. Using `loop {}` does not cover all return types anymore, - /// so instead we manually build something that should pass the type checker. - /// We also add a inline_asm line, as one more barrier for rustc to prevent inlining - /// or const propagation. inline_asm will also triggers an Enzyme crash if due to another - /// bug would ever try to accidentally differentiate this placeholder function body. - /// Finally, we also add back_box usages of all input arguments, to prevent rustc - /// from optimizing any arguments away. - fn gen_enzyme_body( + // Generate turbofish expression from fn name and generics + // Given `foo` and `` params, gen `foo::` + // We use this expression when passing primal and diff function to the autodiff intrinsic + fn gen_turbofish_expr( ecx: &ExtCtxt<'_>, - x: &AutoDiffAttrs, - n_active: u32, - sig: &ast::FnSig, - d_sig: &ast::FnSig, - primal: Ident, - new_names: &[String], - span: Span, - sig_span: Span, - idents: Vec, - errored: bool, + ident: Ident, generics: &Generics, - ) -> P { - let new_decl_span = d_sig.span; - - // Just adding some default inline-asm and black_box usages to prevent early inlining - // and optimizations which alter the function signature. - // - // The bb_primal_call is the black_box call of the primal function. We keep it around, - // since it has the convenient property of returning the type of the primal function, - // Remember, we only care to match types here. - // No matter which return we pick, we always wrap it into a std::hint::black_box call, - // to prevent rustc from propagating it into the caller. - let (mut body, primal_call, bb_primal_call, bb_call_expr) = init_body_helper( - ecx, - span, - primal, - new_names, - sig_span, - new_decl_span, - &idents, - errored, - generics, - ); - - if !has_ret(&d_sig.decl.output) { - // there is no return type that we have to match, () works fine. - return body; - } - - // Everything from here onwards just tries to fulfil the return type. Fun! - - // having an active-only return means we'll drop the original return type. - // So that can be treated identical to not having one in the first place. - let primal_ret = has_ret(&sig.decl.output) && !x.has_active_only_ret(); - - if primal_ret && n_active == 0 && x.mode.is_rev() { - // We only have the primal ret. - body.stmts.push(ecx.stmt_expr(bb_primal_call)); - return body; - } - - if !primal_ret && n_active == 1 { - // Again no tuple return, so return default float val. - let ty = match d_sig.decl.output { - FnRetTy::Ty(ref ty) => ty.clone(), - FnRetTy::Default(span) => { - panic!("Did not expect Default ret ty: {:?}", span); + span: Span, + is_impl: bool, + ) -> Box { + let generic_args = generics + .params + .iter() + .filter_map(|p| match &p.kind { + GenericParamKind::Type { .. } => { + let path = ast::Path::from_ident(p.ident); + let ty = ecx.ty_path(path); + Some(AngleBracketedArg::Arg(GenericArg::Type(ty))) } - }; - let arg = ty.kind.is_simple_path().unwrap(); - let tmp = ecx.def_site_path(&[arg, kw::Default]); - let default_call_expr = ecx.expr_path(ecx.path(span, tmp)); - let default_call_expr = ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]); - body.stmts.push(ecx.stmt_expr(default_call_expr)); - return body; - } - - let mut exprs: P = primal_call; - let d_ret_ty = match d_sig.decl.output { - FnRetTy::Ty(ref ty) => ty.clone(), - FnRetTy::Default(span) => { - panic!("Did not expect Default ret ty: {:?}", span); - } - }; - if x.mode.is_fwd() { - // Fwd mode is easy. If the return activity is Const, we support arbitrary types. - // Otherwise, we only support a scalar, a pair of scalars, or an array of scalars. - // We checked that (on a best-effort base) in the preceding gen_enzyme_decl function. - // In all three cases, we can return `std::hint::black_box(::default())`. - if x.ret_activity == DiffActivity::Const { - // Here we call the primal function, since our dummy function has the same return - // type due to the Const return activity. - exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![exprs]); - } else { - let q = QSelf { ty: d_ret_ty, path_span: span, position: 0 }; - let y = ExprKind::Path( - Some(P(q)), - ecx.path_ident(span, Ident::with_dummy_span(kw::Default)), - ); - let default_call_expr = ecx.expr(span, y); - let default_call_expr = - ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]); - exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![default_call_expr]); - } - } else if x.mode.is_rev() { - if x.width == 1 { - // We either have `-> ArbitraryType` or `-> (ArbitraryType, repeated_float_scalars)`. - match d_ret_ty.kind { - TyKind::Tup(ref args) => { - // We have a tuple return type. We need to create a tuple of the same size - // and fill it with default values. - let mut exprs2 = thin_vec![exprs]; - for arg in args.iter().skip(1) { - let arg = arg.kind.is_simple_path().unwrap(); - let tmp = ecx.def_site_path(&[arg, kw::Default]); - let default_call_expr = ecx.expr_path(ecx.path(span, tmp)); - let default_call_expr = - ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]); - exprs2.push(default_call_expr); - } - exprs = ecx.expr_tuple(new_decl_span, exprs2); - } - _ => { - // Interestingly, even the `-> ArbitraryType` case - // ends up getting matched and handled correctly above, - // so we don't have to handle any other case for now. - panic!("Unsupported return type: {:?}", d_ret_ty); - } + GenericParamKind::Const { .. } => { + let expr = ecx.expr_path(ast::Path::from_ident(p.ident)); + let anon_const = AnonConst { id: ast::DUMMY_NODE_ID, value: expr }; + Some(AngleBracketedArg::Arg(GenericArg::Const(anon_const))) } - } - exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![exprs]); - } else { - unreachable!("Unsupported mode: {:?}", x.mode); - } + GenericParamKind::Lifetime { .. } => None, + }) + .collect::>(); - body.stmts.push(ecx.stmt_expr(exprs)); + let args: AngleBracketedArgs = AngleBracketedArgs { span, args: generic_args }; - body - } + let segment = PathSegment { + ident, + id: ast::DUMMY_NODE_ID, + args: Some(Box::new(GenericArgs::AngleBracketed(args))), + }; - fn gen_primal_call( - ecx: &ExtCtxt<'_>, - span: Span, - primal: Ident, - idents: &[Ident], - generics: &Generics, - ) -> P { - let has_self = idents.len() > 0 && idents[0].name == kw::SelfLower; - - if has_self { - let args: ThinVec<_> = - idents[1..].iter().map(|arg| ecx.expr_path(ecx.path_ident(span, *arg))).collect(); - let self_expr = ecx.expr_self(span); - ecx.expr_method_call(span, self_expr, primal, args) + let segments = if is_impl { + thin_vec![ + PathSegment { ident: Ident::from_str("Self"), id: ast::DUMMY_NODE_ID, args: None }, + segment, + ] } else { - let args: ThinVec<_> = - idents.iter().map(|arg| ecx.expr_path(ecx.path_ident(span, *arg))).collect(); - let mut primal_path = ecx.path_ident(span, primal); - - let is_generic = !generics.params.is_empty(); - - match (is_generic, primal_path.segments.last_mut()) { - (true, Some(function_path)) => { - let primal_generic_types = generics - .params - .iter() - .filter(|param| matches!(param.kind, ast::GenericParamKind::Type { .. })); - - let generated_generic_types = primal_generic_types - .map(|type_param| { - let generic_param = TyKind::Path( - None, - ast::Path { - span, - segments: thin_vec![ast::PathSegment { - ident: type_param.ident, - args: None, - id: ast::DUMMY_NODE_ID, - }], - tokens: None, - }, - ); - - ast::AngleBracketedArg::Arg(ast::GenericArg::Type(P(ast::Ty { - id: type_param.id, - span, - kind: generic_param, - tokens: None, - }))) - }) - .collect(); - - function_path.args = - Some(P(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { - span, - args: generated_generic_types, - }))); - } - _ => {} - } + thin_vec![segment] + }; - let primal_call_expr = ecx.expr_path(primal_path); - ecx.expr_call(span, primal_call_expr, args) - } + let path = Path { span, segments, tokens: None }; + + ecx.expr_path(path) } // Generate the new function declaration. Const arguments are kept as is. Duplicated arguments must @@ -779,7 +603,7 @@ mod llvm_enzyme { sig: &ast::FnSig, x: &AutoDiffAttrs, span: Span, - ) -> (ast::FnSig, Vec, Vec, bool) { + ) -> ast::FnSig { let dcx = ecx.sess.dcx(); let has_ret = has_ret(&sig.decl.output); let sig_args = sig.decl.inputs.len() + if has_ret { 1 } else { 0 }; @@ -791,7 +615,7 @@ mod llvm_enzyme { found: num_activities, }); // This is not the right signature, but we can continue parsing. - return (sig.clone(), vec![], vec![], true); + return sig.clone(); } assert!(sig.decl.inputs.len() == x.input_activity.len()); assert!(has_ret == x.has_ret_activity()); @@ -834,7 +658,7 @@ mod llvm_enzyme { if errors { // This is not the right signature, but we can continue parsing. - return (sig.clone(), new_inputs, idents, true); + return sig.clone(); } let unsafe_activities = x @@ -856,7 +680,7 @@ mod llvm_enzyme { for i in 0..x.width { let mut shadow_arg = arg.clone(); // We += into the shadow in reverse mode. - shadow_arg.ty = P(assure_mut_ref(&arg.ty)); + shadow_arg.ty = Box::new(assure_mut_ref(&arg.ty)); let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind { ident.name } else { @@ -866,7 +690,7 @@ mod llvm_enzyme { let name: String = format!("d{}_{}", old_name, i); new_inputs.push(name.clone()); let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span); - shadow_arg.pat = P(ast::Pat { + shadow_arg.pat = Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Ident(BindingMode::NONE, ident, None), span: shadow_arg.pat.span, @@ -898,7 +722,7 @@ mod llvm_enzyme { let name: String = format!("b{}_{}", old_name, i); new_inputs.push(name.clone()); let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span); - shadow_arg.pat = P(ast::Pat { + shadow_arg.pat = Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Ident(BindingMode::NONE, ident, None), span: shadow_arg.pat.span, @@ -942,7 +766,7 @@ mod llvm_enzyme { let shadow_arg = ast::Param { attrs: ThinVec::new(), ty: ty.clone(), - pat: P(ast::Pat { + pat: Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Ident(BindingMode::NONE, ident, None), span: ty.span, @@ -966,7 +790,12 @@ mod llvm_enzyme { FnRetTy::Default(span) => { // We want to return std::hint::black_box(()). let kind = TyKind::Tup(ThinVec::new()); - let ty = P(rustc_ast::Ty { kind, id: ast::DUMMY_NODE_ID, span, tokens: None }); + let ty = Box::new(rustc_ast::Ty { + kind, + id: ast::DUMMY_NODE_ID, + span, + tokens: None, + }); d_decl.output = FnRetTy::Ty(ty.clone()); assert!(matches!(x.ret_activity, DiffActivity::None)); // this won't be used below, so any type would be fine. @@ -987,7 +816,7 @@ mod llvm_enzyme { }; TyKind::Array(ty.clone(), anon_const) }; - let ty = P(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }); + let ty = Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }); d_decl.output = FnRetTy::Ty(ty); } if matches!(x.ret_activity, DiffActivity::DualOnly | DiffActivity::DualvOnly) { @@ -1000,7 +829,8 @@ mod llvm_enzyme { value: ecx.expr_usize(span, x.width as usize), }; let kind = TyKind::Array(ty.clone(), anon_const); - let ty = P(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }); + let ty = + Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }); d_decl.output = FnRetTy::Ty(ty); } } @@ -1022,14 +852,14 @@ mod llvm_enzyme { act_ret.insert(0, ty.clone()); } let kind = TyKind::Tup(act_ret); - P(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }) + Box::new(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None }) } FnRetTy::Default(span) => { if act_ret.len() == 1 { act_ret[0].clone() } else { let kind = TyKind::Tup(act_ret.iter().map(|arg| arg.clone()).collect()); - P(rustc_ast::Ty { kind, id: ast::DUMMY_NODE_ID, span, tokens: None }) + Box::new(rustc_ast::Ty { kind, id: ast::DUMMY_NODE_ID, span, tokens: None }) } } }; @@ -1042,7 +872,7 @@ mod llvm_enzyme { } let d_sig = FnSig { header: d_header, decl: d_decl, span }; trace!("Generated signature: {:?}", d_sig); - (d_sig, new_inputs, idents, false) + d_sig } } diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs index 5f203dd5d113..f7d8f4aa7835 100644 --- a/compiler/rustc_builtin_macros/src/cfg_accessible.rs +++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs @@ -44,7 +44,7 @@ impl MultiItemModifier for Expander { item: Annotatable, _is_derive_const: bool, ) -> ExpandResult, Annotatable> { - let template = AttributeTemplate { list: Some("path"), ..Default::default() }; + let template = AttributeTemplate { list: Some(&["path"]), ..Default::default() }; validate_attr::check_builtin_meta_item( &ecx.sess.psess, meta_item, diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index ec3b87467a9b..dd770fe5f1a1 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -2,7 +2,6 @@ use core::ops::ControlFlow; use rustc_ast as ast; use rustc_ast::mut_visit::MutVisitor; -use rustc_ast::ptr::P; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{Attribute, HasAttrs, HasTokens, NodeId, mut_visit, visit}; use rustc_errors::PResult; @@ -132,7 +131,7 @@ impl CfgEval<'_> { let stmt = parser .parse_stmt_without_recovery(false, ForceCollect::Yes, false)? .unwrap(); - Annotatable::Stmt(P(self.flat_map_stmt(stmt).pop().unwrap())) + Annotatable::Stmt(Box::new(self.flat_map_stmt(stmt).pop().unwrap())) } Annotatable::Expr(_) => { let mut expr = parser.parse_expr_force_collect()?; @@ -166,7 +165,7 @@ impl MutVisitor for CfgEval<'_> { mut_visit::walk_expr(self, expr); } - fn filter_map_expr(&mut self, expr: P) -> Option> { + fn filter_map_expr(&mut self, expr: Box) -> Option> { let mut expr = configure!(self, expr); mut_visit::walk_expr(self, &mut expr); Some(expr) @@ -185,24 +184,24 @@ impl MutVisitor for CfgEval<'_> { mut_visit::walk_flat_map_stmt(self, stmt) } - fn flat_map_item(&mut self, item: P) -> SmallVec<[P; 1]> { + fn flat_map_item(&mut self, item: Box) -> SmallVec<[Box; 1]> { let item = configure!(self, item); mut_visit::walk_flat_map_item(self, item) } fn flat_map_assoc_item( &mut self, - item: P, + item: Box, ctxt: AssocCtxt, - ) -> SmallVec<[P; 1]> { + ) -> SmallVec<[Box; 1]> { let item = configure!(self, item); mut_visit::walk_flat_map_assoc_item(self, item, ctxt) } fn flat_map_foreign_item( &mut self, - foreign_item: P, - ) -> SmallVec<[P; 1]> { + foreign_item: Box, + ) -> SmallVec<[Box; 1]> { let foreign_item = configure!(self, foreign_item); mut_visit::walk_flat_map_foreign_item(self, foreign_item) } diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index fd2d740c0203..8885017b930d 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ExprKind, LitIntType, LitKind, StrStyle, UintTy, token}; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult}; @@ -90,7 +89,7 @@ fn handle_array_element( cx: &ExtCtxt<'_>, guar: &mut Option, missing_literals: &mut Vec, - expr: &P, + expr: &Box, ) -> Option { let dcx = cx.dcx(); diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index e259f5b3955b..a33eca43de59 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -34,8 +34,10 @@ impl MultiItemModifier for Expander { let (sess, features) = (ecx.sess, ecx.ecfg.features); let result = ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| { - let template = - AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; + let template = AttributeTemplate { + list: Some(&["Trait1, Trait2, ..."]), + ..Default::default() + }; validate_attr::check_builtin_meta_item( &sess.psess, meta_item, diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs index 990835fa2773..a0f71a1868b5 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::{Span, sym}; @@ -119,7 +118,7 @@ fn get_substructure_equality_expr( cx: &ExtCtxt<'_>, span: Span, substructure: &Substructure<'_>, -) -> P { +) -> Box { use SubstructureFields::*; match substructure.fields { @@ -180,7 +179,7 @@ fn get_substructure_equality_expr( /// /// Panics if there are not exactly two arguments to compare (should be `self` /// and `other`). -fn get_field_equality_expr(cx: &ExtCtxt<'_>, field: &FieldInfo) -> P { +fn get_field_equality_expr(cx: &ExtCtxt<'_>, field: &FieldInfo) -> Box { let [rhs] = &field.other_selflike_exprs[..] else { cx.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(PartialEq)`"); }; @@ -198,7 +197,7 @@ fn get_field_equality_expr(cx: &ExtCtxt<'_>, field: &FieldInfo) -> P { /// This is used to strip away any number of leading `&` from an expression /// (e.g., `&&&T` becomes `T`). Only removes immutable references; mutable /// references are preserved. -fn peel_refs(mut expr: &P) -> P { +fn peel_refs(mut expr: &Box) -> Box { while let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = &expr.kind { expr = &inner; } @@ -210,7 +209,7 @@ fn peel_refs(mut expr: &P) -> P { /// /// If the given expression is a block, it is wrapped in parentheses; otherwise, /// it is returned unchanged. -fn wrap_block_expr(cx: &ExtCtxt<'_>, expr: P) -> P { +fn wrap_block_expr(cx: &ExtCtxt<'_>, expr: Box) -> Box { if matches!(&expr.kind, ExprKind::Block(..)) { return cx.expr_paren(expr.span, expr); } diff --git a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs index 6082e376435a..75db5d77783e 100644 --- a/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs +++ b/compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs @@ -108,11 +108,7 @@ pub(crate) fn expand_deriving_coerce_pointee( cx.item( span, attrs.clone(), - ast::ItemKind::Impl(Box::new(ast::Impl { - safety: ast::Safety::Default, - polarity: ast::ImplPolarity::Positive, - defaultness: ast::Defaultness::Final, - constness: ast::Const::No, + ast::ItemKind::Impl(ast::Impl { generics: Generics { params: generics .params @@ -137,10 +133,16 @@ pub(crate) fn expand_deriving_coerce_pointee( where_clause: generics.where_clause.clone(), span: generics.span, }, - of_trait: Some(trait_ref), + of_trait: Some(Box::new(ast::TraitImplHeader { + safety: ast::Safety::Default, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: ast::Const::No, + trait_ref, + })), self_ty: self_type.clone(), items: ThinVec::new(), - })), + }), ), )); } @@ -152,16 +154,18 @@ pub(crate) fn expand_deriving_coerce_pointee( let item = cx.item( span, attrs.clone(), - ast::ItemKind::Impl(Box::new(ast::Impl { - safety: ast::Safety::Default, - polarity: ast::ImplPolarity::Positive, - defaultness: ast::Defaultness::Final, - constness: ast::Const::No, + ast::ItemKind::Impl(ast::Impl { generics, - of_trait: Some(trait_ref), + of_trait: Some(Box::new(ast::TraitImplHeader { + safety: ast::Safety::Default, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: ast::Const::No, + trait_ref, + })), self_ty: self_type.clone(), items: ThinVec::new(), - })), + }), ); push(Annotatable::Item(item)); }; diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 1d63ce7d5fd2..597af0e09c06 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -94,7 +94,7 @@ fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> field: &FieldInfo, index: usize, len: usize, - ) -> ast::ptr::P { + ) -> Box { if index < len - 1 { field.self_expr.clone() } else { diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs index b4e2d27fed33..2462114ec24a 100644 --- a/compiler/rustc_builtin_macros/src/deriving/default.rs +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -56,7 +56,7 @@ pub(crate) fn expand_deriving_default( trait_def.expand(cx, mitem, item, push) } -fn default_call(cx: &ExtCtxt<'_>, span: Span) -> ast::ptr::P { +fn default_call(cx: &ExtCtxt<'_>, span: Span) -> Box { // Note that `kw::Default` is "default" and `sym::Default` is "Default"! let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]); cx.expr_call_global(span, default_ident, ThinVec::new()) diff --git a/compiler/rustc_builtin_macros/src/deriving/from.rs b/compiler/rustc_builtin_macros/src/deriving/from.rs new file mode 100644 index 000000000000..ab25de7c9175 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/from.rs @@ -0,0 +1,133 @@ +use rustc_ast as ast; +use rustc_ast::{ItemKind, VariantData}; +use rustc_errors::MultiSpan; +use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; +use rustc_span::{Ident, Span, kw, sym}; +use thin_vec::thin_vec; + +use crate::deriving::generic::ty::{Bounds, Path, PathKind, Ty}; +use crate::deriving::generic::{ + BlockOrExpr, FieldlessVariantsStrategy, MethodDef, SubstructureFields, TraitDef, + combine_substructure, +}; +use crate::deriving::pathvec_std; +use crate::errors; + +/// Generate an implementation of the `From` trait, provided that `item` +/// is a struct or a tuple struct with exactly one field. +pub(crate) fn expand_deriving_from( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &ast::MetaItem, + annotatable: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { + let Annotatable::Item(item) = &annotatable else { + cx.dcx().bug("derive(From) used on something else than an item"); + }; + + let err_span = || { + let item_span = item.kind.ident().map(|ident| ident.span).unwrap_or(item.span); + MultiSpan::from_spans(vec![span, item_span]) + }; + + // `#[derive(From)]` is currently usable only on structs with exactly one field. + let field = match &item.kind { + ItemKind::Struct(_, _, data) => { + if let [field] = data.fields() { + Ok(field.clone()) + } else { + let guar = cx.dcx().emit_err(errors::DeriveFromWrongFieldCount { + span: err_span(), + multiple_fields: data.fields().len() > 1, + }); + Err(guar) + } + } + ItemKind::Enum(_, _, _) | ItemKind::Union(_, _, _) => { + let guar = cx.dcx().emit_err(errors::DeriveFromWrongTarget { + span: err_span(), + kind: &format!("{} {}", item.kind.article(), item.kind.descr()), + }); + Err(guar) + } + _ => cx.dcx().bug("Invalid derive(From) ADT input"), + }; + + let from_type = Ty::AstTy(match field { + Ok(ref field) => field.ty.clone(), + Err(guar) => cx.ty(span, ast::TyKind::Err(guar)), + }); + + let path = + Path::new_(pathvec_std!(convert::From), vec![Box::new(from_type.clone())], PathKind::Std); + + // Generate code like this: + // + // struct S(u32); + // #[automatically_derived] + // impl ::core::convert::From for S { + // #[inline] + // fn from(value: u32) -> S { + // Self(value) + // } + // } + let from_trait_def = TraitDef { + span, + path, + skip_path_as_bound: true, + needs_copy_as_bound_if_packed: false, + additional_bounds: Vec::new(), + supports_unions: false, + methods: vec![MethodDef { + name: sym::from, + generics: Bounds { bounds: vec![] }, + explicit_self: false, + nonself_args: vec![(from_type, sym::value)], + ret_ty: Ty::Self_, + attributes: thin_vec![cx.attr_word(sym::inline, span)], + fieldless_variants_strategy: FieldlessVariantsStrategy::Default, + combine_substructure: combine_substructure(Box::new(|cx, span, substructure| { + let field = match field { + Ok(ref field) => field, + Err(guar) => { + return BlockOrExpr::new_expr(DummyResult::raw_expr(span, Some(guar))); + } + }; + + let self_kw = Ident::new(kw::SelfUpper, span); + let expr: Box = match substructure.fields { + SubstructureFields::StaticStruct(variant, _) => match variant { + // Self { field: value } + VariantData::Struct { .. } => cx.expr_struct_ident( + span, + self_kw, + thin_vec![cx.field_imm( + span, + field.ident.unwrap(), + cx.expr_ident(span, Ident::new(sym::value, span)) + )], + ), + // Self(value) + VariantData::Tuple(_, _) => cx.expr_call_ident( + span, + self_kw, + thin_vec![cx.expr_ident(span, Ident::new(sym::value, span))], + ), + variant => { + cx.dcx().bug(format!("Invalid derive(From) ADT variant: {variant:?}")); + } + }, + _ => cx.dcx().bug("Invalid derive(From) ADT input"), + }; + BlockOrExpr::new_expr(expr) + })), + }], + associated_types: Vec::new(), + is_const, + is_staged_api_crate: cx.ecfg.features.staged_api(), + }; + + from_trait_def.expand(cx, mitem, annotatable, push); +} diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index fc9eed24ee08..3fcf9da94507 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -180,7 +180,6 @@ use std::{iter, vec}; pub(crate) use StaticFields::*; pub(crate) use SubstructureFields::*; -use rustc_ast::ptr::P; use rustc_ast::token::{IdentIsRaw, LitKind, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenTree}; use rustc_ast::{ @@ -272,7 +271,7 @@ pub(crate) struct Substructure<'a> { pub type_ident: Ident, /// Verbatim access to any non-selflike arguments, i.e. arguments that /// don't have type `&Self`. - pub nonselflike_args: &'a [P], + pub nonselflike_args: &'a [Box], pub fields: &'a SubstructureFields<'a>, } @@ -284,10 +283,10 @@ pub(crate) struct FieldInfo { pub name: Option, /// The expression corresponding to this field of `self` /// (specifically, a reference to it). - pub self_expr: P, + pub self_expr: Box, /// The expressions corresponding to references to this field in /// the other selflike arguments. - pub other_selflike_exprs: Vec>, + pub other_selflike_exprs: Vec>, pub maybe_scalar: bool, } @@ -323,7 +322,7 @@ pub(crate) enum SubstructureFields<'a> { /// The discriminant of an enum. The first field is a `FieldInfo` for the discriminants, as /// if they were fields. The second field is the expression to combine the /// discriminant expression with; it will be `None` if no match is necessary. - EnumDiscr(FieldInfo, Option>), + EnumDiscr(FieldInfo, Option>), /// A static method where `Self` is a struct. StaticStruct(&'a ast::VariantData, StaticFields), @@ -345,7 +344,7 @@ pub(crate) fn combine_substructure( struct TypeParameter { bound_generic_params: ThinVec, - ty: P, + ty: Box, } /// The code snippets built up for derived code are sometimes used as blocks @@ -354,23 +353,23 @@ struct TypeParameter { /// avoiding the insertion of any unnecessary blocks. /// /// The statements come before the expression. -pub(crate) struct BlockOrExpr(ThinVec, Option>); +pub(crate) struct BlockOrExpr(ThinVec, Option>); impl BlockOrExpr { pub(crate) fn new_stmts(stmts: ThinVec) -> BlockOrExpr { BlockOrExpr(stmts, None) } - pub(crate) fn new_expr(expr: P) -> BlockOrExpr { + pub(crate) fn new_expr(expr: Box) -> BlockOrExpr { BlockOrExpr(ThinVec::new(), Some(expr)) } - pub(crate) fn new_mixed(stmts: ThinVec, expr: Option>) -> BlockOrExpr { + pub(crate) fn new_mixed(stmts: ThinVec, expr: Option>) -> BlockOrExpr { BlockOrExpr(stmts, expr) } // Converts it into a block. - fn into_block(mut self, cx: &ExtCtxt<'_>, span: Span) -> P { + fn into_block(mut self, cx: &ExtCtxt<'_>, span: Span) -> Box { if let Some(expr) = self.1 { self.0.push(cx.stmt_expr(expr)); } @@ -378,7 +377,7 @@ impl BlockOrExpr { } // Converts it into an expression. - fn into_expr(self, cx: &ExtCtxt<'_>, span: Span) -> P { + fn into_expr(self, cx: &ExtCtxt<'_>, span: Span) -> Box { if self.0.is_empty() { match self.1 { None => cx.expr_block(cx.block(span, ThinVec::new())), @@ -432,7 +431,7 @@ fn find_type_parameters( { self.type_params.push(TypeParameter { bound_generic_params: self.bound_generic_params_stack.clone(), - ty: P(ty.clone()), + ty: Box::new(ty.clone()), }); } @@ -544,7 +543,7 @@ impl<'a> TraitDef<'a> { }) .cloned(), ); - push(Annotatable::Item(P(ast::Item { attrs, ..(*newitem).clone() }))) + push(Annotatable::Item(Box::new(ast::Item { attrs, ..(*newitem).clone() }))) } _ => unreachable!(), } @@ -590,15 +589,15 @@ impl<'a> TraitDef<'a> { cx: &ExtCtxt<'_>, type_ident: Ident, generics: &Generics, - field_tys: Vec>, - methods: Vec>, + field_tys: Vec>, + methods: Vec>, is_packed: bool, - ) -> P { + ) -> Box { let trait_path = self.path.to_path(cx, self.span, type_ident, generics); // Transform associated types from `deriving::ty::Ty` into `ast::AssocItem` let associated_types = self.associated_types.iter().map(|&(ident, ref type_def)| { - P(ast::AssocItem { + Box::new(ast::AssocItem { id: ast::DUMMY_NODE_ID, span: self.span, vis: ast::Visibility { @@ -827,21 +826,25 @@ impl<'a> TraitDef<'a> { ) } - let opt_trait_ref = Some(trait_ref); - cx.item( self.span, attrs, - ast::ItemKind::Impl(Box::new(ast::Impl { - safety: ast::Safety::Default, - polarity: ast::ImplPolarity::Positive, - defaultness: ast::Defaultness::Final, - constness: if self.is_const { ast::Const::Yes(DUMMY_SP) } else { ast::Const::No }, + ast::ItemKind::Impl(ast::Impl { generics: trait_generics, - of_trait: opt_trait_ref, + of_trait: Some(Box::new(ast::TraitImplHeader { + safety: ast::Safety::Default, + polarity: ast::ImplPolarity::Positive, + defaultness: ast::Defaultness::Final, + constness: if self.is_const { + ast::Const::Yes(DUMMY_SP) + } else { + ast::Const::No + }, + trait_ref, + })), self_ty: self_type, items: methods.into_iter().chain(associated_types).collect(), - })), + }), ) } @@ -853,8 +856,8 @@ impl<'a> TraitDef<'a> { generics: &Generics, from_scratch: bool, is_packed: bool, - ) -> P { - let field_tys: Vec> = + ) -> Box { + let field_tys: Vec> = struct_def.fields().iter().map(|field| field.ty.clone()).collect(); let methods = self @@ -906,7 +909,7 @@ impl<'a> TraitDef<'a> { type_ident: Ident, generics: &Generics, from_scratch: bool, - ) -> P { + ) -> Box { let mut field_tys = Vec::new(); for variant in &enum_def.variants { @@ -962,7 +965,7 @@ impl<'a> MethodDef<'a> { cx: &ExtCtxt<'_>, trait_: &TraitDef<'_>, type_ident: Ident, - nonselflike_args: &[P], + nonselflike_args: &[Box], fields: &SubstructureFields<'_>, ) -> BlockOrExpr { let span = trait_.span; @@ -978,7 +981,7 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'_>, generics: &Generics, type_ident: Ident, - ) -> P { + ) -> Box { self.ret_ty.to_ty(cx, trait_.span, type_ident, generics) } @@ -999,7 +1002,8 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'_>, type_ident: Ident, generics: &Generics, - ) -> (Option, ThinVec>, Vec>, Vec<(Ident, P)>) { + ) -> (Option, ThinVec>, Vec>, Vec<(Ident, Box)>) + { let mut selflike_args = ThinVec::new(); let mut nonselflike_args = Vec::new(); let mut nonself_arg_tys = Vec::new(); @@ -1036,9 +1040,9 @@ impl<'a> MethodDef<'a> { type_ident: Ident, generics: &Generics, explicit_self: Option, - nonself_arg_tys: Vec<(Ident, P)>, + nonself_arg_tys: Vec<(Ident, Box)>, body: BlockOrExpr, - ) -> P { + ) -> Box { let span = trait_.span; // Create the generics that aren't for `Self`. let fn_generics = self.generics.to_generics(cx, span, type_ident, generics); @@ -1065,7 +1069,7 @@ impl<'a> MethodDef<'a> { let defaultness = ast::Defaultness::Final; // Create the method. - P(ast::AssocItem { + Box::new(ast::AssocItem { id: ast::DUMMY_NODE_ID, attrs: self.attributes.clone(), span, @@ -1128,8 +1132,8 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'b>, struct_def: &'b VariantData, type_ident: Ident, - selflike_args: &[P], - nonselflike_args: &[P], + selflike_args: &[Box], + nonselflike_args: &[Box], is_packed: bool, ) -> BlockOrExpr { assert!(selflike_args.len() == 1 || selflike_args.len() == 2); @@ -1151,7 +1155,7 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'_>, struct_def: &VariantData, type_ident: Ident, - nonselflike_args: &[P], + nonselflike_args: &[Box], ) -> BlockOrExpr { let summary = trait_.summarise_struct(cx, struct_def); @@ -1205,8 +1209,8 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'b>, enum_def: &'b EnumDef, type_ident: Ident, - mut selflike_args: ThinVec>, - nonselflike_args: &[P], + mut selflike_args: ThinVec>, + nonselflike_args: &[Box], ) -> BlockOrExpr { assert!( !selflike_args.is_empty(), @@ -1418,7 +1422,7 @@ impl<'a> MethodDef<'a> { // ... // _ => ::core::intrinsics::unreachable(), // } - let get_match_expr = |mut selflike_args: ThinVec>| { + let get_match_expr = |mut selflike_args: ThinVec>| { let match_arg = if selflike_args.len() == 1 { selflike_args.pop().unwrap() } else { @@ -1454,7 +1458,7 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'_>, enum_def: &EnumDef, type_ident: Ident, - nonselflike_args: &[P], + nonselflike_args: &[Box], ) -> BlockOrExpr { self.call_substructure_method( cx, @@ -1503,7 +1507,7 @@ impl<'a> TraitDef<'a> { struct_def: &'a VariantData, prefixes: &[String], by_ref: ByRef, - ) -> ThinVec> { + ) -> ThinVec> { prefixes .iter() .map(|prefix| { @@ -1558,7 +1562,7 @@ impl<'a> TraitDef<'a> { fn create_fields(&self, struct_def: &'a VariantData, mk_exprs: F) -> Vec where - F: Fn(usize, &ast::FieldDef, Span) -> Vec>, + F: Fn(usize, &ast::FieldDef, Span) -> Vec>, { struct_def .fields() @@ -1606,7 +1610,7 @@ impl<'a> TraitDef<'a> { fn create_struct_field_access_fields( &self, cx: &ExtCtxt<'_>, - selflike_args: &[P], + selflike_args: &[Box], struct_def: &'a VariantData, is_packed: bool, ) -> Vec { @@ -1651,7 +1655,7 @@ pub(crate) enum CsFold<'a> { /// The combination of two field expressions. E.g. for `PartialEq::eq` this /// is something like ` && `. - Combine(Span, P, P), + Combine(Span, Box, Box), // The fallback case for a struct or enum variant with no fields. Fieldless, @@ -1665,9 +1669,9 @@ pub(crate) fn cs_fold( trait_span: Span, substructure: &Substructure<'_>, mut f: F, -) -> P +) -> Box where - F: FnMut(&ExtCtxt<'_>, CsFold<'_>) -> P, + F: FnMut(&ExtCtxt<'_>, CsFold<'_>) -> Box, { match substructure.fields { EnumMatching(.., all_fields) | Struct(_, all_fields) => { diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs index f34a6ae1d982..1458553d4925 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -2,8 +2,7 @@ //! when specifying impls to be derived. pub(crate) use Ty::*; -use rustc_ast::ptr::P; -use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind}; +use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind, TyKind}; use rustc_expand::base::ExtCtxt; use rustc_span::source_map::respan; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw}; @@ -41,7 +40,7 @@ impl Path { span: Span, self_ty: Ident, self_generics: &Generics, - ) -> P { + ) -> Box { cx.ty_path(self.to_path(cx, span, self_ty, self_generics)) } pub(crate) fn to_path( @@ -66,7 +65,7 @@ impl Path { } } -/// A type. Supports pointers, Self, and literals. +/// A type. Supports pointers, Self, literals, unit or an arbitrary AST path. #[derive(Clone)] pub(crate) enum Ty { Self_, @@ -77,6 +76,8 @@ pub(crate) enum Ty { Path(Path), /// For () return types. Unit, + /// An arbitrary type. + AstTy(Box), } pub(crate) fn self_ref() -> Ty { @@ -90,7 +91,7 @@ impl Ty { span: Span, self_ty: Ident, self_generics: &Generics, - ) -> P { + ) -> Box { match self { Ref(ty, mutbl) => { let raw_ty = ty.to_ty(cx, span, self_ty, self_generics); @@ -102,6 +103,7 @@ impl Ty { let ty = ast::TyKind::Tup(ThinVec::new()); cx.ty(span, ty) } + AstTy(ty) => ty.clone(), } } @@ -133,6 +135,10 @@ impl Ty { cx.path_all(span, false, vec![self_ty], params) } Path(p) => p.to_path(cx, span, self_ty, generics), + AstTy(ty) => match &ty.kind { + TyKind::Path(_, path) => path.clone(), + _ => cx.dcx().span_bug(span, "non-path in a path in generic `derive`"), + }, Ref(..) => cx.dcx().span_bug(span, "ref in a path in generic `derive`"), Unit => cx.dcx().span_bug(span, "unit in a path in generic `derive`"), } @@ -192,7 +198,7 @@ impl Bounds { } } -pub(crate) fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P, ast::ExplicitSelf) { +pub(crate) fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (Box, ast::ExplicitSelf) { // This constructs a fresh `self` path. let self_path = cx.expr_self(span); let self_ty = respan(span, SelfKind::Region(None, ast::Mutability::Not)); diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs index e45d09b57960..cee6952fa346 100644 --- a/compiler/rustc_builtin_macros/src/deriving/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -1,7 +1,6 @@ //! The compiler code necessary to implement the `#[derive]` extensions. use rustc_ast as ast; -use rustc_ast::ptr::P; use rustc_ast::{GenericArg, MetaItem}; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier}; use rustc_span::{Span, Symbol, sym}; @@ -24,6 +23,7 @@ pub(crate) mod clone; pub(crate) mod coerce_pointee; pub(crate) mod debug; pub(crate) mod default; +pub(crate) mod from; pub(crate) mod hash; #[path = "cmp/eq.rs"] @@ -66,7 +66,7 @@ impl MultiItemModifier for BuiltinDerive { &mut |a| { // Cannot use 'ecx.stmt_item' here, because we need to pass 'ecx' // to the function - items.push(Annotatable::Stmt(P(ast::Stmt { + items.push(Annotatable::Stmt(Box::new(ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Item(a.expect_item()), span, @@ -91,20 +91,20 @@ fn call_intrinsic( cx: &ExtCtxt<'_>, span: Span, intrinsic: Symbol, - args: ThinVec>, -) -> P { + args: ThinVec>, +) -> Box { let span = cx.with_def_site_ctxt(span); let path = cx.std_path(&[sym::intrinsics, intrinsic]); cx.expr_call_global(span, path, args) } /// Constructs an expression that calls the `unreachable` intrinsic. -fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> P { +fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> Box { let span = cx.with_def_site_ctxt(span); let path = cx.std_path(&[sym::intrinsics, sym::unreachable]); let call = cx.expr_call_global(span, path, ThinVec::new()); - cx.expr_block(P(ast::Block { + cx.expr_block(Box::new(ast::Block { stmts: thin_vec![cx.stmt_expr(call)], id: ast::DUMMY_NODE_ID, rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated), @@ -116,7 +116,7 @@ fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> P { fn assert_ty_bounds( cx: &ExtCtxt<'_>, stmts: &mut ThinVec, - ty: P, + ty: Box, span: Span, assert_path: &[Symbol], ) { diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs index ccfcc3079ebf..08f555b9e52f 100644 --- a/compiler/rustc_builtin_macros/src/edition_panic.rs +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::*; @@ -48,7 +47,7 @@ fn expand<'cx>( ExpandResult::Ready(MacEager::expr( cx.expr( sp, - ExprKind::MacCall(P(MacCall { + ExprKind::MacCall(Box::new(MacCall { path: Path { span: sp, segments: cx @@ -58,7 +57,7 @@ fn expand<'cx>( .collect(), tokens: None, }, - args: P(DelimArgs { + args: Box::new(DelimArgs { dspan: DelimSpan::from_single(sp), delim: Delimiter::Parenthesis, tokens: tts, diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 6bcf4d3e0a2e..54e8f7503377 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -446,6 +446,24 @@ pub(crate) struct DefaultHasArg { pub(crate) span: Span, } +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_from_wrong_target)] +#[note(builtin_macros_derive_from_usage_note)] +pub(crate) struct DeriveFromWrongTarget<'a> { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) kind: &'a str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_from_wrong_field_count)] +#[note(builtin_macros_derive_from_usage_note)] +pub(crate) struct DeriveFromWrongFieldCount { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) multiple_fields: bool, +} + #[derive(Diagnostic)] #[diag(builtin_macros_derive_macro_call)] pub(crate) struct DeriveMacroCall { @@ -905,14 +923,6 @@ pub(crate) struct TakesNoArguments<'a> { pub name: &'a str, } -#[derive(Diagnostic)] -#[diag(builtin_macros_proc_macro_attribute_only_be_used_on_bare_functions)] -pub(crate) struct AttributeOnlyBeUsedOnBareFunctions<'a> { - #[primary_span] - pub span: Span, - pub path: &'a str, -} - #[derive(Diagnostic)] #[diag(builtin_macros_proc_macro_attribute_only_usable_with_crate_type)] pub(crate) struct AttributeOnlyUsableWithCrateType<'a> { diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 6785cb6aef5a..ec613b7b7103 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,7 +1,6 @@ use std::ops::Range; use parse::Position::ArgumentNamed; -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{ Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, @@ -45,7 +44,7 @@ use PositionUsedAs::*; #[derive(Debug)] struct MacroInput { - fmtstr: P, + fmtstr: Box, args: FormatArguments, /// Whether the first argument was a string literal or a result from eager macro expansion. /// If it's not a string literal, we disallow implicit argument capturing. @@ -1018,7 +1017,7 @@ fn expand_format_args_impl<'cx>( }; match mac { Ok(format_args) => { - MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) + MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(Box::new(format_args)))) } Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))), } diff --git a/compiler/rustc_builtin_macros/src/format_foreign.rs b/compiler/rustc_builtin_macros/src/format_foreign.rs index 13d5b42942ac..3e5a26c05568 100644 --- a/compiler/rustc_builtin_macros/src/format_foreign.rs +++ b/compiler/rustc_builtin_macros/src/format_foreign.rs @@ -346,18 +346,18 @@ pub(crate) mod printf { // ```regex // (?x) // ^ % - // (?: (?P \d+) \$ )? - // (?P [-+ 0\#']* ) - // (?P \d+ | \* (?: (?P \d+) \$ )? )? - // (?: \. (?P \d+ | \* (?: (?P \d+) \$ )? ) )? - // (?P + // (?: (?Box \d+) \$ )? + // (?Box [-+ 0\#']* ) + // (?Box \d+ | \* (?: (?Box \d+) \$ )? )? + // (?: \. (?Box \d+ | \* (?: (?Box \d+) \$ )? ) )? + // (?Box // # Standard // hh | h | ll | l | L | z | j | t // // # Other // | I32 | I64 | I | q // )? - // (?P . ) + // (?Box . ) // ``` // Used to establish the full span at the end. diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index 4b1958bce322..f14b1920722f 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -1,7 +1,6 @@ use rustc_ast::expand::allocator::{ ALLOCATOR_METHODS, AllocatorMethod, AllocatorMethodInput, AllocatorTy, global_fn_name, }; -use rustc_ast::ptr::P; use rustc_ast::{ self as ast, AttrVec, Expr, Fn, FnHeader, FnSig, Generics, ItemKind, Mutability, Param, Safety, Stmt, StmtKind, Ty, TyKind, @@ -51,7 +50,7 @@ pub(crate) fn expand( let const_body = ecx.expr_block(ecx.block(span, stmts)); let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body); let const_item = if is_stmt { - Annotatable::Stmt(P(ecx.stmt_item(span, const_item))) + Annotatable::Stmt(Box::new(ecx.stmt_item(span, const_item))) } else { Annotatable::Item(const_item) }; @@ -90,7 +89,7 @@ impl AllocFnFactory<'_, '_> { self.cx.stmt_item(self.ty_span, item) } - fn call_allocator(&self, method: Symbol, mut args: ThinVec>) -> P { + fn call_allocator(&self, method: Symbol, mut args: ThinVec>) -> Box { let method = self.cx.std_path(&[sym::alloc, sym::GlobalAlloc, method]); let method = self.cx.expr_path(self.cx.path(self.ty_span, method)); let allocator = self.cx.path_ident(self.ty_span, self.global); @@ -105,7 +104,7 @@ impl AllocFnFactory<'_, '_> { thin_vec![self.cx.attr_word(sym::rustc_std_internal_symbol, self.span)] } - fn arg_ty(&self, input: &AllocatorMethodInput, args: &mut ThinVec) -> P { + fn arg_ty(&self, input: &AllocatorMethodInput, args: &mut ThinVec) -> Box { match input.ty { AllocatorTy::Layout => { // If an allocator method is ever introduced having multiple @@ -148,7 +147,7 @@ impl AllocFnFactory<'_, '_> { } } - fn ret_ty(&self, ty: &AllocatorTy) -> P { + fn ret_ty(&self, ty: &AllocatorTy) -> Box { match *ty { AllocatorTy::ResultPtr => self.ptr_u8(), @@ -160,12 +159,12 @@ impl AllocFnFactory<'_, '_> { } } - fn usize(&self) -> P { + fn usize(&self) -> Box { let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span)); self.cx.ty_path(usize) } - fn ptr_u8(&self) -> P { + fn ptr_u8(&self) -> Box { let u8 = self.cx.path_ident(self.span, Ident::new(sym::u8, self.span)); let ty_u8 = self.cx.ty_path(u8); self.cx.ty_ptr(self.span, ty_u8, Mutability::Mut) diff --git a/compiler/rustc_builtin_macros/src/iter.rs b/compiler/rustc_builtin_macros/src/iter.rs index 7ad83903a1be..e9f340ef1197 100644 --- a/compiler/rustc_builtin_macros/src/iter.rs +++ b/compiler/rustc_builtin_macros/src/iter.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{CoroutineKind, DUMMY_NODE_ID, Expr, ast, token}; use rustc_errors::PResult; @@ -24,7 +23,7 @@ fn parse_closure<'a>( cx: &mut ExtCtxt<'a>, span: Span, stream: TokenStream, -) -> PResult<'a, P> { +) -> PResult<'a, Box> { let mut closure_parser = cx.new_parser_from_tts(stream); let coroutine_kind = Some(CoroutineKind::Gen { diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 7bc448a9acb8..1bcea95fbb7b 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -8,7 +8,6 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(rust_logo)] #![feature(assert_matches)] -#![feature(autodiff)] #![feature(box_patterns)] #![feature(decl_macro)] #![feature(if_let_guard)] @@ -139,6 +138,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { PartialEq: partial_eq::expand_deriving_partial_eq, PartialOrd: partial_ord::expand_deriving_partial_ord, CoercePointee: coerce_pointee::expand_deriving_coerce_pointee, + From: from::expand_deriving_from, } let client = rustc_proc_macro::bridge::client::Client::expand1(rustc_proc_macro::quote); diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs index b61af0b0aaa1..34faafdc07c6 100644 --- a/compiler/rustc_builtin_macros/src/pattern_type.rs +++ b/compiler/rustc_builtin_macros/src/pattern_type.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AnonConst, DUMMY_NODE_ID, Ty, TyPat, TyPatKind, ast, token}; use rustc_errors::PResult; @@ -22,7 +21,10 @@ pub(crate) fn expand<'cx>( ExpandResult::Ready(base::MacEager::ty(cx.ty(sp, ast::TyKind::Pat(ty, pat)))) } -fn parse_pat_ty<'a>(cx: &mut ExtCtxt<'a>, stream: TokenStream) -> PResult<'a, (P, P)> { +fn parse_pat_ty<'a>( + cx: &mut ExtCtxt<'a>, + stream: TokenStream, +) -> PResult<'a, (Box, Box)> { let mut parser = cx.new_parser_from_tts(stream); let ty = parser.parse_ty()?; @@ -45,15 +47,15 @@ fn parse_pat_ty<'a>(cx: &mut ExtCtxt<'a>, stream: TokenStream) -> PResult<'a, (P Ok((ty, pat)) } -fn ty_pat(kind: TyPatKind, span: Span) -> P { - P(TyPat { id: DUMMY_NODE_ID, kind, span, tokens: None }) +fn ty_pat(kind: TyPatKind, span: Span) -> Box { + Box::new(TyPat { id: DUMMY_NODE_ID, kind, span, tokens: None }) } -fn pat_to_ty_pat(cx: &mut ExtCtxt<'_>, pat: ast::Pat) -> P { +fn pat_to_ty_pat(cx: &mut ExtCtxt<'_>, pat: ast::Pat) -> Box { let kind = match pat.kind { ast::PatKind::Range(start, end, include_end) => TyPatKind::Range( - start.map(|value| P(AnonConst { id: DUMMY_NODE_ID, value })), - end.map(|value| P(AnonConst { id: DUMMY_NODE_ID, value })), + start.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })), + end.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })), include_end, ), ast::PatKind::Or(variants) => { diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index df70c93c1c2d..6ac3e17503d0 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -1,6 +1,5 @@ use std::{mem, slice}; -use rustc_ast::ptr::P; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, HasNodeId, NodeId, attr}; use rustc_ast_pretty::pprust; @@ -232,12 +231,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { let fn_ident = if let ast::ItemKind::Fn(fn_) = &item.kind { fn_.ident } else { - self.dcx - .create_err(errors::AttributeOnlyBeUsedOnBareFunctions { - span: attr.span, - path: &pprust::path_to_string(&attr.get_normal_item().path), - }) - .emit(); + // Error handled by general target checking logic return; }; @@ -286,7 +280,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { // // ... // ]; // } -fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P { +fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box { let expn_id = cx.resolver.expansion_for_ast_pass( DUMMY_SP, AstPass::ProcMacroHarness, diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index ecfd46a84ec3..37bab5be5421 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -3,7 +3,6 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast as ast; -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{join_path_idents, token}; use rustc_ast_pretty::pprust; @@ -144,7 +143,7 @@ pub(crate) fn expand_include<'cx>( node_id: ast::NodeId, } impl<'a> MacResult for ExpandInclude<'a> { - fn make_expr(mut self: Box>) -> Option> { + fn make_expr(mut self: Box>) -> Option> { let expr = parse_expr(&mut self.p).ok()?; if self.p.token != token::Eof { self.p.psess.buffer_lint( @@ -157,7 +156,7 @@ pub(crate) fn expand_include<'cx>( Some(expr) } - fn make_items(mut self: Box>) -> Option; 1]>> { + fn make_items(mut self: Box>) -> Option; 1]>> { let mut ret = SmallVec::new(); loop { match self.p.parse_item(ForceCollect::No) { diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index ba3d8368b2a0..7a189ee1f4d0 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -4,11 +4,13 @@ use std::assert_matches::assert_matches; use std::iter; -use rustc_ast::ptr::P; -use rustc_ast::{self as ast, GenericParamKind, attr, join_path_idents}; +use rustc_ast::{self as ast, GenericParamKind, HasNodeId, attr, join_path_idents}; use rustc_ast_pretty::pprust; +use rustc_attr_parsing::AttributeParser; use rustc_errors::{Applicability, Diag, Level}; use rustc_expand::base::*; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; use rustc_span::{ErrorGuaranteed, FileNameDisplayPreference, Ident, Span, Symbol, sym}; use thin_vec::{ThinVec, thin_vec}; use tracing::debug; @@ -75,7 +77,7 @@ pub(crate) fn expand_test_case( } let ret = if is_stmt { - Annotatable::Stmt(P(ecx.stmt_item(item.span, item))) + Annotatable::Stmt(Box::new(ecx.stmt_item(item.span, item))) } else { Annotatable::Item(item) }; @@ -128,7 +130,7 @@ pub(crate) fn expand_test_or_bench( let ast::ItemKind::Fn(fn_) = &item.kind else { not_testable_error(cx, attr_sp, Some(&item)); return if is_stmt { - vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] + vec![Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))] } else { vec![Annotatable::Item(item)] }; @@ -152,7 +154,7 @@ pub(crate) fn expand_test_or_bench( }; if check_result.is_err() { return if is_stmt { - vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] + vec![Annotatable::Stmt(Box::new(cx.stmt_item(item.span, item)))] } else { vec![Annotatable::Item(item)] }; @@ -198,7 +200,7 @@ pub(crate) fn expand_test_or_bench( // `-Cinstrument-coverage` builds. // This requires `#[allow_internal_unstable(coverage_attribute)]` on the // corresponding macro declaration in `core::macros`. - let coverage_off = |mut expr: P| { + let coverage_off = |mut expr: Box| { assert_matches!(expr.kind, ast::ExprKind::Closure(_)); expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp)); expr @@ -385,11 +387,11 @@ pub(crate) fn expand_test_or_bench( if is_stmt { vec![ // Access to libtest under a hygienic name - Annotatable::Stmt(P(cx.stmt_item(sp, test_extern))), + Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_extern))), // The generated test case - Annotatable::Stmt(P(cx.stmt_item(sp, test_const))), + Annotatable::Stmt(Box::new(cx.stmt_item(sp, test_const))), // The original item - Annotatable::Stmt(P(cx.stmt_item(sp, item))), + Annotatable::Stmt(Box::new(cx.stmt_item(sp, item))), ] } else { vec![ @@ -473,39 +475,19 @@ fn should_ignore_message(i: &ast::Item) -> Option { } fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { - match attr::find_by_name(&i.attrs, sym::should_panic) { - Some(attr) => { - match attr.meta_item_list() { - // Handle #[should_panic(expected = "foo")] - Some(list) => { - let msg = list - .iter() - .find(|mi| mi.has_name(sym::expected)) - .and_then(|mi| mi.meta_item()) - .and_then(|mi| mi.value_str()); - if list.len() != 1 || msg.is_none() { - cx.dcx() - .struct_span_warn( - attr.span, - "argument must be of the form: \ - `expected = \"error message\"`", - ) - .with_note( - "errors in this attribute were erroneously \ - allowed and will become a hard error in a \ - future release", - ) - .emit(); - ShouldPanic::Yes(None) - } else { - ShouldPanic::Yes(msg) - } - } - // Handle #[should_panic] and #[should_panic = "expected"] - None => ShouldPanic::Yes(attr.value_str()), - } - } - None => ShouldPanic::No, + if let Some(Attribute::Parsed(AttributeKind::ShouldPanic { reason, .. })) = + AttributeParser::parse_limited( + cx.sess, + &i.attrs, + sym::should_panic, + i.span, + i.node_id(), + None, + ) + { + ShouldPanic::Yes(reason) + } else { + ShouldPanic::No } } diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 111c85d49eb0..a9d91f77560a 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -5,7 +5,6 @@ use std::mem; use rustc_ast as ast; use rustc_ast::entry::EntryPointType; use rustc_ast::mut_visit::*; -use rustc_ast::ptr::P; use rustc_ast::visit::Visitor; use rustc_ast::{ModKind, attr}; use rustc_errors::DiagCtxtHandle; @@ -142,7 +141,7 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> { if let ast::ItemKind::Mod( _, _, - ModKind::Loaded(.., ast::ModSpans { inner_span: span, .. }, _), + ModKind::Loaded(.., ast::ModSpans { inner_span: span, .. }), ) = item.kind { let prev_tests = mem::take(&mut self.tests); @@ -284,7 +283,7 @@ fn generate_test_harness( /// [`TestCtxt::reexport_test_harness_main`] provides a different name for the `main` /// function and [`TestCtxt::test_runner`] provides a path that replaces /// `test::test_main_static`. -fn mk_main(cx: &mut TestCtxt<'_>) -> P { +fn mk_main(cx: &mut TestCtxt<'_>) -> Box { let sp = cx.def_site; let ecx = &cx.ext_cx; let test_ident = Ident::new(sym::test, sp); @@ -348,7 +347,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P { define_opaque: None, })); - let main = P(ast::Item { + let main = Box::new(ast::Item { attrs: thin_vec![main_attr, coverage_attr, doc_hidden_attr], id: ast::DUMMY_NODE_ID, kind: main, @@ -364,7 +363,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P { /// Creates a slice containing every test like so: /// &[&test1, &test2] -fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P { +fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> Box { debug!("building test vector from {} tests", cx.test_cases.len()); let ecx = &cx.ext_cx; diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 38fec2bff14c..f00c170e4850 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; @@ -83,7 +82,7 @@ type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), Err #[allow(rustc::untranslatable_diagnostic)] pub(crate) fn expr_to_spanned_string<'a>( cx: &'a mut ExtCtxt<'_>, - expr: P, + expr: Box, err_msg: &'static str, ) -> ExpandResult, ()> { if !cx.force_mode @@ -135,7 +134,7 @@ pub(crate) fn expr_to_spanned_string<'a>( /// compilation on error, merely emits a non-fatal error and returns `Err`. pub(crate) fn expr_to_string( cx: &mut ExtCtxt<'_>, - expr: P, + expr: Box, err_msg: &'static str, ) -> ExpandResult, ()> { expr_to_spanned_string(cx, expr, err_msg).map(|res| { @@ -158,7 +157,7 @@ pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, nam } /// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`. -pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result, ErrorGuaranteed> { +pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result, ErrorGuaranteed> { let guar = match p.parse_expr() { Ok(expr) => return Ok(expr), Err(err) => err.emit(), @@ -209,7 +208,7 @@ pub(crate) fn get_single_expr_from_tts( span: Span, tts: TokenStream, name: &str, -) -> ExpandResult, ErrorGuaranteed>, ()> { +) -> ExpandResult, ErrorGuaranteed>, ()> { let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); @@ -232,7 +231,7 @@ pub(crate) fn get_single_expr_from_tts( pub(crate) fn get_exprs_from_tts( cx: &mut ExtCtxt<'_>, tts: TokenStream, -) -> ExpandResult>, ErrorGuaranteed>, ()> { +) -> ExpandResult>, ErrorGuaranteed>, ()> { let mut p = cx.new_parser_from_tts(tts); let mut es = Vec::new(); while p.token != token::Eof { diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index a04cfa272376..a56466750e75 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -281,8 +281,8 @@ fn data_id_for_static( .abi .bytes(); - let linkage = if import_linkage == rustc_middle::mir::mono::Linkage::ExternalWeak - || import_linkage == rustc_middle::mir::mono::Linkage::WeakAny + let linkage = if import_linkage == rustc_hir::attrs::Linkage::ExternalWeak + || import_linkage == rustc_hir::attrs::Linkage::WeakAny { Linkage::Preemptible } else { @@ -310,7 +310,10 @@ fn data_id_for_static( // `extern_with_linkage_foo` will instead be initialized to // zero. - let ref_name = format!("_rust_extern_with_linkage_{}", symbol_name); + let ref_name = format!( + "_rust_extern_with_linkage_{:016x}_{symbol_name}", + tcx.stable_crate_id(LOCAL_CRATE) + ); let ref_data_id = module.declare_data(&ref_name, Linkage::Local, false, false).unwrap(); let mut data = DataDescription::new(); data.set_align(align); @@ -329,8 +332,8 @@ fn data_id_for_static( let linkage = if definition { crate::linkage::get_static_linkage(tcx, def_id) - } else if attrs.linkage == Some(rustc_middle::mir::mono::Linkage::ExternalWeak) - || attrs.linkage == Some(rustc_middle::mir::mono::Linkage::WeakAny) + } else if attrs.linkage == Some(rustc_hir::attrs::Linkage::ExternalWeak) + || attrs.linkage == Some(rustc_hir::attrs::Linkage::WeakAny) { Linkage::Preemptible } else { diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index 8ec3599b63d8..7e77781dc2fc 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -18,12 +18,11 @@ use rustc_codegen_ssa::{ use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; +use rustc_hir::attrs::Linkage as RLinkage; use rustc_metadata::fs::copy_to_stdout; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::mono::{ - CodegenUnit, Linkage as RLinkage, MonoItem, MonoItemData, Visibility, -}; +use rustc_middle::mir::mono::{CodegenUnit, MonoItem, MonoItemData, Visibility}; use rustc_session::Session; use rustc_session::config::{DebugInfo, OutFileName, OutputFilenames, OutputType}; diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index 4ff5773a06cb..ed40901ac9b8 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -969,7 +969,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = amount.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -982,7 +982,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::Add, ptr, amount); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_xsub => { @@ -991,7 +991,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = amount.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -1004,7 +1004,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::Sub, ptr, amount); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_and => { @@ -1013,7 +1013,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = src.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -1025,7 +1025,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::And, ptr, src); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_or => { @@ -1034,7 +1034,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = src.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -1046,7 +1046,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::Or, ptr, src); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_xor => { @@ -1055,7 +1055,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = src.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -1067,7 +1067,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::Xor, ptr, src); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_nand => { @@ -1076,7 +1076,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let layout = src.layout(); match layout.ty.kind() { - ty::Uint(_) | ty::Int(_) | ty::RawPtr(..) => {} + ty::Uint(_) | ty::Int(_) => {} _ => { report_atomic_type_validation_error(fx, intrinsic, source_info.span, layout.ty); return Ok(()); @@ -1088,7 +1088,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let old = fx.bcx.ins().atomic_rmw(ty, MemFlags::trusted(), AtomicRmwOp::Nand, ptr, src); - let old = CValue::by_val(old, layout); + let old = CValue::by_val(old, ret.layout()); ret.write_cvalue(fx, old); } sym::atomic_max => { diff --git a/compiler/rustc_codegen_cranelift/src/linkage.rs b/compiler/rustc_codegen_cranelift/src/linkage.rs index ca853aac1589..d76ab9d0109f 100644 --- a/compiler/rustc_codegen_cranelift/src/linkage.rs +++ b/compiler/rustc_codegen_cranelift/src/linkage.rs @@ -1,4 +1,5 @@ -use rustc_middle::mir::mono::{Linkage as RLinkage, MonoItem, Visibility}; +use rustc_hir::attrs::Linkage as RLinkage; +use rustc_middle::mir::mono::{MonoItem, Visibility}; use crate::prelude::*; diff --git a/compiler/rustc_codegen_gcc/build_system/src/abi_test.rs b/compiler/rustc_codegen_gcc/build_system/src/abi_test.rs index 3c1531be27a5..a85886d87f36 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/abi_test.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/abi_test.rs @@ -31,7 +31,7 @@ pub fn run() -> Result<(), String> { Some("clones/abi-cafe".as_ref()), true, ) - .map_err(|err| (format!("Git clone failed with message: {err:?}!")))?; + .map_err(|err| format!("Git clone failed with message: {err:?}!"))?; // Configure abi-cafe to use the exact same rustc version we use - this is crucial. // Otherwise, the concept of ABI compatibility becomes meanignless. std::fs::copy("rust-toolchain", "clones/abi-cafe/rust-toolchain") diff --git a/compiler/rustc_codegen_gcc/build_system/src/fuzz.rs b/compiler/rustc_codegen_gcc/build_system/src/fuzz.rs index 453211366b31..9714ce29af90 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/fuzz.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/fuzz.rs @@ -43,18 +43,18 @@ pub fn run() -> Result<(), String> { "--start" => { start = str::parse(&args.next().ok_or_else(|| "Fuzz start not provided!".to_string())?) - .map_err(|err| (format!("Fuzz start not a number {err:?}!")))?; + .map_err(|err| format!("Fuzz start not a number {err:?}!"))?; } "--count" => { count = str::parse(&args.next().ok_or_else(|| "Fuzz count not provided!".to_string())?) - .map_err(|err| (format!("Fuzz count not a number {err:?}!")))?; + .map_err(|err| format!("Fuzz count not a number {err:?}!"))?; } "-j" | "--jobs" => { threads = str::parse( &args.next().ok_or_else(|| "Fuzz thread count not provided!".to_string())?, ) - .map_err(|err| (format!("Fuzz thread count not a number {err:?}!")))?; + .map_err(|err| format!("Fuzz thread count not a number {err:?}!"))?; } _ => return Err(format!("Unknown option {arg}")), } @@ -66,7 +66,7 @@ pub fn run() -> Result<(), String> { Some("clones/rustlantis".as_ref()), true, ) - .map_err(|err| (format!("Git clone failed with message: {err:?}!")))?; + .map_err(|err| format!("Git clone failed with message: {err:?}!"))?; // Ensure that we are on the newest rustlantis commit. let cmd: &[&dyn AsRef] = &[&"git", &"pull", &"origin"]; diff --git a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch b/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch index 9cc377850b9b..3a8c37a8b8d9 100644 --- a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch +++ b/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch @@ -1,29 +1,28 @@ -From b8f3eed3053c9333b5dfbeaeb2a6a65a4b3156df Mon Sep 17 00:00:00 2001 -From: Antoni Boucher -Date: Tue, 29 Aug 2023 13:06:34 -0400 +From 190e26c9274b3c93a9ee3516b395590e6bd9213b Mon Sep 17 00:00:00 2001 +From: None +Date: Sun, 3 Aug 2025 19:54:56 -0400 Subject: [PATCH] Patch 0001-Add-stdarch-Cargo.toml-for-testing.patch --- - library/stdarch/Cargo.toml | 23 +++++++++++++++++++++++ - 1 file changed, 23 insertions(+) + library/stdarch/Cargo.toml | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) create mode 100644 library/stdarch/Cargo.toml diff --git a/library/stdarch/Cargo.toml b/library/stdarch/Cargo.toml new file mode 100644 -index 0000000..4c63700 +index 0000000..bd6725c --- /dev/null +++ b/library/stdarch/Cargo.toml -@@ -0,0 +1,21 @@ +@@ -0,0 +1,20 @@ +[workspace] +resolver = "1" +members = [ -+ "crates/core_arch", -+ "crates/std_detect", -+ "crates/stdarch-gen-arm", ++ "crates/*", + #"examples/" +] +exclude = [ -+ "crates/wasm-assert-instr-tests" ++ "crates/wasm-assert-instr-tests", ++ "rust_programs", +] + +[profile.release] @@ -36,5 +35,5 @@ index 0000000..4c63700 +opt-level = 3 +incremental = true -- -2.42.0 +2.50.1 diff --git a/compiler/rustc_codegen_gcc/rust-toolchain b/compiler/rustc_codegen_gcc/rust-toolchain index 2fe8ec4647fa..058e734be5cf 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain +++ b/compiler/rustc_codegen_gcc/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2025-07-04" +channel = "nightly-2025-08-03" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/compiler/rustc_codegen_gcc/src/allocator.rs b/compiler/rustc_codegen_gcc/src/allocator.rs index 0d8dc93274f9..2a95a7368aac 100644 --- a/compiler/rustc_codegen_gcc/src/allocator.rs +++ b/compiler/rustc_codegen_gcc/src/allocator.rs @@ -99,11 +99,15 @@ fn create_const_value_function( let func = context.new_function(None, FunctionType::Exported, output, &[], name, false); #[cfg(feature = "master")] - func.add_attribute(FnAttribute::Visibility(symbol_visibility_to_gcc( - tcx.sess.default_visibility(), - ))); - - func.add_attribute(FnAttribute::AlwaysInline); + { + func.add_attribute(FnAttribute::Visibility(symbol_visibility_to_gcc( + tcx.sess.default_visibility(), + ))); + + // FIXME(antoyo): cg_llvm sets AlwaysInline, but AlwaysInline is different in GCC and using + // it here will causes linking errors when using LTO. + func.add_attribute(FnAttribute::Inline); + } if tcx.sess.must_emit_unwind_tables() { // TODO(antoyo): emit unwind tables. diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs index c105916bbb2b..e9d72e457a08 100644 --- a/compiler/rustc_codegen_gcc/src/base.rs +++ b/compiler/rustc_codegen_gcc/src/base.rs @@ -8,8 +8,8 @@ use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; use rustc_codegen_ssa::traits::DebugInfoCodegenMethods; +use rustc_hir::attrs::Linkage; use rustc_middle::dep_graph; -use rustc_middle::mir::mono::Linkage; #[cfg(feature = "master")] use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::TyCtxt; diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 4aee211e2efa..f7a7a3f8c7e3 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -540,9 +540,15 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn ret(&mut self, mut value: RValue<'gcc>) { let expected_return_type = self.current_func().get_return_type(); - if !expected_return_type.is_compatible_with(value.get_type()) { - // NOTE: due to opaque pointers now being used, we need to cast here. - value = self.context.new_cast(self.location, value, expected_return_type); + let value_type = value.get_type(); + if !expected_return_type.is_compatible_with(value_type) { + // NOTE: due to opaque pointers now being used, we need to (bit)cast here. + if self.is_native_int_type(value_type) && self.is_native_int_type(expected_return_type) + { + value = self.context.new_cast(self.location, value, expected_return_type); + } else { + value = self.context.new_bitcast(self.location, value, expected_return_type); + } } self.llbb().end_with_return(self.location, value); } @@ -1279,11 +1285,19 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn intcast( &mut self, - value: RValue<'gcc>, + mut value: RValue<'gcc>, dest_typ: Type<'gcc>, - _is_signed: bool, + is_signed: bool, ) -> RValue<'gcc> { - // NOTE: is_signed is for value, not dest_typ. + let value_type = value.get_type(); + if is_signed && !value_type.is_signed(self.cx) { + let signed_type = value_type.to_signed(self.cx); + value = self.gcc_int_cast(value, signed_type); + } else if !is_signed && value_type.is_signed(self.cx) { + let unsigned_type = value_type.to_unsigned(self.cx); + value = self.gcc_int_cast(value, unsigned_type); + } + self.gcc_int_cast(value, dest_typ) } @@ -1657,6 +1671,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { dst: RValue<'gcc>, src: RValue<'gcc>, order: AtomicOrdering, + ret_ptr: bool, ) -> RValue<'gcc> { let size = get_maybe_pointer_size(src); let name = match op { @@ -1684,6 +1699,9 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { let atomic_function = self.context.get_builtin_function(name); let order = self.context.new_rvalue_from_int(self.i32_type, order.to_gcc()); + // FIXME: If `ret_ptr` is true and `src` is an integer, we should really tell GCC + // that this is a pointer operation that needs to preserve provenance -- but like LLVM, + // GCC does not currently seems to support that. let void_ptr_type = self.context.new_type::<*mut ()>(); let volatile_void_ptr_type = void_ptr_type.make_volatile(); let dst = self.context.new_cast(self.location, dst, volatile_void_ptr_type); @@ -1691,7 +1709,8 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { let new_src_type = atomic_function.get_param(1).to_rvalue().get_type(); let src = self.context.new_bitcast(self.location, src, new_src_type); let res = self.context.new_call(self.location, atomic_function, &[dst, src, order]); - self.context.new_cast(self.location, res, src.get_type()) + let res_type = if ret_ptr { void_ptr_type } else { src.get_type() }; + self.context.new_cast(self.location, res, res_type) } fn atomic_fence(&mut self, order: AtomicOrdering, scope: SynchronizationScope) { diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index c04c75e1b11f..619277eba8b8 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -5,12 +5,13 @@ use rustc_abi::{self as abi, Align, HasDataLayout, Primitive, Size, WrappingRang use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods, }; +use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::interpret::{ self, ConstAllocation, ErrorHandled, Scalar as InterpScalar, read_target_uint, }; -use rustc_middle::mir::mono::Linkage; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance}; use rustc_middle::{bug, span_bug}; @@ -384,8 +385,8 @@ fn check_and_apply_linkage<'gcc, 'tcx>( // linkage and there are no definitions), then // `extern_with_linkage_foo` will instead be initialized to // zero. - let mut real_name = "_rust_extern_with_linkage_".to_string(); - real_name.push_str(sym); + let real_name = + format!("_rust_extern_with_linkage_{:016x}_{sym}", cx.tcx.stable_crate_id(LOCAL_CRATE)); let global2 = cx.define_global(&real_name, gcc_type, is_tls, attrs.link_section); // TODO(antoyo): set linkage. let value = cx.const_ptrcast(global1.get_address(None), gcc_type); diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index 6f21ce9352b5..9fb7f6bad684 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -4,12 +4,15 @@ // cSpell:words cmpti divti modti mulodi muloti udivti umodti -use gccjit::{BinaryOp, ComparisonOp, FunctionType, Location, RValue, ToRValue, Type, UnaryOp}; +use gccjit::{ + BinaryOp, CType, ComparisonOp, FunctionType, Location, RValue, ToRValue, Type, UnaryOp, +}; use rustc_abi::{CanonAbi, Endian, ExternAbi}; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; use rustc_codegen_ssa::traits::{BackendTypes, BaseTypeCodegenMethods, BuilderMethods, OverflowOp}; use rustc_middle::ty::{self, Ty}; use rustc_target::callconv::{ArgAbi, ArgAttributes, FnAbi, PassMode}; +use rustc_type_ir::{Interner, TyKind}; use crate::builder::{Builder, ToGccComp}; use crate::common::{SignType, TypeReflection}; @@ -167,9 +170,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { if a_type.is_vector() { // Vector types need to be bitcast. // TODO(antoyo): perhaps use __builtin_convertvector for vector casting. - b = self.context.new_bitcast(self.location, b, a.get_type()); + b = self.context.new_bitcast(self.location, b, a_type); } else { - b = self.context.new_cast(self.location, b, a.get_type()); + b = self.context.new_cast(self.location, b, a_type); } } self.context.new_binary_op(self.location, operation, a_type, a, b) @@ -216,13 +219,22 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { operation_name: &str, signed: bool, a: RValue<'gcc>, - b: RValue<'gcc>, + mut b: RValue<'gcc>, ) -> RValue<'gcc> { let a_type = a.get_type(); let b_type = b.get_type(); if (self.is_native_int_type_or_bool(a_type) && self.is_native_int_type_or_bool(b_type)) || (a_type.is_vector() && b_type.is_vector()) { + if !a_type.is_compatible_with(b_type) { + if a_type.is_vector() { + // Vector types need to be bitcast. + // TODO(antoyo): perhaps use __builtin_convertvector for vector casting. + b = self.context.new_bitcast(self.location, b, a_type); + } else { + b = self.context.new_cast(self.location, b, a_type); + } + } self.context.new_binary_op(self.location, operation, a_type, a, b) } else { debug_assert!(a_type.dyncast_array().is_some()); @@ -351,6 +363,11 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { // TODO(antoyo): is it correct to use rhs type instead of the parameter typ? .new_local(self.location, rhs.get_type(), "binopResult") .get_address(self.location); + let new_type = type_kind_to_gcc_type(new_kind); + let new_type = self.context.new_c_type(new_type); + let lhs = self.context.new_cast(self.location, lhs, new_type); + let rhs = self.context.new_cast(self.location, rhs, new_type); + let res = self.context.new_cast(self.location, res, new_type.make_pointer()); let overflow = self.overflow_call(intrinsic, &[lhs, rhs, res], None); (res.dereference(self.location).to_rvalue(), overflow) } @@ -477,11 +494,27 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let lhs_low = self.context.new_cast(self.location, self.low(lhs), unsigned_type); let rhs_low = self.context.new_cast(self.location, self.low(rhs), unsigned_type); + let mut lhs_high = self.high(lhs); + let mut rhs_high = self.high(rhs); + + match op { + IntPredicate::IntUGT + | IntPredicate::IntUGE + | IntPredicate::IntULT + | IntPredicate::IntULE => { + lhs_high = self.context.new_cast(self.location, lhs_high, unsigned_type); + rhs_high = self.context.new_cast(self.location, rhs_high, unsigned_type); + } + // TODO(antoyo): we probably need to handle signed comparison for unsigned + // integers. + _ => (), + } + let condition = self.context.new_comparison( self.location, ComparisonOp::LessThan, - self.high(lhs), - self.high(rhs), + lhs_high, + rhs_high, ); self.llbb().end_with_conditional(self.location, condition, block1, block2); @@ -495,8 +528,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let condition = self.context.new_comparison( self.location, ComparisonOp::GreaterThan, - self.high(lhs), - self.high(rhs), + lhs_high, + rhs_high, ); block2.end_with_conditional(self.location, condition, block3, block4); @@ -620,7 +653,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } } - pub fn gcc_xor(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { + pub fn gcc_xor(&self, a: RValue<'gcc>, mut b: RValue<'gcc>) -> RValue<'gcc> { let a_type = a.get_type(); let b_type = b.get_type(); if a_type.is_vector() && b_type.is_vector() { @@ -628,6 +661,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { a ^ b } else if self.is_native_int_type_or_bool(a_type) && self.is_native_int_type_or_bool(b_type) { + if !a_type.is_compatible_with(b_type) { + b = self.context.new_cast(self.location, b, a_type); + } a ^ b } else { self.concat_low_high_rvalues( @@ -1042,3 +1078,25 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.context.new_array_constructor(None, typ, &values) } } + +fn type_kind_to_gcc_type(kind: TyKind) -> CType { + use rustc_middle::ty::IntTy::*; + use rustc_middle::ty::UintTy::*; + use rustc_middle::ty::{Int, Uint}; + + match kind { + Int(I8) => CType::Int8t, + Int(I16) => CType::Int16t, + Int(I32) => CType::Int32t, + Int(I64) => CType::Int64t, + Int(I128) => CType::Int128t, + + Uint(U8) => CType::UInt8t, + Uint(U16) => CType::UInt16t, + Uint(U32) => CType::UInt32t, + Uint(U64) => CType::UInt64t, + Uint(U128) => CType::UInt128t, + + _ => unimplemented!("Kind: {:?}", kind), + } +} diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs index 915ed875e32f..d1b2a93243d2 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs @@ -95,8 +95,11 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "cubema" => "__builtin_amdgcn_cubema", "cubesc" => "__builtin_amdgcn_cubesc", "cubetc" => "__builtin_amdgcn_cubetc", + "cvt.f16.bf8" => "__builtin_amdgcn_cvt_f16_bf8", + "cvt.f16.fp8" => "__builtin_amdgcn_cvt_f16_fp8", "cvt.f32.bf8" => "__builtin_amdgcn_cvt_f32_bf8", "cvt.f32.fp8" => "__builtin_amdgcn_cvt_f32_fp8", + "cvt.f32.fp8.e5m3" => "__builtin_amdgcn_cvt_f32_fp8_e5m3", "cvt.off.f32.i4" => "__builtin_amdgcn_cvt_off_f32_i4", "cvt.pk.bf8.f32" => "__builtin_amdgcn_cvt_pk_bf8_f32", "cvt.pk.f16.bf8" => "__builtin_amdgcn_cvt_pk_f16_bf8", @@ -181,6 +184,12 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "dot4.f32.fp8.bf8" => "__builtin_amdgcn_dot4_f32_fp8_bf8", "dot4.f32.fp8.fp8" => "__builtin_amdgcn_dot4_f32_fp8_fp8", "ds.add.gs.reg.rtn" => "__builtin_amdgcn_ds_add_gs_reg_rtn", + "ds.atomic.async.barrier.arrive.b64" => { + "__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64" + } + "ds.atomic.barrier.arrive.rtn.b64" => { + "__builtin_amdgcn_ds_atomic_barrier_arrive_rtn_b64" + } "ds.bpermute" => "__builtin_amdgcn_ds_bpermute", "ds.bpermute.fi.b32" => "__builtin_amdgcn_ds_bpermute_fi_b32", "ds.gws.barrier" => "__builtin_amdgcn_ds_gws_barrier", @@ -198,8 +207,32 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "fdot2.f16.f16" => "__builtin_amdgcn_fdot2_f16_f16", "fdot2.f32.bf16" => "__builtin_amdgcn_fdot2_f32_bf16", "fdot2c.f32.bf16" => "__builtin_amdgcn_fdot2c_f32_bf16", + "flat.prefetch" => "__builtin_amdgcn_flat_prefetch", "fmul.legacy" => "__builtin_amdgcn_fmul_legacy", + "global.load.async.to.lds.b128" => { + "__builtin_amdgcn_global_load_async_to_lds_b128" + } + "global.load.async.to.lds.b32" => { + "__builtin_amdgcn_global_load_async_to_lds_b32" + } + "global.load.async.to.lds.b64" => { + "__builtin_amdgcn_global_load_async_to_lds_b64" + } + "global.load.async.to.lds.b8" => "__builtin_amdgcn_global_load_async_to_lds_b8", "global.load.lds" => "__builtin_amdgcn_global_load_lds", + "global.prefetch" => "__builtin_amdgcn_global_prefetch", + "global.store.async.from.lds.b128" => { + "__builtin_amdgcn_global_store_async_from_lds_b128" + } + "global.store.async.from.lds.b32" => { + "__builtin_amdgcn_global_store_async_from_lds_b32" + } + "global.store.async.from.lds.b64" => { + "__builtin_amdgcn_global_store_async_from_lds_b64" + } + "global.store.async.from.lds.b8" => { + "__builtin_amdgcn_global_store_async_from_lds_b8" + } "groupstaticsize" => "__builtin_amdgcn_groupstaticsize", "iglp.opt" => "__builtin_amdgcn_iglp_opt", "implicit.buffer.ptr" => "__builtin_amdgcn_implicit_buffer_ptr", @@ -291,6 +324,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.incperflevel" => "__builtin_amdgcn_s_incperflevel", "s.memrealtime" => "__builtin_amdgcn_s_memrealtime", "s.memtime" => "__builtin_amdgcn_s_memtime", + "s.monitor.sleep" => "__builtin_amdgcn_s_monitor_sleep", "s.sendmsg" => "__builtin_amdgcn_s_sendmsg", "s.sendmsghalt" => "__builtin_amdgcn_s_sendmsghalt", "s.setprio" => "__builtin_amdgcn_s_setprio", @@ -300,11 +334,15 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.sleep.var" => "__builtin_amdgcn_s_sleep_var", "s.ttracedata" => "__builtin_amdgcn_s_ttracedata", "s.ttracedata.imm" => "__builtin_amdgcn_s_ttracedata_imm", + "s.wait.asynccnt" => "__builtin_amdgcn_s_wait_asynccnt", "s.wait.event.export.ready" => "__builtin_amdgcn_s_wait_event_export_ready", + "s.wait.tensorcnt" => "__builtin_amdgcn_s_wait_tensorcnt", "s.waitcnt" => "__builtin_amdgcn_s_waitcnt", "sad.hi.u8" => "__builtin_amdgcn_sad_hi_u8", "sad.u16" => "__builtin_amdgcn_sad_u16", "sad.u8" => "__builtin_amdgcn_sad_u8", + "sat.pk4.i4.i8" => "__builtin_amdgcn_sat_pk4_i4_i8", + "sat.pk4.u4.u8" => "__builtin_amdgcn_sat_pk4_u4_u8", "sched.barrier" => "__builtin_amdgcn_sched_barrier", "sched.group.barrier" => "__builtin_amdgcn_sched_group_barrier", "sdot2" => "__builtin_amdgcn_sdot2", @@ -346,8 +384,13 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "smfmac.i32.16x16x64.i8" => "__builtin_amdgcn_smfmac_i32_16x16x64_i8", "smfmac.i32.32x32x32.i8" => "__builtin_amdgcn_smfmac_i32_32x32x32_i8", "smfmac.i32.32x32x64.i8" => "__builtin_amdgcn_smfmac_i32_32x32x64_i8", + "struct.ptr.buffer.load.lds" => "__builtin_amdgcn_struct_ptr_buffer_load_lds", "sudot4" => "__builtin_amdgcn_sudot4", "sudot8" => "__builtin_amdgcn_sudot8", + "tensor.load.to.lds" => "__builtin_amdgcn_tensor_load_to_lds", + "tensor.load.to.lds.d2" => "__builtin_amdgcn_tensor_load_to_lds_d2", + "tensor.store.from.lds" => "__builtin_amdgcn_tensor_store_from_lds", + "tensor.store.from.lds.d2" => "__builtin_amdgcn_tensor_store_from_lds_d2", "udot2" => "__builtin_amdgcn_udot2", "udot4" => "__builtin_amdgcn_udot4", "udot8" => "__builtin_amdgcn_udot8", @@ -6326,6 +6369,23 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { } s390(name, full_name) } + "spv" => { + #[allow(non_snake_case)] + fn spv(name: &str, full_name: &str) -> &'static str { + match name { + // spv + "num.subgroups" => "__builtin_spirv_num_subgroups", + "subgroup.id" => "__builtin_spirv_subgroup_id", + "subgroup.local.invocation.id" => { + "__builtin_spirv_subgroup_local_invocation_id" + } + "subgroup.max.size" => "__builtin_spirv_subgroup_max_size", + "subgroup.size" => "__builtin_spirv_subgroup_size", + _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), + } + } + spv(name, full_name) + } "ve" => { #[allow(non_snake_case)] fn ve(name: &str, full_name: &str) -> &'static str { diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 0753ac1aeb84..eb0a5336a1f1 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -925,10 +925,17 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { // TODO(antoyo): use width? let arg_type = arg.get_type(); let result_type = self.u32_type; + let arg = if arg_type.is_signed(self.cx) { + let new_type = arg_type.to_unsigned(self.cx); + self.gcc_int_cast(arg, new_type) + } else { + arg + }; + let arg_type = arg.get_type(); let count_leading_zeroes = // TODO(antoyo): write a new function Type::is_compatible_with(&Type) and use it here // instead of using is_uint(). - if arg_type.is_uint(self.cx) { + if arg_type.is_uchar(self.cx) || arg_type.is_ushort(self.cx) || arg_type.is_uint(self.cx) { "__builtin_clz" } else if arg_type.is_ulong(self.cx) { diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 350915a277e3..fdc15d580eff 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -206,6 +206,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( ); } + #[cfg(feature = "master")] + if name == sym::simd_funnel_shl { + return Ok(simd_funnel_shift( + bx, + args[0].immediate(), + args[1].immediate(), + args[2].immediate(), + true, + )); + } + + #[cfg(feature = "master")] + if name == sym::simd_funnel_shr { + return Ok(simd_funnel_shift( + bx, + args[0].immediate(), + args[1].immediate(), + args[2].immediate(), + false, + )); + } + if name == sym::simd_bswap { return Ok(simd_bswap(bx, args[0].immediate())); } @@ -1434,3 +1456,62 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( unimplemented!("simd {}", name); } + +#[cfg(feature = "master")] +fn simd_funnel_shift<'a, 'gcc, 'tcx>( + bx: &mut Builder<'a, 'gcc, 'tcx>, + a: RValue<'gcc>, + b: RValue<'gcc>, + shift: RValue<'gcc>, + shift_left: bool, +) -> RValue<'gcc> { + use crate::common::SignType; + + let a_type = a.get_type(); + let vector_type = a_type.unqualified().dyncast_vector().expect("vector type"); + let num_units = vector_type.get_num_units(); + let elem_type = vector_type.get_element_type(); + + let (new_int_type, int_shift_val, int_mask) = if elem_type.is_compatible_with(bx.u8_type) + || elem_type.is_compatible_with(bx.i8_type) + { + (bx.u16_type, 8, u8::MAX as u64) + } else if elem_type.is_compatible_with(bx.u16_type) || elem_type.is_compatible_with(bx.i16_type) + { + (bx.u32_type, 16, u16::MAX as u64) + } else if elem_type.is_compatible_with(bx.u32_type) || elem_type.is_compatible_with(bx.i32_type) + { + (bx.u64_type, 32, u32::MAX as u64) + } else if elem_type.is_compatible_with(bx.u64_type) || elem_type.is_compatible_with(bx.i64_type) + { + (bx.u128_type, 64, u64::MAX) + } else { + unimplemented!("funnel shift on {:?}", elem_type); + }; + + let int_mask = bx.context.new_rvalue_from_long(new_int_type, int_mask as i64); + let int_shift_val = bx.context.new_rvalue_from_int(new_int_type, int_shift_val); + let mut elements = vec![]; + let unsigned_type = elem_type.to_unsigned(bx); + for i in 0..num_units { + let index = bx.context.new_rvalue_from_int(bx.int_type, i as i32); + let a_val = bx.context.new_vector_access(None, a, index).to_rvalue(); + let a_val = bx.context.new_bitcast(None, a_val, unsigned_type); + // TODO: we probably need to use gcc_int_cast instead. + let a_val = bx.gcc_int_cast(a_val, new_int_type); + let b_val = bx.context.new_vector_access(None, b, index).to_rvalue(); + let b_val = bx.context.new_bitcast(None, b_val, unsigned_type); + let b_val = bx.gcc_int_cast(b_val, new_int_type); + let shift_val = bx.context.new_vector_access(None, shift, index).to_rvalue(); + let shift_val = bx.gcc_int_cast(shift_val, new_int_type); + let mut val = a_val << int_shift_val | b_val; + if shift_left { + val = (val << shift_val) >> int_shift_val; + } else { + val = (val >> shift_val) & int_mask; + } + let val = bx.gcc_int_cast(val, elem_type); + elements.push(val); + } + bx.context.new_rvalue_from_vector(None, a_type, &elements) +} diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 613315f77a6b..4025aba82da3 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -50,6 +50,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_symbol_mangling; extern crate rustc_target; +extern crate rustc_type_ir; // This prevents duplicating functions and statics that are already part of the host rustc process. #[allow(unused_extern_crates)] @@ -92,7 +93,6 @@ use gccjit::{CType, Context, OptimizationLevel}; #[cfg(feature = "master")] use gccjit::{TargetInfo, Version}; use rustc_ast::expand::allocator::AllocatorKind; -use rustc_ast::expand::autodiff_attrs::AutoDiffItem; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule}; use rustc_codegen_ssa::back::write::{ CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryFn, @@ -362,12 +362,7 @@ impl WriteBackendMethods for GccCodegenBackend { _exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, - diff_fncs: Vec, ) -> Result, FatalError> { - if !diff_fncs.is_empty() { - unimplemented!(); - } - back::lto::run_fat(cgcx, each_linked_rlib_for_lto, modules) } diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs index ff188c437dae..35d44d21bcbf 100644 --- a/compiler/rustc_codegen_gcc/src/mono_item.rs +++ b/compiler/rustc_codegen_gcc/src/mono_item.rs @@ -1,11 +1,12 @@ #[cfg(feature = "master")] use gccjit::{FnAttribute, VarAttribute}; use rustc_codegen_ssa::traits::PreDefineCodegenMethods; +use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::mono::{Linkage, Visibility}; +use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; diff --git a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt index b9126fb73a77..bf0633f73200 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt @@ -28,6 +28,6 @@ tests/ui/macros/macro-comma-behavior-rpass.rs tests/ui/macros/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs tests/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs tests/ui/macros/stringify.rs -tests/ui/reexport-test-harness-main.rs tests/ui/rfcs/rfc-1937-termination-trait/termination-trait-in-test.rs tests/ui/binding/fn-arg-incomplete-pattern-drop-order.rs +tests/ui/lto/debuginfo-lto-alloc.rs diff --git a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt index 842533cd3c62..29032b321fa7 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt @@ -6,7 +6,6 @@ tests/run-make/doctests-keep-binaries/ tests/run-make/doctests-runtool/ tests/run-make/emit-shared-files/ tests/run-make/exit-code/ -tests/run-make/issue-22131/ tests/run-make/issue-64153/ tests/run-make/llvm-ident/ tests/run-make/native-link-modifier-bundle/ diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index 6979c04d5343..41fb4729c07d 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -10,11 +10,10 @@ tests/ui/iterators/iter-sum-overflow-overflow-checks.rs tests/ui/mir/mir_drop_order.rs tests/ui/mir/mir_let_chains_drop_order.rs tests/ui/mir/mir_match_guard_let_chains_drop_order.rs -tests/ui/oom_unwind.rs +tests/ui/panics/oom-panic-unwind.rs tests/ui/panic-runtime/abort-link-to-unwinding-crates.rs tests/ui/panic-runtime/abort.rs tests/ui/panic-runtime/link-to-abort.rs -tests/ui/unwind-no-uwtable.rs tests/ui/parser/unclosed-delimiter-in-dep.rs tests/ui/consts/missing_span_in_backtrace.rs tests/ui/drop/dynamic-drop.rs @@ -82,3 +81,8 @@ tests/ui/coroutine/panic-drops.rs tests/ui/coroutine/panic-safe.rs tests/ui/process/nofile-limit.rs tests/ui/simd/intrinsic/generic-arithmetic-pass.rs +tests/ui/linking/no-gc-encapsulation-symbols.rs +tests/ui/panics/unwind-force-no-unwind-tables.rs +tests/ui/attributes/fn-align-dyn.rs +tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs +tests/ui/explicit-tail-calls/recursion-etc.rs diff --git a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt index 31023e50ffa1..4fb018b3ecd8 100644 --- a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt +++ b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt @@ -8,6 +8,7 @@ clzll cmse codegened csky +ctfe ctlz ctpop cttz @@ -25,6 +26,7 @@ fwrapv gimple hrtb immediates +interner liblto llbb llcx @@ -47,6 +49,7 @@ mavx mcmodel minimumf minnumf +miri monomorphization monomorphizations monomorphized diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 009e7e2487b6..399f8b6e7628 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -24,6 +24,7 @@ use crate::attributes::{self, llfn_attrs_from_instance}; use crate::builder::Builder; use crate::context::CodegenCx; use crate::llvm::{self, Attribute, AttributePlace}; +use crate::llvm_util; use crate::type_::Type; use crate::type_of::LayoutLlvmExt; use crate::value::Value; @@ -41,12 +42,13 @@ trait ArgAttributesExt { const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] = [(ArgAttribute::InReg, llvm::AttributeKind::InReg)]; -const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [ +const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [ (ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias), (ArgAttribute::NoCapture, llvm::AttributeKind::NoCapture), (ArgAttribute::NonNull, llvm::AttributeKind::NonNull), (ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly), (ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef), + (ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly), ]; fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> { @@ -82,6 +84,10 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&' } for (attr, llattr) in OPTIMIZATION_ATTRIBUTES { if regular.contains(attr) { + // captures(address, read_provenance) is only available since LLVM 21. + if attr == ArgAttribute::CapturesReadOnly && llvm_util::get_version() < (21, 0, 0) { + continue; + } attrs.push(llattr.create_attr(cx.llcx)); } } @@ -500,7 +506,16 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { } } PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => { - apply(attrs); + let i = apply(attrs); + if cx.sess().opts.optimize != config::OptLevel::No + && llvm_util::get_version() >= (21, 0, 0) + { + attributes::apply_to_llfn( + llfn, + llvm::AttributePlace::Argument(i), + &[llvm::AttributeKind::DeadOnReturn.create_attr(cx.llcx)], + ); + } } PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => { assert!(!on_stack); diff --git a/compiler/rustc_codegen_llvm/src/allocator.rs b/compiler/rustc_codegen_llvm/src/allocator.rs index 2b5090ed6dba..23610aa856cb 100644 --- a/compiler/rustc_codegen_llvm/src/allocator.rs +++ b/compiler/rustc_codegen_llvm/src/allocator.rs @@ -8,11 +8,12 @@ use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::config::{DebugInfo, OomStrategy}; use rustc_symbol_mangling::mangle_internal_symbol; +use smallvec::SmallVec; use crate::builder::SBuilder; use crate::declare::declare_simple_fn; use crate::llvm::{self, False, True, Type, Value}; -use crate::{SimpleCx, attributes, debuginfo}; +use crate::{SimpleCx, attributes, debuginfo, llvm_util}; pub(crate) unsafe fn codegen( tcx: TyCtxt<'_>, @@ -147,6 +148,20 @@ fn create_wrapper_function( llvm::Visibility::from_generic(tcx.sess.default_visibility()), ty, ); + + let mut attrs = SmallVec::<[_; 2]>::new(); + + let target_cpu = llvm_util::target_cpu(tcx.sess); + let target_cpu_attr = llvm::CreateAttrStringValue(cx.llcx, "target-cpu", target_cpu); + + let tune_cpu_attr = llvm_util::tune_cpu(tcx.sess) + .map(|tune_cpu| llvm::CreateAttrStringValue(cx.llcx, "tune-cpu", tune_cpu)); + + attrs.push(target_cpu_attr); + attrs.extend(tune_cpu_attr); + + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &attrs); + let no_return = if no_return { // -> ! DIFlagNoReturn let no_return = llvm::AttributeKind::NoReturn.create_attr(cx.llcx); diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index c548f4675834..5affb26483aa 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -28,22 +28,6 @@ pub(crate) fn apply_to_callsite(callsite: &Value, idx: AttributePlace, attrs: &[ } } -pub(crate) fn has_attr(llfn: &Value, idx: AttributePlace, attr: AttributeKind) -> bool { - llvm::HasAttributeAtIndex(llfn, idx, attr) -} - -pub(crate) fn has_string_attr(llfn: &Value, name: &str) -> bool { - llvm::HasStringAttribute(llfn, name) -} - -pub(crate) fn remove_from_llfn(llfn: &Value, place: AttributePlace, kind: AttributeKind) { - llvm::RemoveRustEnumAttributeAtIndex(llfn, place, kind); -} - -pub(crate) fn remove_string_attr_from_llfn(llfn: &Value, name: &str) { - llvm::RemoveStringAttrFromFn(llfn, name); -} - /// Get LLVM attribute for the provided inline heuristic. #[inline] fn inline_attr<'ll>(cx: &CodegenCx<'ll, '_>, inline: InlineAttr) -> Option<&'ll Attribute> { @@ -436,6 +420,16 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR_ZEROED) { to_add.push(create_alloc_family_attr(cx.llcx)); + if let Some(zv) = + cx.tcx.get_attr(instance.def_id(), rustc_span::sym::rustc_allocator_zeroed_variant) + && let Some(name) = zv.value_str() + { + to_add.push(llvm::CreateAttrStringValue( + cx.llcx, + "alloc-variant-zeroed", + &mangle_internal_symbol(cx.tcx, name.as_str()), + )); + } // apply to argument place instead of function let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx); attributes::apply_to_llfn(llfn, AttributePlace::Argument(1), &[alloc_align]); @@ -513,7 +507,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( to_add.push(llvm::CreateAttrStringValue(cx.llcx, "wasm-import-module", module)); let name = - codegen_fn_attrs.link_name.unwrap_or_else(|| cx.tcx.item_name(instance.def_id())); + codegen_fn_attrs.symbol_name.unwrap_or_else(|| cx.tcx.item_name(instance.def_id())); let name = name.as_str(); to_add.push(llvm::CreateAttrStringValue(cx.llcx, "wasm-import-name", name)); } diff --git a/compiler/rustc_codegen_llvm/src/back/archive.rs b/compiler/rustc_codegen_llvm/src/back/archive.rs index 0a161442933a..7a340ae83f3d 100644 --- a/compiler/rustc_codegen_llvm/src/back/archive.rs +++ b/compiler/rustc_codegen_llvm/src/back/archive.rs @@ -1,104 +1,21 @@ //! A helper class for dealing with static archives -use std::ffi::{CStr, CString, c_char, c_void}; -use std::path::{Path, PathBuf}; -use std::{io, mem, ptr, str}; +use std::ffi::{CStr, c_char, c_void}; +use std::io; use rustc_codegen_ssa::back::archive::{ - ArArchiveBuilder, ArchiveBuildFailure, ArchiveBuilder, ArchiveBuilderBuilder, - DEFAULT_OBJECT_READER, ObjectReader, UnknownArchiveKind, try_extract_macho_fat_archive, + ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, ObjectReader, }; use rustc_session::Session; -use crate::llvm::archive_ro::{ArchiveRO, Child}; -use crate::llvm::{self, ArchiveKind, last_error}; - -/// Helper for adding many files to an archive. -#[must_use = "must call build() to finish building the archive"] -pub(crate) struct LlvmArchiveBuilder<'a> { - sess: &'a Session, - additions: Vec, -} - -enum Addition { - File { path: PathBuf, name_in_archive: String }, - Archive { path: PathBuf, archive: ArchiveRO, skip: Box bool> }, -} - -impl Addition { - fn path(&self) -> &Path { - match self { - Addition::File { path, .. } | Addition::Archive { path, .. } => path, - } - } -} - -fn is_relevant_child(c: &Child<'_>) -> bool { - match c.name() { - Some(name) => !name.contains("SYMDEF"), - None => false, - } -} - -impl<'a> ArchiveBuilder for LlvmArchiveBuilder<'a> { - fn add_archive( - &mut self, - archive: &Path, - skip: Box bool + 'static>, - ) -> io::Result<()> { - let mut archive = archive.to_path_buf(); - if self.sess.target.llvm_target.contains("-apple-macosx") { - if let Some(new_archive) = try_extract_macho_fat_archive(self.sess, &archive)? { - archive = new_archive - } - } - let archive_ro = match ArchiveRO::open(&archive) { - Ok(ar) => ar, - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), - }; - if self.additions.iter().any(|ar| ar.path() == archive) { - return Ok(()); - } - self.additions.push(Addition::Archive { - path: archive, - archive: archive_ro, - skip: Box::new(skip), - }); - Ok(()) - } - - /// Adds an arbitrary file to this archive - fn add_file(&mut self, file: &Path) { - let name = file.file_name().unwrap().to_str().unwrap(); - self.additions - .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() }); - } - - /// Combine the provided files, rlibs, and native libraries into a single - /// `Archive`. - fn build(mut self: Box, output: &Path) -> bool { - match self.build_with_llvm(output) { - Ok(any_members) => any_members, - Err(error) => { - self.sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error }) - } - } - } -} +use crate::llvm; pub(crate) struct LlvmArchiveBuilderBuilder; impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box { - // Keeping LlvmArchiveBuilder around in case of a regression caused by using - // ArArchiveBuilder. - // FIXME(#128955) remove a couple of months after #128936 gets merged in case - // no regression is found. - if false { - Box::new(LlvmArchiveBuilder { sess, additions: Vec::new() }) - } else { - Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER)) - } + // Use the `object` crate to build archives, with a little bit of help from LLVM. + Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER)) } } @@ -178,91 +95,3 @@ fn llvm_is_64_bit_object_file(buf: &[u8]) -> bool { fn llvm_is_ec_object_file(buf: &[u8]) -> bool { unsafe { llvm::LLVMRustIsECObject(buf.as_ptr(), buf.len()) } } - -impl<'a> LlvmArchiveBuilder<'a> { - fn build_with_llvm(&mut self, output: &Path) -> io::Result { - let kind = &*self.sess.target.archive_format; - let kind = kind - .parse::() - .map_err(|_| kind) - .unwrap_or_else(|kind| self.sess.dcx().emit_fatal(UnknownArchiveKind { kind })); - - let mut additions = mem::take(&mut self.additions); - // Values in the `members` list below will contain pointers to the strings allocated here. - // So they need to get dropped after all elements of `members` get freed. - let mut strings = Vec::new(); - let mut members = Vec::new(); - - let dst = CString::new(output.to_str().unwrap())?; - - unsafe { - for addition in &mut additions { - match addition { - Addition::File { path, name_in_archive } => { - let path = CString::new(path.to_str().unwrap())?; - let name = CString::new(name_in_archive.as_bytes())?; - members.push(llvm::LLVMRustArchiveMemberNew( - path.as_ptr(), - name.as_ptr(), - None, - )); - strings.push(path); - strings.push(name); - } - Addition::Archive { archive, skip, .. } => { - for child in archive.iter() { - let child = child.map_err(string_to_io_error)?; - if !is_relevant_child(&child) { - continue; - } - let child_name = child.name().unwrap(); - if skip(child_name) { - continue; - } - - // It appears that LLVM's archive writer is a little - // buggy if the name we pass down isn't just the - // filename component, so chop that off here and - // pass it in. - // - // See LLVM bug 25877 for more info. - let child_name = - Path::new(child_name).file_name().unwrap().to_str().unwrap(); - let name = CString::new(child_name)?; - let m = llvm::LLVMRustArchiveMemberNew( - ptr::null(), - name.as_ptr(), - Some(child.raw), - ); - members.push(m); - strings.push(name); - } - } - } - } - - let r = llvm::LLVMRustWriteArchive( - dst.as_ptr(), - members.len() as libc::size_t, - members.as_ptr() as *const &_, - true, - kind, - self.sess.target.arch == "arm64ec", - ); - let ret = if r.into_result().is_err() { - let msg = last_error().unwrap_or_else(|| "failed to write archive".into()); - Err(io::Error::new(io::ErrorKind::Other, msg)) - } else { - Ok(!members.is_empty()) - }; - for member in members { - llvm::LLVMRustArchiveMemberFree(member); - } - ret - } - } -} - -fn string_to_io_error(s: String) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("bad archive: {s}")) -} diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index c269f11e931b..853d0295238e 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -24,9 +24,8 @@ use crate::back::write::{ self, CodegenDiagnosticsStage, DiagnosticHandlers, bitcode_section_name, save_temp_bitcode, }; use crate::errors::{LlvmError, LtoBitcodeFromRlib}; -use crate::llvm::AttributePlace::Function; use crate::llvm::{self, build_string}; -use crate::{LlvmCodegenBackend, ModuleLlvm, SimpleCx, attributes}; +use crate::{LlvmCodegenBackend, ModuleLlvm, SimpleCx}; /// We keep track of the computed LTO cache keys from the previous /// session to determine which CGUs we can reuse. @@ -593,31 +592,6 @@ pub(crate) fn run_pass_manager( } if cfg!(llvm_enzyme) && enable_ad && !thin { - let cx = - SimpleCx::new(module.module_llvm.llmod(), &module.module_llvm.llcx, cgcx.pointer_size); - - for function in cx.get_functions() { - let enzyme_marker = "enzyme_marker"; - if attributes::has_string_attr(function, enzyme_marker) { - // Sanity check: Ensure 'noinline' is present before replacing it. - assert!( - attributes::has_attr(function, Function, llvm::AttributeKind::NoInline), - "Expected __enzyme function to have 'noinline' before adding 'alwaysinline'" - ); - - attributes::remove_from_llfn(function, Function, llvm::AttributeKind::NoInline); - attributes::remove_string_attr_from_llfn(function, enzyme_marker); - - assert!( - !attributes::has_string_attr(function, enzyme_marker), - "Expected function to not have 'enzyme_marker'" - ); - - let always_inline = llvm::AttributeKind::AlwaysInline.create_attr(cx.llcx); - attributes::apply_to_llfn(function, Function, &[always_inline]); - } - } - let opt_stage = llvm::OptStage::FatLTO; let stage = write::AutodiffStage::PostAD; if !config.autodiff.contains(&config::AutoDiff::NoPostopt) { diff --git a/compiler/rustc_codegen_llvm/src/back/mod.rs b/compiler/rustc_codegen_llvm/src/back/mod.rs new file mode 100644 index 000000000000..6cb89f80ab89 --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/back/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod archive; +pub(crate) mod lto; +pub(crate) mod owned_target_machine; +mod profiling; +pub(crate) mod write; diff --git a/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs b/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs index 8e82013e94ad..6d8178320feb 100644 --- a/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs +++ b/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs @@ -1,4 +1,5 @@ -use std::ffi::{CStr, c_char}; +use std::assert_matches::assert_matches; +use std::ffi::CStr; use std::marker::PhantomData; use std::ptr::NonNull; @@ -41,11 +42,9 @@ impl OwnedTargetMachine { args_cstr_buff: &[u8], use_wasm_eh: bool, ) -> Result> { - assert!(args_cstr_buff.len() > 0); - assert!( - *args_cstr_buff.last().unwrap() == 0, - "The last character must be a null terminator." - ); + // The argument list is passed as the concatenation of one or more C strings. + // This implies that there must be a last byte, and it must be 0. + assert_matches!(args_cstr_buff, [.., b'\0'], "the last byte must be a NUL terminator"); // SAFETY: llvm::LLVMRustCreateTargetMachine copies pointed to data let tm_ptr = unsafe { @@ -71,7 +70,7 @@ impl OwnedTargetMachine { output_obj_file.as_ptr(), debug_info_compression.as_ptr(), use_emulated_tls, - args_cstr_buff.as_ptr() as *const c_char, + args_cstr_buff.as_ptr(), args_cstr_buff.len(), use_wasm_eh, ) @@ -99,7 +98,7 @@ impl Drop for OwnedTargetMachine { // llvm::LLVMRustCreateTargetMachine OwnedTargetMachine is not copyable so there is no // double free or use after free. unsafe { - llvm::LLVMRustDisposeTargetMachine(self.tm_unique.as_mut()); + llvm::LLVMRustDisposeTargetMachine(self.tm_unique.as_ptr()); } } } diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 85a06f457ebe..62998003ca11 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -862,7 +862,7 @@ pub(crate) fn codegen( .generic_activity_with_arg("LLVM_module_codegen_embed_bitcode", &*module.name); let thin_bc = module.thin_lto_buffer.as_deref().expect("cannot find embedded bitcode"); - embed_bitcode(cgcx, llcx, llmod, &config.bc_cmdline, &thin_bc); + embed_bitcode(cgcx, llcx, llmod, &thin_bc); } } @@ -1058,7 +1058,6 @@ fn embed_bitcode( cgcx: &CodegenContext, llcx: &llvm::Context, llmod: &llvm::Module, - cmdline: &str, bitcode: &[u8], ) { // We're adding custom sections to the output object file, but we definitely @@ -1074,7 +1073,9 @@ fn embed_bitcode( // * Mach-O - this is for macOS. Inspecting the source code for the native // linker here shows that the `.llvmbc` and `.llvmcmd` sections are // automatically skipped by the linker. In that case there's nothing extra - // that we need to do here. + // that we need to do here. We do need to make sure that the + // `__LLVM,__cmdline` section exists even though it is empty as otherwise + // ld64 rejects the object file. // // * Wasm - the native LLD linker is hard-coded to skip `.llvmbc` and // `.llvmcmd` sections, so there's nothing extra we need to do. @@ -1111,7 +1112,7 @@ fn embed_bitcode( llvm::set_linkage(llglobal, llvm::Linkage::PrivateLinkage); llvm::LLVMSetGlobalConstant(llglobal, llvm::True); - let llconst = common::bytes_in_context(llcx, cmdline.as_bytes()); + let llconst = common::bytes_in_context(llcx, &[]); let llglobal = llvm::add_global(llmod, common::val_ty(llconst), c"rustc.embedded.cmdline"); llvm::set_initializer(llglobal, llconst); let section = if cgcx.target_is_like_darwin { @@ -1128,7 +1129,7 @@ fn embed_bitcode( let section_flags = if cgcx.is_pe_coff { "n" } else { "e" }; let asm = create_section_with_flags_asm(".llvmbc", section_flags, bitcode); llvm::append_module_inline_asm(llmod, &asm); - let asm = create_section_with_flags_asm(".llvmcmd", section_flags, cmdline.as_bytes()); + let asm = create_section_with_flags_asm(".llvmcmd", section_flags, &[]); llvm::append_module_inline_asm(llmod, &asm); } } diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 5dda836988c8..9cc5d8dbc21c 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -18,9 +18,10 @@ use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; use rustc_codegen_ssa::traits::*; use rustc_data_structures::small_c_str::SmallCStr; +use rustc_hir::attrs::Linkage; use rustc_middle::dep_graph; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; -use rustc_middle::mir::mono::{Linkage, Visibility}; +use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::TyCtxt; use rustc_session::config::DebugInfo; use rustc_span::Symbol; diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index da2a153d819f..427c75d40e9c 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -557,13 +557,25 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let (size, signed) = ty.int_size_and_signed(self.tcx); let width = size.bits(); - if oop == OverflowOp::Sub && !signed { - // Emit sub and icmp instead of llvm.usub.with.overflow. LLVM considers these - // to be the canonical form. It will attempt to reform llvm.usub.with.overflow - // in the backend if profitable. - let sub = self.sub(lhs, rhs); - let cmp = self.icmp(IntPredicate::IntULT, lhs, rhs); - return (sub, cmp); + if !signed { + match oop { + OverflowOp::Sub => { + // Emit sub and icmp instead of llvm.usub.with.overflow. LLVM considers these + // to be the canonical form. It will attempt to reform llvm.usub.with.overflow + // in the backend if profitable. + let sub = self.sub(lhs, rhs); + let cmp = self.icmp(IntPredicate::IntULT, lhs, rhs); + return (sub, cmp); + } + OverflowOp::Add => { + // Like with sub above, using icmp is the preferred form. See + // + let add = self.add(lhs, rhs); + let cmp = self.icmp(IntPredicate::IntULT, add, lhs); + return (add, cmp); + } + OverflowOp::Mul => {} + } } let oop_str = match oop { @@ -1327,15 +1339,13 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { &mut self, op: rustc_codegen_ssa::common::AtomicRmwBinOp, dst: &'ll Value, - mut src: &'ll Value, + src: &'ll Value, order: rustc_middle::ty::AtomicOrdering, + ret_ptr: bool, ) -> &'ll Value { - // The only RMW operation that LLVM supports on pointers is compare-exchange. - let requires_cast_to_int = self.val_ty(src) == self.type_ptr() - && op != rustc_codegen_ssa::common::AtomicRmwBinOp::AtomicXchg; - if requires_cast_to_int { - src = self.ptrtoint(src, self.type_isize()); - } + // FIXME: If `ret_ptr` is true and `src` is not a pointer, we *should* tell LLVM that the + // LHS is a pointer and the operation should be provenance-preserving, but LLVM does not + // currently support that (https://github.com/llvm/llvm-project/issues/120837). let mut res = unsafe { llvm::LLVMBuildAtomicRMW( self.llbuilder, @@ -1346,7 +1356,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { llvm::False, // SingleThreaded ) }; - if requires_cast_to_int { + if ret_ptr && self.val_ty(res) != self.type_ptr() { res = self.inttoptr(res, self.type_ptr()); } res @@ -1443,7 +1453,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { instance: Option>, ) { let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet, instance); - llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail); + llvm::LLVMSetTailCallKind(call, llvm::TailCallKind::MustTail); match &fn_abi.ret.mode { PassMode::Ignore | PassMode::Indirect { .. } => self.ret_void(), @@ -1686,7 +1696,11 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { return; } - self.call_intrinsic(intrinsic, &[self.val_ty(ptr)], &[self.cx.const_u64(size), ptr]); + if crate::llvm_util::get_version() >= (22, 0, 0) { + self.call_intrinsic(intrinsic, &[self.val_ty(ptr)], &[ptr]); + } else { + self.call_intrinsic(intrinsic, &[self.val_ty(ptr)], &[self.cx.const_u64(size), ptr]); + } } } impl<'a, 'll, CX: Borrow>> GenericBuilder<'a, 'll, CX> { @@ -1886,48 +1900,4 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { ) { self.call_intrinsic("llvm.instrprof.increment", &[], &[fn_name, hash, num_counters, index]); } - - /// Emits a call to `llvm.instrprof.mcdc.parameters`. - /// - /// This doesn't produce any code directly, but is used as input by - /// the LLVM pass that handles coverage instrumentation. - /// - /// (See clang's [`CodeGenPGO::emitMCDCParameters`] for comparison.) - /// - /// [`CodeGenPGO::emitMCDCParameters`]: - /// https://github.com/rust-lang/llvm-project/blob/5399a24/clang/lib/CodeGen/CodeGenPGO.cpp#L1124 - #[instrument(level = "debug", skip(self))] - pub(crate) fn mcdc_parameters( - &mut self, - fn_name: &'ll Value, - hash: &'ll Value, - bitmap_bits: &'ll Value, - ) { - self.call_intrinsic("llvm.instrprof.mcdc.parameters", &[], &[fn_name, hash, bitmap_bits]); - } - - #[instrument(level = "debug", skip(self))] - pub(crate) fn mcdc_tvbitmap_update( - &mut self, - fn_name: &'ll Value, - hash: &'ll Value, - bitmap_index: &'ll Value, - mcdc_temp: &'ll Value, - ) { - let args = &[fn_name, hash, bitmap_index, mcdc_temp]; - self.call_intrinsic("llvm.instrprof.mcdc.tvbitmap.update", &[], args); - } - - #[instrument(level = "debug", skip(self))] - pub(crate) fn mcdc_condbitmap_reset(&mut self, mcdc_temp: &'ll Value) { - self.store(self.const_i32(0), mcdc_temp, self.tcx.data_layout.i32_align.abi); - } - - #[instrument(level = "debug", skip(self))] - pub(crate) fn mcdc_condbitmap_update(&mut self, cond_index: &'ll Value, mcdc_temp: &'ll Value) { - let align = self.tcx.data_layout.i32_align.abi; - let current_tv_index = self.load(self.cx.type_i32(), mcdc_temp, align); - let new_tv_index = self.add(current_tv_index, cond_index); - self.store(new_tv_index, mcdc_temp, align); - } } diff --git a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs index 829b3c513c25..e2df3265f6f7 100644 --- a/compiler/rustc_codegen_llvm/src/builder/autodiff.rs +++ b/compiler/rustc_codegen_llvm/src/builder/autodiff.rs @@ -1,40 +1,92 @@ use std::ptr; -use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, AutoDiffItem, DiffActivity, DiffMode}; -use rustc_codegen_ssa::ModuleCodegen; +use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode}; use rustc_codegen_ssa::common::TypeKind; -use rustc_codegen_ssa::traits::BaseTypeCodegenMethods; -use rustc_errors::FatalError; -use rustc_middle::bug; -use tracing::{debug, trace}; +use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; +use rustc_middle::ty::{PseudoCanonicalInput, Ty, TyCtxt, TypingEnv}; +use rustc_middle::{bug, ty}; +use tracing::debug; -use crate::back::write::llvm_err; -use crate::builder::{SBuilder, UNNAMED}; +use crate::builder::{Builder, PlaceRef, UNNAMED}; use crate::context::SimpleCx; use crate::declare::declare_simple_fn; -use crate::errors::{AutoDiffWithoutEnable, LlvmError}; -use crate::llvm::AttributePlace::Function; -use crate::llvm::{Metadata, True}; +use crate::llvm; +use crate::llvm::{Metadata, True, Type}; use crate::value::Value; -use crate::{CodegenContext, LlvmCodegenBackend, ModuleLlvm, attributes, llvm}; -fn get_params(fnc: &Value) -> Vec<&Value> { - let param_num = llvm::LLVMCountParams(fnc) as usize; - let mut fnc_args: Vec<&Value> = vec![]; - fnc_args.reserve(param_num); - unsafe { - llvm::LLVMGetParams(fnc, fnc_args.as_mut_ptr()); - fnc_args.set_len(param_num); +pub(crate) fn adjust_activity_to_abi<'tcx>( + tcx: TyCtxt<'tcx>, + fn_ty: Ty<'tcx>, + da: &mut Vec, +) { + if !matches!(fn_ty.kind(), ty::FnDef(..)) { + bug!("expected fn def for autodiff, got {:?}", fn_ty); } - fnc_args -} -fn has_sret(fnc: &Value) -> bool { - let num_args = llvm::LLVMCountParams(fnc) as usize; - if num_args == 0 { - false - } else { - unsafe { llvm::LLVMRustHasAttributeAtIndex(fnc, 0, llvm::AttributeKind::StructRet) } + // We don't actually pass the types back into the type system. + // All we do is decide how to handle the arguments. + let sig = fn_ty.fn_sig(tcx).skip_binder(); + + let mut new_activities = vec![]; + let mut new_positions = vec![]; + for (i, ty) in sig.inputs().iter().enumerate() { + if let Some(inner_ty) = ty.builtin_deref(true) { + if inner_ty.is_slice() { + // Now we need to figure out the size of each slice element in memory to allow + // safety checks and usability improvements in the backend. + let sty = match inner_ty.builtin_index() { + Some(sty) => sty, + None => { + panic!("slice element type unknown"); + } + }; + let pci = PseudoCanonicalInput { + typing_env: TypingEnv::fully_monomorphized(), + value: sty, + }; + + let layout = tcx.layout_of(pci); + let elem_size = match layout { + Ok(layout) => layout.size, + Err(_) => { + bug!("autodiff failed to compute slice element size"); + } + }; + let elem_size: u32 = elem_size.bytes() as u32; + + // We know that the length will be passed as extra arg. + if !da.is_empty() { + // We are looking at a slice. The length of that slice will become an + // extra integer on llvm level. Integers are always const. + // However, if the slice get's duplicated, we want to know to later check the + // size. So we mark the new size argument as FakeActivitySize. + // There is one FakeActivitySize per slice, so for convenience we store the + // slice element size in bytes in it. We will use the size in the backend. + let activity = match da[i] { + DiffActivity::DualOnly + | DiffActivity::Dual + | DiffActivity::Dualv + | DiffActivity::DuplicatedOnly + | DiffActivity::Duplicated => { + DiffActivity::FakeActivitySize(Some(elem_size)) + } + DiffActivity::Const => DiffActivity::Const, + _ => bug!("unexpected activity for ptr/ref"), + }; + new_activities.push(activity); + new_positions.push(i + 1); + } + + continue; + } + } + } + // now add the extra activities coming from slices + // Reverse order to not invalidate the indices + for _ in 0..new_activities.len() { + let pos = new_positions.pop().unwrap(); + let activity = new_activities.pop().unwrap(); + da.insert(pos, activity); } } @@ -48,14 +100,13 @@ fn has_sret(fnc: &Value) -> bool { // need to match those. // FIXME(ZuseZ4): This logic is a bit more complicated than it should be, can we simplify it // using iterators and peek()? -fn match_args_from_caller_to_enzyme<'ll>( +fn match_args_from_caller_to_enzyme<'ll, 'tcx>( cx: &SimpleCx<'ll>, - builder: &SBuilder<'ll, 'll>, + builder: &mut Builder<'_, 'll, 'tcx>, width: u32, args: &mut Vec<&'ll llvm::Value>, inputs: &[DiffActivity], outer_args: &[&'ll llvm::Value], - has_sret: bool, ) { debug!("matching autodiff arguments"); // We now handle the issue that Rust level arguments not always match the llvm-ir level @@ -67,14 +118,6 @@ fn match_args_from_caller_to_enzyme<'ll>( let mut outer_pos: usize = 0; let mut activity_pos = 0; - if has_sret { - // Then the first outer arg is the sret pointer. Enzyme doesn't know about sret, so the - // inner function will still return something. We increase our outer_pos by one, - // and once we're done with all other args we will take the return of the inner call and - // update the sret pointer with it - outer_pos = 1; - } - let enzyme_const = cx.create_metadata(b"enzyme_const"); let enzyme_out = cx.create_metadata(b"enzyme_out"); let enzyme_dup = cx.create_metadata(b"enzyme_dup"); @@ -193,92 +236,6 @@ fn match_args_from_caller_to_enzyme<'ll>( } } -// On LLVM-IR, we can luckily declare __enzyme_ functions without specifying the input -// arguments. We do however need to declare them with their correct return type. -// We already figured the correct return type out in our frontend, when generating the outer_fn, -// so we can now just go ahead and use that. This is not always trivial, e.g. because sret. -// Beyond sret, this article describes our challenges nicely: -// -// I.e. (i32, f32) will get merged into i64, but we don't handle that yet. -fn compute_enzyme_fn_ty<'ll>( - cx: &SimpleCx<'ll>, - attrs: &AutoDiffAttrs, - fn_to_diff: &'ll Value, - outer_fn: &'ll Value, -) -> &'ll llvm::Type { - let fn_ty = cx.get_type_of_global(outer_fn); - let mut ret_ty = cx.get_return_type(fn_ty); - - let has_sret = has_sret(outer_fn); - - if has_sret { - // Now we don't just forward the return type, so we have to figure it out based on the - // primal return type, in combination with the autodiff settings. - let fn_ty = cx.get_type_of_global(fn_to_diff); - let inner_ret_ty = cx.get_return_type(fn_ty); - - let void_ty = unsafe { llvm::LLVMVoidTypeInContext(cx.llcx) }; - if inner_ret_ty == void_ty { - // This indicates that even the inner function has an sret. - // Right now I only look for an sret in the outer function. - // This *probably* needs some extra handling, but I never ran - // into such a case. So I'll wait for user reports to have a test case. - bug!("sret in inner function"); - } - - if attrs.width == 1 { - // Enzyme returns a struct of style: - // `{ original_ret(if requested), float, float, ... }` - let mut struct_elements = vec![]; - if attrs.has_primal_ret() { - struct_elements.push(inner_ret_ty); - } - // Next, we push the list of active floats, since they will be lowered to `enzyme_out`, - // and therefore part of the return struct. - let param_tys = cx.func_params_types(fn_ty); - for (act, param_ty) in attrs.input_activity.iter().zip(param_tys) { - if matches!(act, DiffActivity::Active) { - // Now find the float type at position i based on the fn_ty, - // to know what (f16/f32/f64/...) to add to the struct. - struct_elements.push(param_ty); - } - } - ret_ty = cx.type_struct(&struct_elements, false); - } else { - // First we check if we also have to deal with the primal return. - match attrs.mode { - DiffMode::Forward => match attrs.ret_activity { - DiffActivity::Dual => { - let arr_ty = - unsafe { llvm::LLVMArrayType2(inner_ret_ty, attrs.width as u64 + 1) }; - ret_ty = arr_ty; - } - DiffActivity::DualOnly => { - let arr_ty = - unsafe { llvm::LLVMArrayType2(inner_ret_ty, attrs.width as u64) }; - ret_ty = arr_ty; - } - DiffActivity::Const => { - todo!("Not sure, do we need to do something here?"); - } - _ => { - bug!("unreachable"); - } - }, - DiffMode::Reverse => { - todo!("Handle sret for reverse mode"); - } - _ => { - bug!("unreachable"); - } - } - } - } - - // LLVM can figure out the input types on it's own, so we take a shortcut here. - unsafe { llvm::LLVMFunctionType(ret_ty, ptr::null(), 0, True) } -} - /// When differentiating `fn_to_diff`, take a `outer_fn` and generate another /// function with expected naming and calling conventions[^1] which will be /// discovered by the enzyme LLVM pass and its body populated with the differentiated @@ -288,11 +245,15 @@ fn compute_enzyme_fn_ty<'ll>( /// [^1]: // FIXME(ZuseZ4): `outer_fn` should include upstream safety checks to // cover some assumptions of enzyme/autodiff, which could lead to UB otherwise. -fn generate_enzyme_call<'ll>( +pub(crate) fn generate_enzyme_call<'ll, 'tcx>( + builder: &mut Builder<'_, 'll, 'tcx>, cx: &SimpleCx<'ll>, fn_to_diff: &'ll Value, - outer_fn: &'ll Value, + outer_name: &str, + ret_ty: &'ll Type, + fn_args: &[&'ll Value], attrs: AutoDiffAttrs, + dest: PlaceRef<'tcx, &'ll Value>, ) { // We have to pick the name depending on whether we want forward or reverse mode autodiff. let mut ad_name: String = match attrs.mode { @@ -302,11 +263,9 @@ fn generate_enzyme_call<'ll>( } .to_string(); - // add outer_fn name to ad_name to make it unique, in case users apply autodiff to multiple + // add outer_name to ad_name to make it unique, in case users apply autodiff to multiple // functions. Unwrap will only panic, if LLVM gave us an invalid string. - let name = llvm::get_value_name(outer_fn); - let outer_fn_name = std::str::from_utf8(&name).unwrap(); - ad_name.push_str(outer_fn_name); + ad_name.push_str(outer_name); // Let us assume the user wrote the following function square: // @@ -316,14 +275,8 @@ fn generate_enzyme_call<'ll>( // %0 = fmul double %x, %x // ret double %0 // } - // ``` - // - // The user now applies autodiff to the function square, in which case fn_to_diff will be `square`. - // Our macro generates the following placeholder code (slightly simplified): // - // ```llvm // define double @dsquare(double %x) { - // ; placeholder code // return 0.0; // } // ``` @@ -340,175 +293,44 @@ fn generate_enzyme_call<'ll>( // ret double %0 // } // ``` - unsafe { - let enzyme_ty = compute_enzyme_fn_ty(cx, &attrs, fn_to_diff, outer_fn); - - // FIXME(ZuseZ4): the CC/Addr/Vis values are best effort guesses, we should look at tests and - // think a bit more about what should go here. - let cc = llvm::LLVMGetFunctionCallConv(outer_fn); - let ad_fn = declare_simple_fn( - cx, - &ad_name, - llvm::CallConv::try_from(cc).expect("invalid callconv"), - llvm::UnnamedAddr::No, - llvm::Visibility::Default, - enzyme_ty, - ); - - // Otherwise LLVM might inline our temporary code before the enzyme pass has a chance to - // do it's work. - let attr = llvm::AttributeKind::NoInline.create_attr(cx.llcx); - attributes::apply_to_llfn(ad_fn, Function, &[attr]); - - // We add a made-up attribute just such that we can recognize it after AD to update - // (no)-inline attributes. We'll then also remove this attribute. - let enzyme_marker_attr = llvm::CreateAttrString(cx.llcx, "enzyme_marker"); - attributes::apply_to_llfn(outer_fn, Function, &[enzyme_marker_attr]); - - // first, remove all calls from fnc - let entry = llvm::LLVMGetFirstBasicBlock(outer_fn); - let br = llvm::LLVMRustGetTerminator(entry); - llvm::LLVMRustEraseInstFromParent(br); - - let last_inst = llvm::LLVMRustGetLastInstruction(entry).unwrap(); - let mut builder = SBuilder::build(cx, entry); - - let num_args = llvm::LLVMCountParams(&fn_to_diff); - let mut args = Vec::with_capacity(num_args as usize + 1); - args.push(fn_to_diff); - - let enzyme_primal_ret = cx.create_metadata(b"enzyme_primal_return"); - if matches!(attrs.ret_activity, DiffActivity::Dual | DiffActivity::Active) { - args.push(cx.get_metadata_value(enzyme_primal_ret)); - } - if attrs.width > 1 { - let enzyme_width = cx.create_metadata(b"enzyme_width"); - args.push(cx.get_metadata_value(enzyme_width)); - args.push(cx.get_const_int(cx.type_i64(), attrs.width as u64)); - } - - let has_sret = has_sret(outer_fn); - let outer_args: Vec<&llvm::Value> = get_params(outer_fn); - match_args_from_caller_to_enzyme( - &cx, - &builder, - attrs.width, - &mut args, - &attrs.input_activity, - &outer_args, - has_sret, - ); - - let call = builder.call(enzyme_ty, ad_fn, &args, None); - - // This part is a bit iffy. LLVM requires that a call to an inlineable function has some - // metadata attached to it, but we just created this code oota. Given that the - // differentiated function already has partly confusing metadata, and given that this - // affects nothing but the auttodiff IR, we take a shortcut and just steal metadata from the - // dummy code which we inserted at a higher level. - // FIXME(ZuseZ4): Work with Enzyme core devs to clarify what debug metadata issues we have, - // and how to best improve it for enzyme core and rust-enzyme. - let md_ty = cx.get_md_kind_id("dbg"); - if llvm::LLVMRustHasMetadata(last_inst, md_ty) { - let md = llvm::LLVMRustDIGetInstMetadata(last_inst) - .expect("failed to get instruction metadata"); - let md_todiff = cx.get_metadata_value(md); - llvm::LLVMSetMetadata(call, md_ty, md_todiff); - } else { - // We don't panic, since depending on whether we are in debug or release mode, we might - // have no debug info to copy, which would then be ok. - trace!("no dbg info"); - } - - // Now that we copied the metadata, get rid of dummy code. - llvm::LLVMRustEraseInstUntilInclusive(entry, last_inst); - - if cx.val_ty(call) == cx.type_void() || has_sret { - if has_sret { - // This is what we already have in our outer_fn (shortened): - // define void @_foo(ptr <..> sret([32 x i8]) initializes((0, 32)) %0, <...>) { - // %7 = call [4 x double] (...) @__enzyme_fwddiff_foo(ptr @square, metadata !"enzyme_width", i64 4, <...>) - // - // store [4 x double] %7, ptr %0, align 8 - // ret void - // } - - // now store the result of the enzyme call into the sret pointer. - let sret_ptr = outer_args[0]; - let call_ty = cx.val_ty(call); - if attrs.width == 1 { - assert_eq!(cx.type_kind(call_ty), TypeKind::Struct); - } else { - assert_eq!(cx.type_kind(call_ty), TypeKind::Array); - } - llvm::LLVMBuildStore(&builder.llbuilder, call, sret_ptr); - } - builder.ret_void(); - } else { - builder.ret(call); - } - - // Let's crash in case that we messed something up above and generated invalid IR. - llvm::LLVMRustVerifyFunction( - outer_fn, - llvm::LLVMRustVerifierFailureAction::LLVMAbortProcessAction, - ); - } -} - -pub(crate) fn differentiate<'ll>( - module: &'ll ModuleCodegen, - cgcx: &CodegenContext, - diff_items: Vec, -) -> Result<(), FatalError> { - for item in &diff_items { - trace!("{}", item); + let enzyme_ty = unsafe { llvm::LLVMFunctionType(ret_ty, ptr::null(), 0, True) }; + + // FIXME(ZuseZ4): the CC/Addr/Vis values are best effort guesses, we should look at tests and + // think a bit more about what should go here. + let cc = unsafe { llvm::LLVMGetFunctionCallConv(fn_to_diff) }; + let ad_fn = declare_simple_fn( + cx, + &ad_name, + llvm::CallConv::try_from(cc).expect("invalid callconv"), + llvm::UnnamedAddr::No, + llvm::Visibility::Default, + enzyme_ty, + ); + + let num_args = llvm::LLVMCountParams(&fn_to_diff); + let mut args = Vec::with_capacity(num_args as usize + 1); + args.push(fn_to_diff); + + let enzyme_primal_ret = cx.create_metadata(b"enzyme_primal_return"); + if matches!(attrs.ret_activity, DiffActivity::Dual | DiffActivity::Active) { + args.push(cx.get_metadata_value(enzyme_primal_ret)); } - - let diag_handler = cgcx.create_dcx(); - - let cx = SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); - - // First of all, did the user try to use autodiff without using the -Zautodiff=Enable flag? - if !diff_items.is_empty() - && !cgcx.opts.unstable_opts.autodiff.contains(&rustc_session::config::AutoDiff::Enable) - { - return Err(diag_handler.handle().emit_almost_fatal(AutoDiffWithoutEnable)); - } - - // Here we replace the placeholder code with the actual autodiff code, which calls Enzyme. - for item in diff_items.iter() { - let name = item.source.clone(); - let fn_def: Option<&llvm::Value> = cx.get_function(&name); - let Some(fn_def) = fn_def else { - return Err(llvm_err( - diag_handler.handle(), - LlvmError::PrepareAutoDiff { - src: item.source.clone(), - target: item.target.clone(), - error: "could not find source function".to_owned(), - }, - )); - }; - debug!(?item.target); - let fn_target: Option<&llvm::Value> = cx.get_function(&item.target); - let Some(fn_target) = fn_target else { - return Err(llvm_err( - diag_handler.handle(), - LlvmError::PrepareAutoDiff { - src: item.source.clone(), - target: item.target.clone(), - error: "could not find target function".to_owned(), - }, - )); - }; - - generate_enzyme_call(&cx, fn_def, fn_target, item.attrs.clone()); + if attrs.width > 1 { + let enzyme_width = cx.create_metadata(b"enzyme_width"); + args.push(cx.get_metadata_value(enzyme_width)); + args.push(cx.get_const_int(cx.type_i64(), attrs.width as u64)); } - // FIXME(ZuseZ4): support SanitizeHWAddress and prevent illegal/unsupported opts + match_args_from_caller_to_enzyme( + &cx, + builder, + attrs.width, + &mut args, + &attrs.input_activity, + fn_args, + ); - trace!("done with differentiate()"); + let call = builder.call(enzyme_ty, None, None, ad_fn, &args, None, None); - Ok(()) + builder.store_to_place(call, dest.val); } diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 0b96b63bc857..9ec7b0f80aee 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -4,14 +4,15 @@ use rustc_abi::{Align, HasDataLayout, Primitive, Scalar, Size, WrappingRange}; use rustc_codegen_ssa::common; use rustc_codegen_ssa::traits::*; use rustc_hir::LangItem; +use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::interpret::{ Allocation, ConstAllocation, ErrorHandled, InitChunk, Pointer, Scalar as InterpScalar, read_target_uint, }; -use rustc_middle::mir::mono::{Linkage, MonoItem}; +use rustc_middle::mir::mono::MonoItem; use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance}; use rustc_middle::{bug, span_bug}; @@ -191,8 +192,8 @@ fn check_and_apply_linkage<'ll, 'tcx>( // linkage and there are no definitions), then // `extern_with_linkage_foo` will instead be initialized to // zero. - let mut real_name = "_rust_extern_with_linkage_".to_string(); - real_name.push_str(sym); + let real_name = + format!("_rust_extern_with_linkage_{:016x}_{sym}", cx.tcx.stable_crate_id(LOCAL_CRATE)); let g2 = cx.define_global(&real_name, llty).unwrap_or_else(|| { cx.sess().dcx().emit_fatal(SymbolAlreadyDefined { span: cx.tcx.def_span(def_id), diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index ee77774c6883..4a7de7d2e69e 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -8,7 +8,6 @@ use std::str; use rustc_abi::{HasDataLayout, Size, TargetDataLayout, VariantIdx}; use rustc_codegen_ssa::back::versioned_llvm_target; use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh}; -use rustc_codegen_ssa::common::TypeKind; use rustc_codegen_ssa::errors as ssa_errors; use rustc_codegen_ssa::traits::*; use rustc_data_structures::base_n::{ALPHANUMERIC_ONLY, ToBaseN}; @@ -213,6 +212,12 @@ pub(crate) unsafe fn create_module<'ll>( target_data_layout = target_data_layout.replace("p8:128:128:128:48", "p8:128:128") } } + if llvm_version < (22, 0, 0) { + if sess.target.arch == "avr" { + // LLVM 22.0 updated the default layout on avr: https://github.com/llvm/llvm-project/pull/153010 + target_data_layout = target_data_layout.replace("n8:16", "n8") + } + } // Ensure the data-layout values hardcoded remain the defaults. { @@ -372,6 +377,15 @@ pub(crate) unsafe fn create_module<'ll>( } } + if let Some(regparm_count) = sess.opts.unstable_opts.regparm { + llvm::add_module_flag_u32( + llmod, + llvm::ModuleFlagMergeBehavior::Error, + "NumRegisterParameters", + regparm_count, + ); + } + if let Some(BranchProtection { bti, pac_ret }) = sess.opts.unstable_opts.branch_protection { if sess.target.arch == "aarch64" { llvm::add_module_flag_u32( @@ -457,6 +471,15 @@ pub(crate) unsafe fn create_module<'ll>( } } + if sess.opts.unstable_opts.indirect_branch_cs_prefix { + llvm::add_module_flag_u32( + llmod, + llvm::ModuleFlagMergeBehavior::Override, + "indirect_branch_cs_prefix", + 1, + ); + } + match (sess.opts.unstable_opts.small_data_threshold, sess.target.small_data_threshold_support()) { // Set up the small-data optimization limit for architectures that use @@ -654,10 +677,6 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { } } impl<'ll> SimpleCx<'ll> { - pub(crate) fn get_return_type(&self, ty: &'ll Type) -> &'ll Type { - assert_eq!(self.type_kind(ty), TypeKind::Function); - unsafe { llvm::LLVMGetReturnType(ty) } - } pub(crate) fn get_type_of_global(&self, val: &'ll Value) -> &'ll Type { unsafe { llvm::LLVMGlobalGetValueType(val) } } @@ -721,16 +740,6 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { llvm::LLVMMDStringInContext2(self.llcx(), name.as_ptr() as *const c_char, name.len()) } } - - pub(crate) fn get_functions(&self) -> Vec<&'ll Value> { - let mut functions = vec![]; - let mut func = unsafe { llvm::LLVMGetFirstFunction(self.llmod()) }; - while let Some(f) = func { - functions.push(f); - func = unsafe { llvm::LLVMGetNextFunction(f) } - } - functions - } } impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs index f6000e728400..a4b60d420f3f 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs @@ -73,48 +73,6 @@ pub(crate) struct CounterExpression { pub(crate) rhs: Counter, } -pub(crate) mod mcdc { - use rustc_middle::mir::coverage::{ConditionId, ConditionInfo, DecisionInfo}; - - /// Must match the layout of `LLVMRustMCDCDecisionParameters`. - #[repr(C)] - #[derive(Clone, Copy, Debug, Default)] - pub(crate) struct DecisionParameters { - bitmap_idx: u32, - num_conditions: u16, - } - - type LLVMConditionId = i16; - - /// Must match the layout of `LLVMRustMCDCBranchParameters`. - #[repr(C)] - #[derive(Clone, Copy, Debug, Default)] - pub(crate) struct BranchParameters { - condition_id: LLVMConditionId, - condition_ids: [LLVMConditionId; 2], - } - - impl From for BranchParameters { - fn from(value: ConditionInfo) -> Self { - let to_llvm_cond_id = |cond_id: Option| { - cond_id.and_then(|id| LLVMConditionId::try_from(id.as_usize()).ok()).unwrap_or(-1) - }; - let ConditionInfo { condition_id, true_next_id, false_next_id } = value; - Self { - condition_id: to_llvm_cond_id(Some(condition_id)), - condition_ids: [to_llvm_cond_id(false_next_id), to_llvm_cond_id(true_next_id)], - } - } - } - - impl From for DecisionParameters { - fn from(info: DecisionInfo) -> Self { - let DecisionInfo { bitmap_idx, num_conditions } = info; - Self { bitmap_idx, num_conditions } - } - } -} - /// A span of source code coordinates to be embedded in coverage metadata. /// /// Must match the layout of `LLVMRustCoverageSpan`. @@ -148,26 +106,14 @@ pub(crate) struct Regions { pub(crate) code_regions: Vec, pub(crate) expansion_regions: Vec, pub(crate) branch_regions: Vec, - pub(crate) mcdc_branch_regions: Vec, - pub(crate) mcdc_decision_regions: Vec, } impl Regions { /// Returns true if none of this structure's tables contain any regions. pub(crate) fn has_no_regions(&self) -> bool { - let Self { - code_regions, - expansion_regions, - branch_regions, - mcdc_branch_regions, - mcdc_decision_regions, - } = self; - - code_regions.is_empty() - && expansion_regions.is_empty() - && branch_regions.is_empty() - && mcdc_branch_regions.is_empty() - && mcdc_decision_regions.is_empty() + let Self { code_regions, expansion_regions, branch_regions } = self; + + code_regions.is_empty() && expansion_regions.is_empty() && branch_regions.is_empty() } } @@ -195,21 +141,3 @@ pub(crate) struct BranchRegion { pub(crate) true_counter: Counter, pub(crate) false_counter: Counter, } - -/// Must match the layout of `LLVMRustCoverageMCDCBranchRegion`. -#[derive(Clone, Debug)] -#[repr(C)] -pub(crate) struct MCDCBranchRegion { - pub(crate) cov_span: CoverageSpan, - pub(crate) true_counter: Counter, - pub(crate) false_counter: Counter, - pub(crate) mcdc_branch_params: mcdc::BranchParameters, -} - -/// Must match the layout of `LLVMRustCoverageMCDCDecisionRegion`. -#[derive(Clone, Debug)] -#[repr(C)] -pub(crate) struct MCDCDecisionRegion { - pub(crate) cov_span: CoverageSpan, - pub(crate) mcdc_decision_params: mcdc::DecisionParameters, -} diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs index 907d6d41a1fb..bc4f6bb6a82b 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs @@ -63,13 +63,7 @@ pub(crate) fn write_function_mappings_to_buffer( expressions: &[ffi::CounterExpression], regions: &ffi::Regions, ) -> Vec { - let ffi::Regions { - code_regions, - expansion_regions, - branch_regions, - mcdc_branch_regions, - mcdc_decision_regions, - } = regions; + let ffi::Regions { code_regions, expansion_regions, branch_regions } = regions; // SAFETY: // - All types are FFI-compatible and have matching representations in Rust/C++. @@ -87,10 +81,6 @@ pub(crate) fn write_function_mappings_to_buffer( expansion_regions.len(), branch_regions.as_ptr(), branch_regions.len(), - mcdc_branch_regions.as_ptr(), - mcdc_branch_regions.len(), - mcdc_decision_regions.as_ptr(), - mcdc_decision_regions.len(), buffer, ) }) diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 8c9dfcfd18c2..d1cb95507d91 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -1,3 +1,4 @@ +use std::assert_matches::assert_matches; use std::sync::Arc; use itertools::Itertools; @@ -5,6 +6,7 @@ use rustc_abi::Align; use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods}; use rustc_data_structures::fx::FxIndexMap; use rustc_index::IndexVec; +use rustc_macros::TryFromU32; use rustc_middle::ty::TyCtxt; use rustc_session::RemapFileNameExt; use rustc_session::config::RemapPathScopeComponents; @@ -20,6 +22,23 @@ mod covfun; mod spans; mod unused; +/// Version number that will be included the `__llvm_covmap` section header. +/// Corresponds to LLVM's `llvm::coverage::CovMapVersion` (in `CoverageMapping.h`), +/// or at least the subset that we know and care about. +/// +/// Note that version `n` is encoded as `(n-1)`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, TryFromU32)] +enum CovmapVersion { + /// Used by LLVM 18 onwards. + Version7 = 6, +} + +impl CovmapVersion { + fn to_u32(self) -> u32 { + self as u32 + } +} + /// Generates and exports the coverage map, which is embedded in special /// linker sections in the final binary. /// @@ -29,19 +48,13 @@ pub(crate) fn finalize(cx: &mut CodegenCx<'_, '_>) { let tcx = cx.tcx; // Ensure that LLVM is using a version of the coverage mapping format that - // agrees with our Rust-side code. Expected versions (encoded as n-1) are: - // - `CovMapVersion::Version7` (6) used by LLVM 18-19 - let covmap_version = { - let llvm_covmap_version = llvm_cov::mapping_version(); - let expected_versions = 6..=6; - assert!( - expected_versions.contains(&llvm_covmap_version), - "Coverage mapping version exposed by `llvm-wrapper` is out of sync; \ - expected {expected_versions:?} but was {llvm_covmap_version}" - ); - // This is the version number that we will embed in the covmap section: - llvm_covmap_version - }; + // agrees with our Rust-side code. Expected versions are: + // - `Version7` (6) used by LLVM 18 onwards. + let covmap_version = + CovmapVersion::try_from(llvm_cov::mapping_version()).unwrap_or_else(|raw_version: u32| { + panic!("unknown coverage mapping version reported by `llvm-wrapper`: {raw_version}") + }); + assert_matches!(covmap_version, CovmapVersion::Version7); debug!("Generating coverage map for CodegenUnit: `{}`", cx.codegen_unit.name()); @@ -201,7 +214,11 @@ impl VirtualFileMapping { /// Generates the contents of the covmap record for this CGU, which mostly /// consists of a header and a list of filenames. The record is then stored /// as a global variable in the `__llvm_covmap` section. -fn generate_covmap_record<'ll>(cx: &mut CodegenCx<'ll, '_>, version: u32, filenames_buffer: &[u8]) { +fn generate_covmap_record<'ll>( + cx: &mut CodegenCx<'ll, '_>, + version: CovmapVersion, + filenames_buffer: &[u8], +) { // A covmap record consists of four target-endian u32 values, followed by // the encoded filenames table. Two of the header fields are unused in // modern versions of the LLVM coverage mapping format, and are always 0. @@ -212,7 +229,7 @@ fn generate_covmap_record<'ll>(cx: &mut CodegenCx<'ll, '_>, version: u32, filena cx.const_u32(0), // (unused) cx.const_u32(filenames_buffer.len() as u32), cx.const_u32(0), // (unused) - cx.const_u32(version), + cx.const_u32(version.to_u32()), ], /* packed */ false, ); diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs index b704cf2b1cd4..e0da8d368762 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs @@ -27,6 +27,9 @@ use crate::llvm; /// the final record that will be embedded in the `__llvm_covfun` section. #[derive(Debug)] pub(crate) struct CovfunRecord<'tcx> { + /// Not used directly, but helpful in debug messages. + _instance: Instance<'tcx>, + mangled_function_name: &'tcx str, source_hash: u64, is_used: bool, @@ -55,6 +58,7 @@ pub(crate) fn prepare_covfun_record<'tcx>( let expressions = prepare_expressions(ids_info); let mut covfun = CovfunRecord { + _instance: instance, mangled_function_name: tcx.symbol_name(instance).name, source_hash: if is_used { fn_cov_info.function_source_hash } else { 0 }, is_used, @@ -102,11 +106,21 @@ fn fill_region_tables<'tcx>( ids_info: &'tcx CoverageIdsInfo, covfun: &mut CovfunRecord<'tcx>, ) { + // If this function is unused, replace all counters with zero. + let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter { + let term = if covfun.is_used { + ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term") + } else { + CovTerm::Zero + }; + ffi::Counter::from_term(term) + }; + // Currently a function's mappings must all be in the same file, so use the // first mapping's span to determine the file. let source_map = tcx.sess.source_map(); let Some(first_span) = (try { fn_cov_info.mappings.first()?.span }) else { - debug_assert!(false, "function has no mappings: {:?}", covfun.mangled_function_name); + debug_assert!(false, "function has no mappings: {covfun:?}"); return; }; let source_file = source_map.lookup_source_file(first_span.lo()); @@ -117,7 +131,7 @@ fn fill_region_tables<'tcx>( // codegen needs to handle that gracefully to avoid #133606. // It's hard for tests to trigger this organically, so instead we set // `-Zcoverage-options=discard-all-spans-in-codegen` to force it to occur. - let discard_all = tcx.sess.coverage_discard_all_spans_in_codegen(); + let discard_all = tcx.sess.coverage_options().discard_all_spans_in_codegen; let make_coords = |span: Span| { if discard_all { None } else { spans::make_coords(source_map, &source_file, span) } }; @@ -126,23 +140,11 @@ fn fill_region_tables<'tcx>( code_regions, expansion_regions: _, // FIXME(Zalathar): Fill out support for expansion regions branch_regions, - mcdc_branch_regions, - mcdc_decision_regions, } = &mut covfun.regions; // For each counter/region pair in this function+file, convert it to a // form suitable for FFI. for &Mapping { ref kind, span } in &fn_cov_info.mappings { - // If this function is unused, replace all counters with zero. - let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter { - let term = if covfun.is_used { - ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term") - } else { - CovTerm::Zero - }; - ffi::Counter::from_term(term) - }; - let Some(coords) = make_coords(span) else { continue }; let cov_span = coords.make_coverage_span(local_file_id); @@ -157,20 +159,6 @@ fn fill_region_tables<'tcx>( false_counter: counter_for_bcb(false_bcb), }); } - MappingKind::MCDCBranch { true_bcb, false_bcb, mcdc_params } => { - mcdc_branch_regions.push(ffi::MCDCBranchRegion { - cov_span, - true_counter: counter_for_bcb(true_bcb), - false_counter: counter_for_bcb(false_bcb), - mcdc_branch_params: ffi::mcdc::BranchParameters::from(mcdc_params), - }); - } - MappingKind::MCDCDecision(mcdc_decision_params) => { - mcdc_decision_regions.push(ffi::MCDCDecisionRegion { - cov_span, - mcdc_decision_params: ffi::mcdc::DecisionParameters::from(mcdc_decision_params), - }); - } } } } @@ -184,6 +172,7 @@ pub(crate) fn generate_covfun_record<'tcx>( covfun: &CovfunRecord<'tcx>, ) { let &CovfunRecord { + _instance, mangled_function_name, source_hash, is_used, diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs index 119237abd6b8..6a58f495c9d8 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs @@ -1,11 +1,10 @@ use std::cell::{OnceCell, RefCell}; use std::ffi::{CStr, CString}; -use rustc_abi::Size; use rustc_codegen_ssa::traits::{ - BuilderMethods, ConstCodegenMethods, CoverageInfoBuilderMethods, MiscCodegenMethods, + ConstCodegenMethods, CoverageInfoBuilderMethods, MiscCodegenMethods, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; use rustc_middle::mir::coverage::CoverageKind; use rustc_middle::ty::Instance; use tracing::{debug, instrument}; @@ -28,34 +27,13 @@ pub(crate) struct CguCoverageContext<'ll, 'tcx> { /// symbol name, and `llvm-cov` will exit fatally if it can't resolve that /// hash back to an entry in the binary's `__llvm_prf_names` linker section. pub(crate) pgo_func_name_var_map: RefCell, &'ll llvm::Value>>, - pub(crate) mcdc_condition_bitmap_map: RefCell, Vec<&'ll llvm::Value>>>, covfun_section_name: OnceCell, } impl<'ll, 'tcx> CguCoverageContext<'ll, 'tcx> { pub(crate) fn new() -> Self { - Self { - pgo_func_name_var_map: Default::default(), - mcdc_condition_bitmap_map: Default::default(), - covfun_section_name: Default::default(), - } - } - - /// LLVM use a temp value to record evaluated mcdc test vector of each decision, which is - /// called condition bitmap. In order to handle nested decisions, several condition bitmaps can - /// be allocated for a function body. These values are named `mcdc.addr.{i}` and are a 32-bit - /// integers. They respectively hold the condition bitmaps for decisions with a depth of `i`. - fn try_get_mcdc_condition_bitmap( - &self, - instance: &Instance<'tcx>, - decision_depth: u16, - ) -> Option<&'ll llvm::Value> { - self.mcdc_condition_bitmap_map - .borrow() - .get(instance) - .and_then(|bitmap_map| bitmap_map.get(decision_depth as usize)) - .copied() // Dereference Option<&&Value> to Option<&Value> + Self { pgo_func_name_var_map: Default::default(), covfun_section_name: Default::default() } } /// Returns the list of instances considered "used" in this CGU, as @@ -105,38 +83,6 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { } impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { - fn init_coverage(&mut self, instance: Instance<'tcx>) { - let Some(function_coverage_info) = - self.tcx.instance_mir(instance.def).function_coverage_info.as_deref() - else { - return; - }; - - // If there are no MC/DC bitmaps to set up, return immediately. - if function_coverage_info.mcdc_bitmap_bits == 0 { - return; - } - - let fn_name = self.ensure_pgo_func_name_var(instance); - let hash = self.const_u64(function_coverage_info.function_source_hash); - let bitmap_bits = self.const_u32(function_coverage_info.mcdc_bitmap_bits as u32); - self.mcdc_parameters(fn_name, hash, bitmap_bits); - - // Create pointers named `mcdc.addr.{i}` to stack-allocated condition bitmaps. - let mut cond_bitmaps = vec![]; - for i in 0..function_coverage_info.mcdc_num_condition_bitmaps { - // MC/DC intrinsics will perform loads/stores that use the ABI default - // alignment for i32, so our variable declaration should match. - let align = self.tcx.data_layout.i32_align.abi; - let cond_bitmap = self.alloca(Size::from_bytes(4), align); - llvm::set_value_name(cond_bitmap, format!("mcdc.addr.{i}").as_bytes()); - self.store(self.const_i32(0), cond_bitmap, align); - cond_bitmaps.push(cond_bitmap); - } - - self.coverage_cx().mcdc_condition_bitmap_map.borrow_mut().insert(instance, cond_bitmaps); - } - #[instrument(level = "debug", skip(self))] fn add_coverage(&mut self, instance: Instance<'tcx>, kind: &CoverageKind) { // Our caller should have already taken care of inlining subtleties, @@ -153,7 +99,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { // When that happens, we currently just discard those statements, so // the corresponding code will be undercounted. // FIXME(Zalathar): Find a better solution for mixed-coverage builds. - let Some(coverage_cx) = &bx.cx.coverage_cx else { return }; + let Some(_coverage_cx) = &bx.cx.coverage_cx else { return }; let Some(function_coverage_info) = bx.tcx.instance_mir(instance.def).function_coverage_info.as_deref() @@ -185,30 +131,6 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { } // If a BCB doesn't have an associated physical counter, there's nothing to codegen. CoverageKind::VirtualCounter { .. } => {} - CoverageKind::CondBitmapUpdate { index, decision_depth } => { - let cond_bitmap = coverage_cx - .try_get_mcdc_condition_bitmap(&instance, decision_depth) - .expect("mcdc cond bitmap should have been allocated for updating"); - let cond_index = bx.const_i32(index as i32); - bx.mcdc_condbitmap_update(cond_index, cond_bitmap); - } - CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth } => { - let cond_bitmap = - coverage_cx.try_get_mcdc_condition_bitmap(&instance, decision_depth).expect( - "mcdc cond bitmap should have been allocated for merging \ - into the global bitmap", - ); - assert!( - bitmap_idx as usize <= function_coverage_info.mcdc_bitmap_bits, - "bitmap index of the decision out of range" - ); - - let fn_name = bx.ensure_pgo_func_name_var(instance); - let hash = bx.const_u64(function_coverage_info.function_source_hash); - let bitmap_index = bx.const_u32(bitmap_idx); - bx.mcdc_tvbitmap_update(fn_name, hash, bitmap_index, cond_bitmap); - bx.mcdc_condbitmap_reset(cond_bitmap); - } } } } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index bcfa0381cc1b..6eb7042da617 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -2,9 +2,7 @@ use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; -use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::LOCAL_CRATE; -use rustc_hir::find_attr; use rustc_middle::bug; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerType; use rustc_session::config::{CrateType, DebugInfo}; @@ -86,9 +84,6 @@ pub(crate) fn get_or_insert_gdb_debug_scripts_section_global<'ll>( } pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { - let omit_gdb_pretty_printer_section = - find_attr!(cx.tcx.hir_krate_attrs(), AttributeKind::OmitGdbPrettyPrinterSection); - // To ensure the section `__rustc_debug_gdb_scripts_section__` will not create // ODR violations at link time, this section will not be emitted for rlibs since // each rlib could produce a different set of visualizers that would be embedded @@ -117,8 +112,7 @@ pub(crate) fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { } }); - !omit_gdb_pretty_printer_section - && cx.sess().opts.debuginfo != DebugInfo::None + cx.sess().opts.debuginfo != DebugInfo::None && cx.sess().target.emit_debug_gdb_scripts && embed_visualizers } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs index d1502d2b1e62..18a783a348a4 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs @@ -276,7 +276,7 @@ pub(super) fn build_type_with_children<'ll, 'tcx>( && let ty::Adt(adt_def, args) = ty.kind() { let def_id = adt_def.did(); - // If any sub type reference the original type definition and the sub type has a type + // If any child type references the original type definition and the child type has a type // parameter that strictly contains the original parameter, the original type is a recursive // type that can expanding indefinitely. Example, // ``` @@ -285,21 +285,43 @@ pub(super) fn build_type_with_children<'ll, 'tcx>( // Item(T), // } // ``` - let is_expanding_recursive = adt_def.is_enum() - && debug_context(cx).adt_stack.borrow().iter().any(|(parent_def_id, parent_args)| { - if def_id == *parent_def_id { - args.iter().zip(parent_args.iter()).any(|(arg, parent_arg)| { - if let (Some(arg), Some(parent_arg)) = (arg.as_type(), parent_arg.as_type()) - { - arg != parent_arg && arg.contains(parent_arg) - } else { - false - } - }) - } else { - false - } - }); + let is_expanding_recursive = { + let stack = debug_context(cx).adt_stack.borrow(); + stack + .iter() + .enumerate() + .rev() + .skip(1) + .filter(|(_, (ancestor_def_id, _))| def_id == *ancestor_def_id) + .any(|(ancestor_index, (_, ancestor_args))| { + args.iter() + .zip(ancestor_args.iter()) + .filter_map(|(arg, ancestor_arg)| arg.as_type().zip(ancestor_arg.as_type())) + .any(|(arg, ancestor_arg)| + // Strictly contains. + (arg != ancestor_arg && arg.contains(ancestor_arg)) + // Check all types between current and ancestor use the + // ancestor_arg. + // Otherwise, duplicate wrappers in normal recursive type may be + // regarded as expanding. + // ``` + // struct Recursive { + // a: Box>, + // } + // ``` + // It can produce an ADT stack like this, + // - Box + // - Recursive + // - Box> + && stack[ancestor_index + 1..stack.len()].iter().all( + |(_, intermediate_args)| + intermediate_args + .iter() + .filter_map(|arg| arg.as_type()) + .any(|mid_arg| mid_arg.contains(ancestor_arg)) + )) + }) + }; if is_expanding_recursive { // FIXME: indicate that this is an expanding recursive type in stub metadata? return DINodeCreationResult::new(stub_info.metadata, false); diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index 6cbf2dbf7d3f..2c3a84499ac5 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -533,31 +533,26 @@ impl<'ll, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { // First, let's see if this is a method within an inherent impl. Because // if yes, we want to make the result subroutine DIE a child of the // subroutine's self-type. - if let Some(impl_def_id) = cx.tcx.impl_of_assoc(instance.def_id()) { - // If the method does *not* belong to a trait, proceed - if cx.tcx.trait_id_of_impl(impl_def_id).is_none() { - let impl_self_ty = cx.tcx.instantiate_and_normalize_erasing_regions( - instance.args, - cx.typing_env(), - cx.tcx.type_of(impl_def_id), - ); - - // Only "class" methods are generally understood by LLVM, - // so avoid methods on other types (e.g., `<*mut T>::null`). - if let ty::Adt(def, ..) = impl_self_ty.kind() - && !def.is_box() - { - // Again, only create type information if full debuginfo is enabled - if cx.sess().opts.debuginfo == DebugInfo::Full && !impl_self_ty.has_param() - { - return (type_di_node(cx, impl_self_ty), true); - } else { - return (namespace::item_namespace(cx, def.did()), false); - } + // For trait method impls we still use the "parallel namespace" + // strategy + if let Some(imp_def_id) = cx.tcx.inherent_impl_of_assoc(instance.def_id()) { + let impl_self_ty = cx.tcx.instantiate_and_normalize_erasing_regions( + instance.args, + cx.typing_env(), + cx.tcx.type_of(imp_def_id), + ); + + // Only "class" methods are generally understood by LLVM, + // so avoid methods on other types (e.g., `<*mut T>::null`). + if let ty::Adt(def, ..) = impl_self_ty.kind() + && !def.is_box() + { + // Again, only create type information if full debuginfo is enabled + if cx.sess().opts.debuginfo == DebugInfo::Full && !impl_self_ty.has_param() { + return (type_di_node(cx, impl_self_ty), true); + } else { + return (namespace::item_namespace(cx, def.did()), false); } - } else { - // For trait method impls we still use the "parallel namespace" - // strategy } } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 7b27e496986a..49d3dedbeabd 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -3,24 +3,29 @@ use std::cmp::Ordering; use rustc_abi::{Align, BackendRepr, ExternAbi, Float, HasDataLayout, Primitive, Size}; use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh}; +use rustc_codegen_ssa::codegen_attrs::autodiff_attrs; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; use rustc_codegen_ssa::errors::{ExpectedPointerMutability, InvalidMonomorphization}; use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue}; use rustc_codegen_ssa::mir::place::{PlaceRef, PlaceValue}; use rustc_codegen_ssa::traits::*; -use rustc_hir as hir; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::{self as hir}; use rustc_middle::mir::BinOp; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, LayoutOf}; -use rustc_middle::ty::{self, GenericArgsRef, Ty}; +use rustc_middle::ty::{self, GenericArgsRef, Instance, Ty, TyCtxt, TypingEnv}; use rustc_middle::{bug, span_bug}; use rustc_span::{Span, Symbol, sym}; -use rustc_symbol_mangling::mangle_internal_symbol; +use rustc_symbol_mangling::{mangle_internal_symbol, symbol_name_for_instance_in_crate}; +use rustc_target::callconv::PassMode; use rustc_target::spec::PanicStrategy; use tracing::debug; use crate::abi::FnAbiLlvmExt; use crate::builder::Builder; +use crate::builder::autodiff::{adjust_activity_to_abi, generate_enzyme_call}; use crate::context::CodegenCx; +use crate::errors::AutoDiffWithoutEnable; use crate::llvm::{self, Metadata}; use crate::type_::Type; use crate::type_of::LayoutLlvmExt; @@ -189,6 +194,10 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { &[ptr, args[1].immediate()], ) } + sym::autodiff => { + codegen_autodiff(self, tcx, instance, args, result); + return Ok(()); + } sym::is_val_statically_known => { if let OperandValue::Immediate(imm) = args[0].val { self.call_intrinsic( @@ -321,10 +330,16 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { _ => bug!(), }; let ptr = args[0].immediate(); + let locality = fn_args.const_at(1).to_value().valtree.unwrap_leaf().to_i32(); self.call_intrinsic( "llvm.prefetch", &[self.val_ty(ptr)], - &[ptr, self.const_i32(rw), args[1].immediate(), self.const_i32(cache_type)], + &[ + ptr, + self.const_i32(rw), + self.const_i32(locality), + self.const_i32(cache_type), + ], ) } sym::carrying_mul_add => { @@ -1113,6 +1128,143 @@ fn get_rust_try_fn<'a, 'll, 'tcx>( rust_try } +fn codegen_autodiff<'ll, 'tcx>( + bx: &mut Builder<'_, 'll, 'tcx>, + tcx: TyCtxt<'tcx>, + instance: ty::Instance<'tcx>, + args: &[OperandRef<'tcx, &'ll Value>], + result: PlaceRef<'tcx, &'ll Value>, +) { + if !tcx.sess.opts.unstable_opts.autodiff.contains(&rustc_session::config::AutoDiff::Enable) { + let _ = tcx.dcx().emit_almost_fatal(AutoDiffWithoutEnable); + } + + let fn_args = instance.args; + let callee_ty = instance.ty(tcx, bx.typing_env()); + + let sig = callee_ty.fn_sig(tcx).skip_binder(); + + let ret_ty = sig.output(); + let llret_ty = bx.layout_of(ret_ty).llvm_type(bx); + + // Get source, diff, and attrs + let (source_id, source_args) = match fn_args.into_type_list(tcx)[0].kind() { + ty::FnDef(def_id, source_params) => (def_id, source_params), + _ => bug!("invalid autodiff intrinsic args"), + }; + + let fn_source = match Instance::try_resolve(tcx, bx.cx.typing_env(), *source_id, source_args) { + Ok(Some(instance)) => instance, + Ok(None) => bug!( + "could not resolve ({:?}, {:?}) to a specific autodiff instance", + source_id, + source_args + ), + Err(_) => { + // An error has already been emitted + return; + } + }; + + let source_symbol = symbol_name_for_instance_in_crate(tcx, fn_source.clone(), LOCAL_CRATE); + let Some(fn_to_diff) = bx.cx.get_function(&source_symbol) else { + bug!("could not find source function") + }; + + let (diff_id, diff_args) = match fn_args.into_type_list(tcx)[1].kind() { + ty::FnDef(def_id, diff_args) => (def_id, diff_args), + _ => bug!("invalid args"), + }; + + let fn_diff = match Instance::try_resolve(tcx, bx.cx.typing_env(), *diff_id, diff_args) { + Ok(Some(instance)) => instance, + Ok(None) => bug!( + "could not resolve ({:?}, {:?}) to a specific autodiff instance", + diff_id, + diff_args + ), + Err(_) => { + // An error has already been emitted + return; + } + }; + + let val_arr = get_args_from_tuple(bx, args[2], fn_diff); + let diff_symbol = symbol_name_for_instance_in_crate(tcx, fn_diff.clone(), LOCAL_CRATE); + + let Some(mut diff_attrs) = autodiff_attrs(tcx, fn_diff.def_id()) else { + bug!("could not find autodiff attrs") + }; + + adjust_activity_to_abi( + tcx, + fn_source.ty(tcx, TypingEnv::fully_monomorphized()), + &mut diff_attrs.input_activity, + ); + + // Build body + generate_enzyme_call( + bx, + bx.cx, + fn_to_diff, + &diff_symbol, + llret_ty, + &val_arr, + diff_attrs.clone(), + result, + ); +} + +fn get_args_from_tuple<'ll, 'tcx>( + bx: &mut Builder<'_, 'll, 'tcx>, + tuple_op: OperandRef<'tcx, &'ll Value>, + fn_instance: Instance<'tcx>, +) -> Vec<&'ll Value> { + let cx = bx.cx; + let fn_abi = cx.fn_abi_of_instance(fn_instance, ty::List::empty()); + + match tuple_op.val { + OperandValue::Immediate(val) => vec![val], + OperandValue::Pair(v1, v2) => vec![v1, v2], + OperandValue::Ref(ptr) => { + let tuple_place = PlaceRef { val: ptr, layout: tuple_op.layout }; + + let mut result = Vec::with_capacity(fn_abi.args.len()); + let mut tuple_index = 0; + + for arg in &fn_abi.args { + match arg.mode { + PassMode::Ignore => {} + PassMode::Direct(_) | PassMode::Cast { .. } => { + let field = tuple_place.project_field(bx, tuple_index); + let llvm_ty = field.layout.llvm_type(bx.cx); + let val = bx.load(llvm_ty, field.val.llval, field.val.align); + result.push(val); + tuple_index += 1; + } + PassMode::Pair(_, _) => { + let field = tuple_place.project_field(bx, tuple_index); + let llvm_ty = field.layout.llvm_type(bx.cx); + let pair_val = bx.load(llvm_ty, field.val.llval, field.val.align); + result.push(bx.extract_value(pair_val, 0)); + result.push(bx.extract_value(pair_val, 1)); + tuple_index += 1; + } + PassMode::Indirect { .. } => { + let field = tuple_place.project_field(bx, tuple_index); + result.push(field.val.llval); + tuple_index += 1; + } + } + } + + result + } + + OperandValue::ZeroSized => vec![], + } +} + fn generic_simd_intrinsic<'ll, 'tcx>( bx: &mut Builder<'_, 'll, 'tcx>, name: Symbol, diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index ca84b6de8b11..0fcf31d79930 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -30,7 +30,6 @@ use context::SimpleCx; use errors::ParseTargetMachineConfig; use llvm_util::target_config; use rustc_ast::expand::allocator::AllocatorKind; -use rustc_ast::expand::autodiff_attrs::AutoDiffItem; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule}; use rustc_codegen_ssa::back::write::{ CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryConfig, TargetMachineFactoryFn, @@ -47,18 +46,11 @@ use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest}; use rustc_span::Symbol; -mod back { - pub(crate) mod archive; - pub(crate) mod lto; - pub(crate) mod owned_target_machine; - mod profiling; - pub(crate) mod write; -} - mod abi; mod allocator; mod asm; mod attributes; +mod back; mod base; mod builder; mod callee; @@ -173,15 +165,10 @@ impl WriteBackendMethods for LlvmCodegenBackend { exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, - diff_fncs: Vec, ) -> Result, FatalError> { let mut module = back::lto::run_fat(cgcx, exported_symbols_for_lto, each_linked_rlib_for_lto, modules)?; - if !diff_fncs.is_empty() { - builder::autodiff::differentiate(&module, cgcx, diff_fncs)?; - } - let dcx = cgcx.create_dcx(); let dcx = dcx.handle(); back::lto::run_pass_manager(cgcx, dcx, &mut module, false)?; diff --git a/compiler/rustc_codegen_llvm/src/llvm/archive_ro.rs b/compiler/rustc_codegen_llvm/src/llvm/archive_ro.rs deleted file mode 100644 index 51bcc4d123d3..000000000000 --- a/compiler/rustc_codegen_llvm/src/llvm/archive_ro.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! A wrapper around LLVM's archive (.a) code - -use std::path::Path; -use std::{slice, str}; - -use rustc_fs_util::path_to_c_string; - -pub(crate) struct ArchiveRO { - pub raw: &'static mut super::Archive, -} - -unsafe impl Send for ArchiveRO {} - -pub(crate) struct Iter<'a> { - raw: &'a mut super::ArchiveIterator<'a>, -} - -pub(crate) struct Child<'a> { - pub raw: &'a mut super::ArchiveChild<'a>, -} - -impl ArchiveRO { - /// Opens a static archive for read-only purposes. This is more optimized - /// than the `open` method because it uses LLVM's internal `Archive` class - /// rather than shelling out to `ar` for everything. - /// - /// If this archive is used with a mutable method, then an error will be - /// raised. - pub(crate) fn open(dst: &Path) -> Result { - unsafe { - let s = path_to_c_string(dst); - let ar = super::LLVMRustOpenArchive(s.as_ptr()).ok_or_else(|| { - super::last_error().unwrap_or_else(|| "failed to open archive".to_owned()) - })?; - Ok(ArchiveRO { raw: ar }) - } - } - - pub(crate) fn iter(&self) -> Iter<'_> { - unsafe { Iter { raw: super::LLVMRustArchiveIteratorNew(self.raw) } } - } -} - -impl Drop for ArchiveRO { - fn drop(&mut self) { - unsafe { - super::LLVMRustDestroyArchive(&mut *(self.raw as *mut _)); - } - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = Result, String>; - - fn next(&mut self) -> Option, String>> { - unsafe { - match super::LLVMRustArchiveIteratorNext(self.raw) { - Some(raw) => Some(Ok(Child { raw })), - None => super::last_error().map(Err), - } - } - } -} - -impl<'a> Drop for Iter<'a> { - fn drop(&mut self) { - unsafe { - super::LLVMRustArchiveIteratorFree(&mut *(self.raw as *mut _)); - } - } -} - -impl<'a> Child<'a> { - pub(crate) fn name(&self) -> Option<&'a str> { - unsafe { - let mut name_len = 0; - let name_ptr = super::LLVMRustArchiveChildName(self.raw, &mut name_len); - if name_ptr.is_null() { - None - } else { - let name = slice::from_raw_parts(name_ptr as *const u8, name_len as usize); - str::from_utf8(name).ok().map(|s| s.trim()) - } - } - } -} - -impl<'a> Drop for Child<'a> { - fn drop(&mut self) { - unsafe { - super::LLVMRustArchiveChildFree(&mut *(self.raw as *mut _)); - } - } -} diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 2443194ff483..2461f70a86e3 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -97,6 +97,7 @@ pub(crate) enum ModuleFlagMergeBehavior { // Consts for the LLVM CallConv type, pre-cast to usize. +/// Must match the layout of `LLVMTailCallKind`. #[derive(Copy, Clone, PartialEq, Debug)] #[repr(C)] #[allow(dead_code)] @@ -249,6 +250,8 @@ pub(crate) enum AttributeKind { FnRetThunkExtern = 41, Writable = 42, DeadOnUnwind = 43, + DeadOnReturn = 44, + CapturesReadOnly = 45, } /// LLVMIntPredicate @@ -331,10 +334,15 @@ impl RealPredicate { } } -/// LLVMTypeKind -#[derive(Copy, Clone, PartialEq, Debug)] +/// Must match the layout of `LLVMTypeKind`. +/// +/// Use [`RawEnum`] for values of `LLVMTypeKind` returned from LLVM, +/// to avoid risk of UB if LLVM adds new enum values. +/// +/// All of LLVM's variants should be declared here, even if no Rust-side code refers +/// to them, because unknown variants will cause [`RawEnum::to_rust`] to panic. +#[derive(Copy, Clone, PartialEq, Debug, TryFromU32)] #[repr(C)] -#[expect(dead_code, reason = "Some variants are unused, but are kept to match LLVM-C")] pub(crate) enum TypeKind { Void = 0, Half = 1, @@ -609,17 +617,6 @@ pub(crate) enum DiagnosticLevel { Remark, } -/// LLVMRustArchiveKind -#[derive(Copy, Clone)] -#[repr(C)] -pub(crate) enum ArchiveKind { - K_GNU, - K_BSD, - K_DARWIN, - K_COFF, - K_AIXBIG, -} - unsafe extern "C" { // LLVMRustThinLTOData pub(crate) type ThinLTOData; @@ -768,19 +765,12 @@ pub(crate) struct Builder<'a>(InvariantOpaque<'a>); pub(crate) struct PassManager<'a>(InvariantOpaque<'a>); unsafe extern "C" { pub type TargetMachine; - pub(crate) type Archive; } -#[repr(C)] -pub(crate) struct ArchiveIterator<'a>(InvariantOpaque<'a>); -#[repr(C)] -pub(crate) struct ArchiveChild<'a>(InvariantOpaque<'a>); unsafe extern "C" { pub(crate) type Twine; pub(crate) type DiagnosticInfo; pub(crate) type SMDiagnostic; } -#[repr(C)] -pub(crate) struct RustArchiveMember<'a>(InvariantOpaque<'a>); /// Opaque pointee of `LLVMOperandBundleRef`. #[repr(C)] pub(crate) struct OperandBundle<'a>(InvariantOpaque<'a>); @@ -1045,6 +1035,8 @@ unsafe extern "C" { CanThrow: llvm::Bool, ) -> &'ll Value; + pub(crate) safe fn LLVMGetTypeKind(Ty: &Type) -> RawEnum; + // Operations on integer types pub(crate) fn LLVMInt1TypeInContext(C: &Context) -> &Type; pub(crate) fn LLVMInt8TypeInContext(C: &Context) -> &Type; @@ -1196,7 +1188,7 @@ unsafe extern "C" { pub(crate) safe fn LLVMIsGlobalConstant(GlobalVar: &Value) -> Bool; pub(crate) safe fn LLVMSetGlobalConstant(GlobalVar: &Value, IsConstant: Bool); pub(crate) safe fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool); - pub(crate) safe fn LLVMRustSetTailCallKind(CallInst: &Value, Kind: TailCallKind); + pub(crate) safe fn LLVMSetTailCallKind(CallInst: &Value, kind: TailCallKind); // Operations on attributes pub(crate) fn LLVMCreateStringAttribute( @@ -1840,9 +1832,6 @@ unsafe extern "C" { // Create and destroy contexts. pub(crate) fn LLVMRustContextCreate(shouldDiscardNames: bool) -> &'static mut Context; - /// See llvm::LLVMTypeKind::getTypeID. - pub(crate) fn LLVMRustGetTypeKind(Ty: &Type) -> TypeKind; - // Operations on all values pub(crate) fn LLVMRustGlobalAddMetadata<'a>( Val: &'a Value, @@ -2056,10 +2045,6 @@ unsafe extern "C" { NumExpansionRegions: size_t, BranchRegions: *const crate::coverageinfo::ffi::BranchRegion, NumBranchRegions: size_t, - MCDCBranchRegions: *const crate::coverageinfo::ffi::MCDCBranchRegion, - NumMCDCBranchRegions: size_t, - MCDCDecisionRegions: *const crate::coverageinfo::ffi::MCDCDecisionRegion, - NumMCDCDecisionRegions: size_t, BufferOut: &RustString, ); @@ -2441,7 +2426,7 @@ unsafe extern "C" { OutputObjFile: *const c_char, DebugInfoCompression: *const c_char, UseEmulatedTls: bool, - ArgsCstrBuff: *const c_char, + ArgsCstrBuff: *const c_uchar, // See "PTR_LEN_STR". ArgsCstrBuffLen: usize, UseWasmEH: bool, ) -> *mut TargetMachine; @@ -2508,19 +2493,6 @@ unsafe extern "C" { pub(crate) fn LLVMRustSetNormalizedTarget(M: &Module, triple: *const c_char); pub(crate) fn LLVMRustRunRestrictionPass(M: &Module, syms: *const *const c_char, len: size_t); - pub(crate) fn LLVMRustOpenArchive(path: *const c_char) -> Option<&'static mut Archive>; - pub(crate) fn LLVMRustArchiveIteratorNew(AR: &Archive) -> &mut ArchiveIterator<'_>; - pub(crate) fn LLVMRustArchiveIteratorNext<'a>( - AIR: &ArchiveIterator<'a>, - ) -> Option<&'a mut ArchiveChild<'a>>; - pub(crate) fn LLVMRustArchiveChildName( - ACR: &ArchiveChild<'_>, - size: &mut size_t, - ) -> *const c_char; - pub(crate) fn LLVMRustArchiveChildFree<'a>(ACR: &'a mut ArchiveChild<'a>); - pub(crate) fn LLVMRustArchiveIteratorFree<'a>(AIR: &'a mut ArchiveIterator<'a>); - pub(crate) fn LLVMRustDestroyArchive(AR: &'static mut Archive); - pub(crate) fn LLVMRustWriteTwineToString(T: &Twine, s: &RustString); pub(crate) fn LLVMRustUnpackOptimizationDiagnostic<'a>( @@ -2558,21 +2530,6 @@ unsafe extern "C" { num_ranges: &mut usize, ) -> bool; - pub(crate) fn LLVMRustWriteArchive( - Dst: *const c_char, - NumMembers: size_t, - Members: *const &RustArchiveMember<'_>, - WriteSymbtab: bool, - Kind: ArchiveKind, - isEC: bool, - ) -> LLVMRustResult; - pub(crate) fn LLVMRustArchiveMemberNew<'a>( - Filename: *const c_char, - Name: *const c_char, - Child: Option<&ArchiveChild<'a>>, - ) -> &'a mut RustArchiveMember<'a>; - pub(crate) fn LLVMRustArchiveMemberFree<'a>(Member: &'a mut RustArchiveMember<'a>); - pub(crate) fn LLVMRustSetDataLayoutFromTargetMachine<'a>(M: &'a Module, TM: &'a TargetMachine); pub(crate) fn LLVMRustPositionBuilderPastAllocas<'a>(B: &Builder<'a>, Fn: &'a Value); diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index 154ba4fd6901..7fea7b79a8cf 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -3,7 +3,6 @@ use std::ffi::{CStr, CString}; use std::num::NonZero; use std::ptr; -use std::str::FromStr; use std::string::FromUtf8Error; use libc::c_uint; @@ -16,7 +15,6 @@ pub(crate) use self::MetadataType::*; pub(crate) use self::ffi::*; use crate::common::AsCCharPtr; -pub(crate) mod archive_ro; pub(crate) mod diagnostic; pub(crate) mod enzyme_ffi; mod ffi; @@ -42,32 +40,6 @@ pub(crate) fn AddFunctionAttributes<'ll>( } } -pub(crate) fn HasAttributeAtIndex<'ll>( - llfn: &'ll Value, - idx: AttributePlace, - kind: AttributeKind, -) -> bool { - unsafe { LLVMRustHasAttributeAtIndex(llfn, idx.as_uint(), kind) } -} - -pub(crate) fn HasStringAttribute<'ll>(llfn: &'ll Value, name: &str) -> bool { - unsafe { LLVMRustHasFnAttribute(llfn, name.as_c_char_ptr(), name.len()) } -} - -pub(crate) fn RemoveStringAttrFromFn<'ll>(llfn: &'ll Value, name: &str) { - unsafe { LLVMRustRemoveFnAttribute(llfn, name.as_c_char_ptr(), name.len()) } -} - -pub(crate) fn RemoveRustEnumAttributeAtIndex( - llfn: &Value, - place: AttributePlace, - kind: AttributeKind, -) { - unsafe { - LLVMRustRemoveEnumAttributeAtIndex(llfn, place.as_uint(), kind); - } -} - pub(crate) fn AddCallSiteAttributes<'ll>( callsite: &'ll Value, idx: AttributePlace, @@ -178,21 +150,6 @@ pub(crate) enum CodeGenOptSize { CodeGenOptSizeAggressive = 2, } -impl FromStr for ArchiveKind { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "gnu" => Ok(ArchiveKind::K_GNU), - "bsd" => Ok(ArchiveKind::K_BSD), - "darwin" => Ok(ArchiveKind::K_DARWIN), - "coff" => Ok(ArchiveKind::K_COFF), - "aix_big" => Ok(ArchiveKind::K_AIXBIG), - _ => Err(()), - } - } -} - pub(crate) fn SetInstructionCallConv(instr: &Value, cc: CallConv) { unsafe { LLVMSetInstructionCallConv(instr, cc as c_uint); diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 53899da183a3..90f7cd43268f 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -262,12 +262,22 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option None, // only existed in 18 ("arm", "fp16") => Some(LLVMFeature::new("fullfp16")), + // NVPTX targets added in LLVM 20 + ("nvptx64", "sm_100") if get_version().0 < 20 => None, + ("nvptx64", "sm_100a") if get_version().0 < 20 => None, + ("nvptx64", "sm_101") if get_version().0 < 20 => None, + ("nvptx64", "sm_101a") if get_version().0 < 20 => None, + ("nvptx64", "sm_120") if get_version().0 < 20 => None, + ("nvptx64", "sm_120a") if get_version().0 < 20 => None, + ("nvptx64", "ptx86") if get_version().0 < 20 => None, + ("nvptx64", "ptx87") if get_version().0 < 20 => None, // Filter out features that are not supported by the current LLVM version ("loongarch64", "div32" | "lam-bh" | "lamcas" | "ld-seq-sa" | "scq") if get_version().0 < 20 => { None } + ("loongarch32" | "loongarch64", "32s") if get_version().0 < 21 => None, // Filter out features that are not supported by the current LLVM version ("riscv32" | "riscv64", "zacas") if get_version().0 < 20 => None, ( @@ -324,15 +334,12 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option TargetConfig { - // Add base features for the target. - // We do *not* add the -Ctarget-features there, and instead duplicate the logic for that below. - // The reason is that if LLVM considers a feature implied but we do not, we don't want that to - // show up in `cfg`. That way, `cfg` is entirely under our control -- except for the handling of - // the target CPU, that is still expanded to target features (with all their implied features) - // by LLVM. let target_machine = create_informational_target_machine(sess, true); let (unstable_target_features, target_features) = cfg_target_feature(sess, |feature| { + // This closure determines whether the target CPU has the feature according to LLVM. We do + // *not* consider the `-Ctarget-feature`s here, as that will be handled later in + // `cfg_target_feature`. if let Some(feat) = to_llvm_features(sess, feature) { // All the LLVM features this expands to must be enabled. for llvm_feature in feat { @@ -371,24 +378,25 @@ fn update_target_reliable_float_cfg(sess: &Session, cfg: &mut TargetConfig) { let target_abi = sess.target.options.abi.as_ref(); let target_pointer_width = sess.target.pointer_width; let version = get_version(); + let lt_20_1_1 = version < (20, 1, 1); + let lt_21_0_0 = version < (21, 0, 0); cfg.has_reliable_f16 = match (target_arch, target_os) { - // Selection failure - ("s390x", _) => false, - // LLVM crash without neon (now fixed) + // LLVM crash without neon (fixed in llvm20) ("aarch64", _) - if !cfg.target_features.iter().any(|f| f.as_str() == "neon") - && version < (20, 1, 1) => + if !cfg.target_features.iter().any(|f| f.as_str() == "neon") && lt_20_1_1 => { false } // Unsupported ("arm64ec", _) => false, + // Selection failure (fixed in llvm21) + ("s390x", _) if lt_21_0_0 => false, // MinGW ABI bugs ("x86_64", "windows") if target_env == "gnu" && target_abi != "llvm" => false, // Infinite recursion ("csky", _) => false, - ("hexagon", _) => false, + ("hexagon", _) if lt_21_0_0 => false, // (fixed in llvm21) ("powerpc" | "powerpc64", _) => false, ("sparc" | "sparc64", _) => false, ("wasm32" | "wasm64", _) => false, @@ -401,9 +409,10 @@ fn update_target_reliable_float_cfg(sess: &Session, cfg: &mut TargetConfig) { cfg.has_reliable_f128 = match (target_arch, target_os) { // Unsupported ("arm64ec", _) => false, - // Selection bug - ("mips64" | "mips64r6", _) => false, - // Selection bug + // Selection bug (fixed in llvm20) + ("mips64" | "mips64r6", _) if lt_20_1_1 => false, + // Selection bug . This issue is closed + // but basic math still does not work. ("nvptx64", _) => false, // Unsupported https://github.com/llvm/llvm-project/issues/121122 ("amdgpu", _) => false, @@ -413,8 +422,8 @@ fn update_target_reliable_float_cfg(sess: &Session, cfg: &mut TargetConfig) { // ABI unsupported ("sparc", _) => false, // Stack alignment bug . NB: tests may - // not fail if our compiler-builtins is linked. - ("x86", _) => false, + // not fail if our compiler-builtins is linked. (fixed in llvm21) + ("x86", _) if lt_21_0_0 => false, // MinGW ABI bugs ("x86_64", "windows") if target_env == "gnu" && target_abi != "llvm" => false, // There are no known problems on other platforms, so the only requirement is that symbols diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs index f9edaded60de..5075befae8a5 100644 --- a/compiler/rustc_codegen_llvm/src/mono_item.rs +++ b/compiler/rustc_codegen_llvm/src/mono_item.rs @@ -1,8 +1,9 @@ use rustc_codegen_ssa::traits::*; +use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::bug; -use rustc_middle::mir::mono::{Linkage, Visibility}; +use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; use rustc_session::config::CrateType; diff --git a/compiler/rustc_codegen_llvm/src/type_.rs b/compiler/rustc_codegen_llvm/src/type_.rs index 893655031388..f02d16baf94e 100644 --- a/compiler/rustc_codegen_llvm/src/type_.rs +++ b/compiler/rustc_codegen_llvm/src/type_.rs @@ -204,7 +204,7 @@ impl<'ll, CX: Borrow>> BaseTypeCodegenMethods for GenericCx<'ll, CX> { } fn type_kind(&self, ty: &'ll Type) -> TypeKind { - unsafe { llvm::LLVMRustGetTypeKind(ty).to_generic() } + llvm::LLVMGetTypeKind(ty).to_rust().to_generic() } fn type_ptr(&self) -> &'ll Type { diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 30956fd18557..6a8971de7461 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -26,6 +26,7 @@ rustc_hashes = { path = "../rustc_hashes" } rustc_hir = { path = "../rustc_hir" } rustc_incremental = { path = "../rustc_incremental" } rustc_index = { path = "../rustc_index" } +rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_metadata = { path = "../rustc_metadata" } rustc_middle = { path = "../rustc_middle" } diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index a70d0011d161..42ba01541920 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -101,6 +101,8 @@ codegen_ssa_invalid_monomorphization_basic_float_type = invalid monomorphization codegen_ssa_invalid_monomorphization_basic_integer_type = invalid monomorphization of `{$name}` intrinsic: expected basic integer type, found `{$ty}` +codegen_ssa_invalid_monomorphization_basic_integer_or_ptr_type = invalid monomorphization of `{$name}` intrinsic: expected basic integer or pointer type, found `{$ty}` + codegen_ssa_invalid_monomorphization_cannot_return = invalid monomorphization of `{$name}` intrinsic: cannot return `{$ret_ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` codegen_ssa_invalid_monomorphization_cast_wide_pointer = invalid monomorphization of `{$name}` intrinsic: cannot cast wide pointer `{$ty}` @@ -169,8 +171,8 @@ codegen_ssa_invalid_monomorphization_unsupported_symbol = invalid monomorphizati codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` of size `{$size}` to `{$ret_ty}` -codegen_ssa_invalid_no_sanitize = invalid argument for `no_sanitize` - .note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread` +codegen_ssa_invalid_sanitize = invalid argument for `sanitize` + .note = expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread` codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed @@ -178,6 +180,10 @@ codegen_ssa_ld64_unimplemented_modifier = `as-needed` modifier not implemented y codegen_ssa_lib_def_write_failure = failed to write lib.def file: {$error} +codegen_ssa_link_exe_status_stack_buffer_overrun = 0xc0000409 is `STATUS_STACK_BUFFER_OVERRUN` + .abort_note = this may have been caused by a program abort and not a stack buffer overrun + .event_log_note = consider checking the Application Event Log for Windows Error Reporting events to see the fail fast error code + codegen_ssa_link_exe_unexpected_error = `link.exe` returned an unexpected error codegen_ssa_link_script_unavailable = can only use link script when linking with GNU-like linker @@ -395,6 +401,9 @@ codegen_ssa_version_script_write_failure = failed to write version script: {$err codegen_ssa_visual_studio_not_installed = you may need to install Visual Studio build tools with the "C++ build tools" workload +codegen_ssa_xcrun_about = + the SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file + codegen_ssa_xcrun_command_line_tools_insufficient = when compiling for iOS, tvOS, visionOS or watchOS, you need a full installation of Xcode diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 3710625ac12d..43e1e135a666 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -69,6 +69,15 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&mut CguReuseTr set_reuse(&mut ams.cgu_reuse_tracker); + if tcx.sess.opts.unstable_opts.print_mono_items + && let Some(data) = &ams.cgu_reuse_tracker.data + { + data.actual_reuse.items().all(|(cgu, reuse)| { + println!("CGU_REUSE {cgu} {reuse}"); + true + }); + } + ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess); }); } diff --git a/compiler/rustc_codegen_ssa/src/back/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs index d242efaf4fd4..2274450e20e0 100644 --- a/compiler/rustc_codegen_ssa/src/back/apple.rs +++ b/compiler/rustc_codegen_ssa/src/back/apple.rs @@ -17,7 +17,7 @@ mod tests; /// The canonical name of the desired SDK for a given target. pub(super) fn sdk_name(target: &Target) -> &'static str { - match (&*target.os, &*target.abi) { + match (&*target.os, &*target.env) { ("macos", "") => "MacOSX", ("ios", "") => "iPhoneOS", ("ios", "sim") => "iPhoneSimulator", @@ -34,7 +34,7 @@ pub(super) fn sdk_name(target: &Target) -> &'static str { } pub(super) fn macho_platform(target: &Target) -> u32 { - match (&*target.os, &*target.abi) { + match (&*target.os, &*target.env) { ("macos", _) => object::macho::PLATFORM_MACOS, ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST, ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR, @@ -160,6 +160,10 @@ pub(super) fn add_version_to_llvm_target( pub(super) fn get_sdk_root(sess: &Session) -> Option { let sdk_name = sdk_name(&sess.target); + // Attempt to invoke `xcrun` to find the SDK. + // + // Note that when cross-compiling from e.g. Linux, the `xcrun` binary may sometimes be provided + // as a shim by a cross-compilation helper tool. It usually isn't, but we still try nonetheless. match xcrun_show_sdk_path(sdk_name, sess.verbose_internals()) { Ok((path, stderr)) => { // Emit extra stderr, such as if `-verbose` was passed, or if `xcrun` emitted a warning. @@ -169,7 +173,19 @@ pub(super) fn get_sdk_root(sess: &Session) -> Option { Some(path) } Err(err) => { - let mut diag = sess.dcx().create_err(err); + // Failure to find the SDK is not a hard error, since the user might have specified it + // in a manner unknown to us (moreso if cross-compiling): + // - A compiler driver like `zig cc` which links using an internally bundled SDK. + // - Extra linker arguments (`-Clink-arg=-syslibroot`). + // - A custom linker or custom compiler driver. + // + // Though we still warn, since such cases are uncommon, and it is very hard to debug if + // you do not know the details. + // + // FIXME(madsmtm): Make this a lint, to allow deny warnings to work. + // (Or fix ). + let mut diag = sess.dcx().create_warn(err); + diag.note(fluent::codegen_ssa_xcrun_about); // Recognize common error cases, and give more Rust-specific error messages for those. if let Some(developer_dir) = xcode_select_developer_dir() { @@ -209,6 +225,8 @@ fn xcrun_show_sdk_path( sdk_name: &'static str, verbose: bool, ) -> Result<(PathBuf, String), XcrunError> { + // Intentionally invoke the `xcrun` in PATH, since e.g. nixpkgs provide an `xcrun` shim, so we + // don't want to require `/usr/bin/xcrun`. let mut cmd = Command::new("xcrun"); if verbose { cmd.arg("--verbose"); @@ -280,7 +298,7 @@ fn stdout_to_path(mut stdout: Vec) -> PathBuf { } #[cfg(unix)] let path = ::from_vec(stdout); - #[cfg(not(unix))] // Unimportant, this is only used on macOS - let path = OsString::from(String::from_utf8(stdout).unwrap()); + #[cfg(not(unix))] // Not so important, this is mostly used on macOS + let path = OsString::from(String::from_utf8(stdout).expect("stdout must be UTF-8")); PathBuf::from(path) } diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 162fbf3d6e24..c3777f64e9e9 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -880,6 +880,14 @@ fn link_natively( windows_registry::find_tool(&sess.target.arch, "link.exe").is_some(); sess.dcx().emit_note(errors::LinkExeUnexpectedError); + + // STATUS_STACK_BUFFER_OVERRUN is also used for fast abnormal program termination, e.g. abort(). + // Emit a special diagnostic to let people know that this most likely doesn't indicate a stack buffer overrun. + const STATUS_STACK_BUFFER_OVERRUN: i32 = 0xc0000409u32 as _; + if code == STATUS_STACK_BUFFER_OVERRUN { + sess.dcx().emit_note(errors::LinkExeStatusStackBufferOverrun); + } + if is_vs_installed && has_linker { // the linker is broken sess.dcx().emit_note(errors::RepairVSBuildTools); @@ -1011,11 +1019,12 @@ fn link_natively( (Strip::Debuginfo, _) => { strip_with_external_utility(sess, stripcmd, out_filename, &["--strip-debug"]) } - // Per the manpage, `-x` is the maximum safe strip level for dynamic libraries. (#93988) + + // Per the manpage, --discard-all is the maximum safe strip level for dynamic libraries. (#93988) ( Strip::Symbols, CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro | CrateType::Sdylib, - ) => strip_with_external_utility(sess, stripcmd, out_filename, &["-x"]), + ) => strip_with_external_utility(sess, stripcmd, out_filename, &["--discard-all"]), (Strip::Symbols, _) => { strip_with_external_utility(sess, stripcmd, out_filename, &["--strip-all"]) } @@ -2426,6 +2435,13 @@ fn linker_with_args( // Passed after compiler-generated options to support manual overriding when necessary. add_user_defined_link_args(cmd, sess); + // ------------ Builtin configurable linker scripts ------------ + // The user's link args should be able to overwrite symbols in the compiler's + // linker script that were weakly defined (i.e. defined with `PROVIDE()`). For this + // to work correctly, the user needs to be able to specify linker arguments like + // `--defsym` and `--script` *before* any builtin linker scripts are evaluated. + add_link_script(cmd, sess, tmpdir, crate_type); + // ------------ Object code and libraries, order-dependent ------------ // Post-link CRT objects. @@ -2460,8 +2476,6 @@ fn add_order_independent_options( let apple_sdk_root = add_apple_sdk(cmd, sess, flavor); - add_link_script(cmd, sess, tmpdir, crate_type); - if sess.target.os == "fuchsia" && crate_type == CrateType::Executable && !matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _)) @@ -3025,7 +3039,7 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool /// We need to communicate five things to the linker on Apple/Darwin targets: /// - The architecture. /// - The operating system (and that it's an Apple platform). -/// - The environment / ABI. +/// - The environment. /// - The deployment target. /// - The SDK version. fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) { @@ -3039,7 +3053,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo // `sess.target.arch` (`target_arch`) is not detailed enough. let llvm_arch = sess.target.llvm_target.split_once('-').expect("LLVM target must have arch").0; let target_os = &*sess.target.os; - let target_abi = &*sess.target.abi; + let target_env = &*sess.target.env; // The architecture name to forward to the linker. // @@ -3090,14 +3104,14 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo // > - visionos-simulator // > - xros-simulator // > - driverkit - let platform_name = match (target_os, target_abi) { + let platform_name = match (target_os, target_env) { (os, "") => os, ("ios", "macabi") => "mac-catalyst", ("ios", "sim") => "ios-simulator", ("tvos", "sim") => "tvos-simulator", ("watchos", "sim") => "watchos-simulator", ("visionos", "sim") => "visionos-simulator", - _ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"), + _ => bug!("invalid OS/env combination for Apple target: {target_os}, {target_env}"), }; let min_version = sess.apple_deployment_target().fmt_full().to_string(); @@ -3185,39 +3199,60 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo } fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option { - let os = &sess.target.os; - if sess.target.vendor != "apple" - || !matches!(os.as_ref(), "ios" | "tvos" | "watchos" | "visionos" | "macos") - || !matches!(flavor, LinkerFlavor::Darwin(..)) - { + if !sess.target.is_like_darwin { return None; } - - if os == "macos" && !matches!(flavor, LinkerFlavor::Darwin(Cc::No, _)) { + let LinkerFlavor::Darwin(cc, _) = flavor else { return None; - } - - let sdk_root = sess.time("get_apple_sdk_root", || get_apple_sdk_root(sess))?; + }; - match flavor { - LinkerFlavor::Darwin(Cc::Yes, _) => { - // Use `-isysroot` instead of `--sysroot`, as only the former - // makes Clang treat it as a platform SDK. - // - // This is admittedly a bit strange, as on most targets - // `-isysroot` only applies to include header files, but on Apple - // targets this also applies to libraries and frameworks. - cmd.cc_arg("-isysroot"); - cmd.cc_arg(&sdk_root); - } - LinkerFlavor::Darwin(Cc::No, _) => { - cmd.link_arg("-syslibroot"); - cmd.link_arg(&sdk_root); - } - _ => unreachable!(), + // The default compiler driver on macOS is at `/usr/bin/cc`. This is a trampoline binary that + // effectively invokes `xcrun cc` internally to look up both the compiler binary and the SDK + // root from the current Xcode installation. When cross-compiling, when `rustc` is invoked + // inside Xcode, or when invoking the linker directly, this default logic is unsuitable, so + // instead we invoke `xcrun` manually. + // + // (Note that this doesn't mean we get a duplicate lookup here - passing `SDKROOT` below will + // cause the trampoline binary to skip looking up the SDK itself). + let sdkroot = sess.time("get_apple_sdk_root", || get_apple_sdk_root(sess))?; + + if cc == Cc::Yes { + // There are a few options to pass the SDK root when linking with a C/C++ compiler: + // - The `--sysroot` flag. + // - The `-isysroot` flag. + // - The `SDKROOT` environment variable. + // + // `--sysroot` isn't actually enough to get Clang to treat it as a platform SDK, you need + // to specify `-isysroot`. This is admittedly a bit strange, as on most targets `-isysroot` + // only applies to include header files, but on Apple targets it also applies to libraries + // and frameworks. + // + // This leaves the choice between `-isysroot` and `SDKROOT`. Both are supported by Clang and + // GCC, though they may not be supported by all compiler drivers. We choose `SDKROOT`, + // primarily because that is the same interface that is used when invoking the tool under + // `xcrun -sdk macosx $tool`. + // + // In that sense, if a given compiler driver does not support `SDKROOT`, the blame is fairly + // clearly in the tool in question, since they also don't support being run under `xcrun`. + // + // Additionally, `SDKROOT` is an environment variable and thus optional. It also has lower + // precedence than `-isysroot`, so a custom compiler driver that does not support it and + // instead figures out the SDK on their own can easily do so by using `-isysroot`. + // + // (This in particular affects Clang built with the `DEFAULT_SYSROOT` CMake flag, such as + // the one provided by some versions of Homebrew's `llvm` package. Those will end up + // ignoring the value we set here, and instead use their built-in sysroot). + cmd.cmd().env("SDKROOT", &sdkroot); + } else { + // When invoking the linker directly, we use the `-syslibroot` parameter. `SDKROOT` is not + // read by the linker, so it's really the only option. + // + // This is also what Clang does. + cmd.link_arg("-syslibroot"); + cmd.link_arg(&sdkroot); } - Some(sdk_root) + Some(sdkroot) } fn get_apple_sdk_root(sess: &Session) -> Option { @@ -3246,7 +3281,13 @@ fn get_apple_sdk_root(sess: &Session) -> Option { } "macosx" if sdkroot.contains("iPhoneOS.platform") - || sdkroot.contains("iPhoneSimulator.platform") => {} + || sdkroot.contains("iPhoneSimulator.platform") + || sdkroot.contains("AppleTVOS.platform") + || sdkroot.contains("AppleTVSimulator.platform") + || sdkroot.contains("WatchOS.platform") + || sdkroot.contains("WatchSimulator.platform") + || sdkroot.contains("XROS.platform") + || sdkroot.contains("XRSimulator.platform") => {} "watchos" if sdkroot.contains("WatchSimulator.platform") || sdkroot.contains("MacOSX.platform") => {} diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 050797354b4a..df1e91b12f90 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -1805,11 +1805,18 @@ pub(crate) fn exported_symbols( .collect(); } - if let CrateType::ProcMacro = crate_type { + let mut symbols = if let CrateType::ProcMacro = crate_type { exported_symbols_for_proc_macro_crate(tcx) } else { exported_symbols_for_non_proc_macro(tcx, crate_type) + }; + + if crate_type == CrateType::Dylib || crate_type == CrateType::ProcMacro { + let metadata_symbol_name = exported_symbols::metadata_symbol_name(tcx); + symbols.push((metadata_symbol_name, SymbolExportKind::Data)); } + + symbols } fn exported_symbols_for_non_proc_macro( @@ -1842,12 +1849,8 @@ fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec<(String, Symbol let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); let proc_macro_decls_name = tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id); - let metadata_symbol_name = exported_symbols::metadata_symbol_name(tcx); - vec![ - (proc_macro_decls_name, SymbolExportKind::Data), - (metadata_symbol_name, SymbolExportKind::Data), - ] + vec![(proc_macro_decls_name, SymbolExportKind::Data)] } pub(crate) fn linked_symbols( diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 4b4b39f53533..77096822fdc4 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -8,7 +8,7 @@ use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LOCAL_CRATE, LocalDefId}; use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::exported_symbols::{ - ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, metadata_symbol_name, + ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, }; use rustc_middle::query::LocalCrate; use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, Instance, SymbolName, Ty, TyCtxt}; @@ -289,23 +289,6 @@ fn exported_non_generic_symbols_provider_local<'tcx>( })); } - if tcx.crate_types().contains(&CrateType::Dylib) - || tcx.crate_types().contains(&CrateType::ProcMacro) - { - let symbol_name = metadata_symbol_name(tcx); - let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &symbol_name)); - - symbols.push(( - exported_symbol, - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Data, - used: true, - rustc_std_internal_symbol: false, - }, - )); - } - // Sort so we get a stable incr. comp. hash. symbols.sort_by_cached_key(|s| s.0.symbol_name_for_local_instance(tcx)); @@ -323,7 +306,8 @@ fn exported_generic_symbols_provider_local<'tcx>( let mut symbols: Vec<_> = vec![]; if tcx.local_crate_exports_generics() { - use rustc_middle::mir::mono::{Linkage, MonoItem, Visibility}; + use rustc_hir::attrs::Linkage; + use rustc_middle::mir::mono::{MonoItem, Visibility}; use rustc_middle::ty::InstanceKind; // Normally, we require that shared monomorphizations are not hidden, @@ -586,7 +570,7 @@ fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel // core/std/allocators/etc. For example symbols used to hook up allocation // are not considered for export let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); - let is_extern = codegen_fn_attrs.contains_extern_indicator(); + let is_extern = codegen_fn_attrs.contains_extern_indicator(tcx, sym_def_id); let std_internal = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index aa29afb7f5b1..26d089a11710 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -7,7 +7,6 @@ use std::{fs, io, mem, str, thread}; use rustc_abi::Size; use rustc_ast::attr; -use rustc_ast::expand::autodiff_attrs::AutoDiffItem; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::jobserver::{self, Acquired}; use rustc_data_structures::memmap::Mmap; @@ -38,7 +37,7 @@ use tracing::debug; use super::link::{self, ensure_removed}; use super::lto::{self, SerializedModule}; use crate::back::lto::check_lto_allowed; -use crate::errors::{AutodiffWithoutLto, ErrorCreatingRemarkDir}; +use crate::errors::ErrorCreatingRemarkDir; use crate::traits::*; use crate::{ CachedModuleCodegen, CodegenResults, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind, @@ -76,12 +75,9 @@ pub struct ModuleConfig { /// Names of additional optimization passes to run. pub passes: Vec, /// Some(level) to optimize at a certain level, or None to run - /// absolutely no optimizations (used for the metadata module). + /// absolutely no optimizations (used for the allocator module). pub opt_level: Option, - /// Some(level) to optimize binary size, or None to not affect program size. - pub opt_size: Option, - pub pgo_gen: SwitchWithOptPath, pub pgo_use: Option, pub pgo_sample_use: Option, @@ -102,7 +98,6 @@ pub struct ModuleConfig { pub emit_obj: EmitObj, pub emit_thin_lto: bool, pub emit_thin_lto_summary: bool, - pub bc_cmdline: String, // Miscellaneous flags. These are mostly copied from command-line // options. @@ -110,7 +105,6 @@ pub struct ModuleConfig { pub lint_llvm_ir: bool, pub no_prepopulate_passes: bool, pub no_builtins: bool, - pub time_module: bool, pub vectorize_loop: bool, pub vectorize_slp: bool, pub merge_functions: bool, @@ -171,7 +165,6 @@ impl ModuleConfig { passes: if_regular!(sess.opts.cg.passes.clone(), vec![]), opt_level: opt_level_and_size, - opt_size: opt_level_and_size, pgo_gen: if_regular!( sess.opts.cg.profile_generate.clone(), @@ -221,17 +214,12 @@ impl ModuleConfig { sess.opts.output_types.contains_key(&OutputType::ThinLinkBitcode), false ), - bc_cmdline: sess.target.bitcode_llvm_cmdline.to_string(), verify_llvm_ir: sess.verify_llvm_ir(), lint_llvm_ir: sess.opts.unstable_opts.lint_llvm_ir, no_prepopulate_passes: sess.opts.cg.no_prepopulate_passes, no_builtins: no_builtins || sess.target.no_builtins, - // Exclude metadata and allocator modules from time_passes output, - // since they throw off the "LLVM passes" measurement. - time_module: if_regular!(true, false), - // Copy what clang does by turning on loop vectorization at O2 and // slp vectorization at O3. vectorize_loop: !sess.opts.cg.no_vectorize_loops @@ -454,7 +442,6 @@ pub(crate) fn start_async_codegen( backend: B, tcx: TyCtxt<'_>, target_cpu: String, - autodiff_items: &[AutoDiffItem], ) -> OngoingCodegen { let (coordinator_send, coordinator_receive) = channel(); @@ -473,7 +460,6 @@ pub(crate) fn start_async_codegen( backend.clone(), tcx, &crate_info, - autodiff_items, shared_emitter, codegen_worker_send, coordinator_receive, @@ -728,7 +714,6 @@ pub(crate) enum WorkItem { each_linked_rlib_for_lto: Vec, needs_fat_lto: Vec>, import_only_modules: Vec<(SerializedModule, WorkProduct)>, - autodiff: Vec, }, /// Performs thin-LTO on the given module. ThinLto(lto::ThinModule), @@ -1001,7 +986,6 @@ fn execute_fat_lto_work_item( each_linked_rlib_for_lto: &[PathBuf], mut needs_fat_lto: Vec>, import_only_modules: Vec<(SerializedModule, WorkProduct)>, - autodiff: Vec, module_config: &ModuleConfig, ) -> Result, FatalError> { for (module, wp) in import_only_modules { @@ -1013,7 +997,6 @@ fn execute_fat_lto_work_item( exported_symbols_for_lto, each_linked_rlib_for_lto, needs_fat_lto, - autodiff, )?; let module = B::codegen(cgcx, module, module_config)?; Ok(WorkItemResult::Finished(module)) @@ -1105,7 +1088,6 @@ fn start_executing_work( backend: B, tcx: TyCtxt<'_>, crate_info: &CrateInfo, - autodiff_items: &[AutoDiffItem], shared_emitter: SharedEmitter, codegen_worker_send: Sender, coordinator_receive: Receiver>, @@ -1115,7 +1097,6 @@ fn start_executing_work( ) -> thread::JoinHandle> { let coordinator_send = tx_to_llvm_workers; let sess = tcx.sess; - let autodiff_items = autodiff_items.to_vec(); let mut each_linked_rlib_for_lto = Vec::new(); let mut each_linked_rlib_file_for_lto = Vec::new(); @@ -1448,7 +1429,6 @@ fn start_executing_work( each_linked_rlib_for_lto: each_linked_rlib_file_for_lto, needs_fat_lto, import_only_modules, - autodiff: autodiff_items.clone(), }, 0, )); @@ -1456,11 +1436,6 @@ fn start_executing_work( helper.request_token(); } } else { - if !autodiff_items.is_empty() { - let dcx = cgcx.create_dcx(); - dcx.handle().emit_fatal(AutodiffWithoutLto {}); - } - for (work, cost) in generate_thin_lto_work( &cgcx, &exported_symbols_for_lto, @@ -1740,7 +1715,7 @@ fn spawn_work<'a, B: ExtraBackendMethods>( llvm_start_time: &mut Option>, work: WorkItem, ) { - if cgcx.config(work.module_kind()).time_module && llvm_start_time.is_none() { + if llvm_start_time.is_none() { *llvm_start_time = Some(cgcx.prof.verbose_generic_activity("LLVM_passes")); } @@ -1795,7 +1770,6 @@ fn spawn_work<'a, B: ExtraBackendMethods>( each_linked_rlib_for_lto, needs_fat_lto, import_only_modules, - autodiff, } => { let _timer = cgcx .prof @@ -1806,7 +1780,6 @@ fn spawn_work<'a, B: ExtraBackendMethods>( &each_linked_rlib_for_lto, needs_fat_lto, import_only_modules, - autodiff, module_config, ) } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index b4556ced0b3f..8abaf201abae 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -647,7 +647,7 @@ pub fn codegen_crate( ) -> OngoingCodegen { // Skip crate items and just output metadata in -Z no-codegen mode. if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() { - let ongoing_codegen = start_async_codegen(backend, tcx, target_cpu, &[]); + let ongoing_codegen = start_async_codegen(backend, tcx, target_cpu); ongoing_codegen.codegen_finished(tcx); @@ -665,8 +665,7 @@ pub fn codegen_crate( // Run the monomorphization collector and partition the collected items into // codegen units. - let MonoItemPartitions { codegen_units, autodiff_items, .. } = - tcx.collect_and_partition_mono_items(()); + let MonoItemPartitions { codegen_units, .. } = tcx.collect_and_partition_mono_items(()); // Force all codegen_unit queries so they are already either red or green // when compile_codegen_unit accesses them. We are not able to re-execute @@ -679,34 +678,7 @@ pub fn codegen_crate( } } - let ongoing_codegen = start_async_codegen(backend.clone(), tcx, target_cpu, autodiff_items); - - // Codegen an allocator shim, if necessary. - if let Some(kind) = allocator_kind_for_codegen(tcx) { - let llmod_id = - cgu_name_builder.build_cgu_name(LOCAL_CRATE, &["crate"], Some("allocator")).to_string(); - let module_llvm = tcx.sess.time("write_allocator_module", || { - backend.codegen_allocator( - tcx, - &llmod_id, - kind, - // If allocator_kind is Some then alloc_error_handler_kind must - // also be Some. - tcx.alloc_error_handler_kind(()).unwrap(), - ) - }); - - ongoing_codegen.wait_for_signal_to_codegen_item(); - ongoing_codegen.check_for_errors(tcx.sess); - - // These modules are generally cheap and won't throw off scheduling. - let cost = 0; - submit_codegened_module_to_llvm( - &ongoing_codegen.coordinator, - ModuleCodegen::new_allocator(llmod_id, module_llvm), - cost, - ); - } + let ongoing_codegen = start_async_codegen(backend.clone(), tcx, target_cpu); // For better throughput during parallel processing by LLVM, we used to sort // CGUs largest to smallest. This would lead to better thread utilization @@ -823,6 +795,35 @@ pub fn codegen_crate( } } + // Codegen an allocator shim, if necessary. + // Do this last to ensure the LLVM_passes timer doesn't start while no CGUs have been codegened + // yet for the backend to optimize. + if let Some(kind) = allocator_kind_for_codegen(tcx) { + let llmod_id = + cgu_name_builder.build_cgu_name(LOCAL_CRATE, &["crate"], Some("allocator")).to_string(); + let module_llvm = tcx.sess.time("write_allocator_module", || { + backend.codegen_allocator( + tcx, + &llmod_id, + kind, + // If allocator_kind is Some then alloc_error_handler_kind must + // also be Some. + tcx.alloc_error_handler_kind(()).unwrap(), + ) + }); + + ongoing_codegen.wait_for_signal_to_codegen_item(); + ongoing_codegen.check_for_errors(tcx.sess); + + // These modules are generally cheap and won't throw off scheduling. + let cost = 0; + submit_codegened_module_to_llvm( + &ongoing_codegen.coordinator, + ModuleCodegen::new_allocator(llmod_id, module_llvm), + cost, + ); + } + ongoing_codegen.codegen_finished(tcx); // Since the main thread is sometimes blocked during codegen, we keep track @@ -857,7 +858,7 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( instance: Instance<'tcx>, ) -> bool { fn is_llvm_intrinsic(tcx: TyCtxt<'_>, def_id: DefId) -> bool { - if let Some(name) = tcx.codegen_fn_attrs(def_id).link_name { + if let Some(name) = tcx.codegen_fn_attrs(def_id).symbol_name { name.as_str().starts_with("llvm.") } else { false diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 7f54a47327af..c8690251bd09 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -6,12 +6,10 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr}; use rustc_hir::attrs::{AttributeKind, InlineAttr, InstructionSetAttr, UsedBy}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; -use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS; use rustc_hir::{self as hir, Attribute, LangItem, find_attr, lang_items}; use rustc_middle::middle::codegen_fn_attrs::{ CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry, }; -use rustc_middle::mir::mono::Linkage; use rustc_middle::query::Providers; use rustc_middle::span_bug; use rustc_middle::ty::{self as ty, TyCtxt}; @@ -26,31 +24,6 @@ use crate::target_features::{ check_target_feature_trait_unsafe, check_tied_features, from_target_feature_attr, }; -fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage { - use rustc_middle::mir::mono::Linkage::*; - - // Use the names from src/llvm/docs/LangRef.rst here. Most types are only - // applicable to variable declarations and may not really make sense for - // Rust code in the first place but allow them anyway and trust that the - // user knows what they're doing. Who knows, unanticipated use cases may pop - // up in the future. - // - // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported - // and don't have to be, LLVM treats them as no-ops. - match name { - "available_externally" => AvailableExternally, - "common" => Common, - "extern_weak" => ExternalWeak, - "external" => External, - "internal" => Internal, - "linkonce" => LinkOnceAny, - "linkonce_odr" => LinkOnceODR, - "weak" => WeakAny, - "weak_odr" => WeakODR, - _ => tcx.dcx().span_fatal(tcx.def_span(def_id), "invalid linkage specified"), - } -} - /// In some cases, attributes are only valid on functions, but it's the `check_attr` /// pass that checks that they aren't used anywhere else, rather than this module. /// In these cases, we bail from performing further checks that are only meaningful for @@ -103,39 +76,6 @@ fn parse_instruction_set_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option, did: LocalDefId, attr: &Attribute) -> Option { - let val = attr.value_str()?; - let linkage = linkage_by_name(tcx, did, val.as_str()); - Some(linkage) -} - -// FIXME(jdonszelmann): remove when no_sanitize becomes a parsed attr -fn parse_no_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option { - let list = attr.meta_item_list()?; - let mut sanitizer_set = SanitizerSet::empty(); - - for item in list.iter() { - match item.name() { - Some(sym::address) => { - sanitizer_set |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS - } - Some(sym::cfi) => sanitizer_set |= SanitizerSet::CFI, - Some(sym::kcfi) => sanitizer_set |= SanitizerSet::KCFI, - Some(sym::memory) => sanitizer_set |= SanitizerSet::MEMORY, - Some(sym::memtag) => sanitizer_set |= SanitizerSet::MEMTAG, - Some(sym::shadow_call_stack) => sanitizer_set |= SanitizerSet::SHADOWCALLSTACK, - Some(sym::thread) => sanitizer_set |= SanitizerSet::THREAD, - Some(sym::hwaddress) => sanitizer_set |= SanitizerSet::HWADDRESS, - _ => { - tcx.dcx().emit_err(errors::InvalidNoSanitize { span: item.span() }); - } - } - } - - Some(sanitizer_set) -} - // FIXME(jdonszelmann): remove when patchable_function_entry becomes a parsed attr fn parse_patchable_function_entry( tcx: TyCtxt<'_>, @@ -194,7 +134,7 @@ fn parse_patchable_function_entry( #[derive(Default)] struct InterestingAttributeDiagnosticSpans { link_ordinal: Option, - no_sanitize: Option, + sanitize: Option, inline: Option, no_mangle: Option, } @@ -210,20 +150,12 @@ fn process_builtin_attrs( let mut interesting_spans = InterestingAttributeDiagnosticSpans::default(); let rust_target_features = tcx.rust_target_features(LOCAL_CRATE); - // If our rustc version supports autodiff/enzyme, then we call our handler - // to check for any `#[rustc_autodiff(...)]` attributes. - // FIXME(jdonszelmann): merge with loop below - if cfg!(llvm_enzyme) { - let ad = autodiff_attrs(tcx, did.into()); - codegen_fn_attrs.autodiff_item = ad; - } - for attr in attrs.iter() { if let hir::Attribute::Parsed(p) = attr { match p { AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD, AttributeKind::ExportName { name, .. } => { - codegen_fn_attrs.export_name = Some(*name) + codegen_fn_attrs.symbol_name = Some(*name) } AttributeKind::Inline(inline, span) => { codegen_fn_attrs.inline = *inline; @@ -231,7 +163,13 @@ fn process_builtin_attrs( } AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED, AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align), - AttributeKind::LinkName { name, .. } => codegen_fn_attrs.link_name = Some(*name), + AttributeKind::LinkName { name, .. } => { + // FIXME Remove check for foreign functions once #[link_name] on non-foreign + // functions is a hard error + if tcx.is_foreign_item(did) { + codegen_fn_attrs.symbol_name = Some(*name); + } + } AttributeKind::LinkOrdinal { ordinal, span } => { codegen_fn_attrs.link_ordinal = Some(*ordinal); interesting_spans.link_ordinal = Some(*span); @@ -332,6 +270,28 @@ fn process_builtin_attrs( AttributeKind::StdInternalSymbol(_) => { codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL } + AttributeKind::Linkage(linkage, _) => { + let linkage = Some(*linkage); + + if tcx.is_foreign_item(did) { + codegen_fn_attrs.import_linkage = linkage; + + if tcx.is_mutable_static(did.into()) { + let mut diag = tcx.dcx().struct_span_err( + attr.span(), + "extern mutable statics are not allowed with `#[linkage]`", + ); + diag.note( + "marking the extern static mutable would allow changing which \ + symbol the static references rather than make the target of the \ + symbol mutable", + ); + diag.emit(); + } + } else { + codegen_fn_attrs.linkage = linkage; + } + } _ => {} } } @@ -349,33 +309,7 @@ fn process_builtin_attrs( codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED } sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL, - sym::linkage => { - let linkage = parse_linkage_attr(tcx, did, attr); - - if tcx.is_foreign_item(did) { - codegen_fn_attrs.import_linkage = linkage; - - if tcx.is_mutable_static(did.into()) { - let mut diag = tcx.dcx().struct_span_err( - attr.span(), - "extern mutable statics are not allowed with `#[linkage]`", - ); - diag.note( - "marking the extern static mutable would allow changing which \ - symbol the static references rather than make the target of the \ - symbol mutable", - ); - diag.emit(); - } - } else { - codegen_fn_attrs.linkage = linkage; - } - } - sym::no_sanitize => { - interesting_spans.no_sanitize = Some(attr.span()); - codegen_fn_attrs.no_sanitize |= - parse_no_sanitize_attr(tcx, attr).unwrap_or_default(); - } + sym::sanitize => interesting_spans.sanitize = Some(attr.span()), sym::instruction_set => { codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr) } @@ -399,6 +333,8 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.sess.opts.unstable_opts.min_function_alignment); + // Compute the disabled sanitizers. + codegen_fn_attrs.no_sanitize |= tcx.disabled_sanitizers_for(did); // On trait methods, inherit the `#[align]` of the trait's method prototype. codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did)); @@ -443,6 +379,27 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code if tcx.should_inherit_track_caller(did) { codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER; } + + // Foreign items by default use no mangling for their symbol name. + if tcx.is_foreign_item(did) { + // There's a few exceptions to this rule though: + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) { + // * `#[rustc_std_internal_symbol]` mangles the symbol name in a special way + // both for exports and imports through foreign items. This is handled further, + // during symbol mangling logic. + } else if codegen_fn_attrs.symbol_name.is_some() { + // * This can be overridden with the `#[link_name]` attribute + } else { + // NOTE: there's one more exception that we cannot apply here. On wasm, + // some items cannot be `no_mangle`. + // However, we don't have enough information here to determine that. + // As such, no_mangle foreign items on wasm that have the same defid as some + // import will *still* be mangled despite this. + // + // if none of the exceptions apply; apply no_mangle + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; + } + } } fn check_result( @@ -475,17 +432,17 @@ fn check_result( if !codegen_fn_attrs.no_sanitize.is_empty() && codegen_fn_attrs.inline.always() && let (Some(no_sanitize_span), Some(inline_span)) = - (interesting_spans.no_sanitize, interesting_spans.inline) + (interesting_spans.sanitize, interesting_spans.inline) { let hir_id = tcx.local_def_id_to_hir_id(did); tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, no_sanitize_span, |lint| { - lint.primary_message("`no_sanitize` will have no effect after inlining"); + lint.primary_message("setting `sanitize` off will have no effect after inlining"); lint.span_note(inline_span, "inlining requested here"); }) } // error when specifying link_name together with link_ordinal - if let Some(_) = codegen_fn_attrs.link_name + if let Some(_) = codegen_fn_attrs.symbol_name && let Some(_) = codegen_fn_attrs.link_ordinal { let msg = "cannot use `#[link_name]` with `#[link_ordinal]`"; @@ -532,14 +489,11 @@ fn handle_lang_items( // strippable by the linker. // // Additionally weak lang items have predetermined symbol names. - if let Some(lang_item) = lang_item { - if WEAK_LANG_ITEMS.contains(&lang_item) { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL; - } - if let Some(link_name) = lang_item.link_name() { - codegen_fn_attrs.export_name = Some(link_name); - codegen_fn_attrs.link_name = Some(link_name); - } + if let Some(lang_item) = lang_item + && let Some(link_name) = lang_item.link_name() + { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL; + codegen_fn_attrs.symbol_name = Some(link_name); } // error when using no_mangle on a lang item item @@ -605,6 +559,93 @@ fn opt_trait_item(tcx: TyCtxt<'_>, def_id: DefId) -> Option { } } +/// For an attr that has the `sanitize` attribute, read the list of +/// disabled sanitizers. `current_attr` holds the information about +/// previously parsed attributes. +fn parse_sanitize_attr( + tcx: TyCtxt<'_>, + attr: &Attribute, + current_attr: SanitizerSet, +) -> SanitizerSet { + let mut result = current_attr; + if let Some(list) = attr.meta_item_list() { + for item in list.iter() { + let MetaItemInner::MetaItem(set) = item else { + tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() }); + break; + }; + let segments = set.path.segments.iter().map(|x| x.ident.name).collect::>(); + match segments.as_slice() { + // Similar to clang, sanitize(address = ..) and + // sanitize(kernel_address = ..) control both ASan and KASan + // Source: https://reviews.llvm.org/D44981. + [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS + } + [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::ADDRESS; + result &= !SanitizerSet::KERNELADDRESS; + } + [sym::cfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::CFI, + [sym::cfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::CFI, + [sym::kcfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::KCFI, + [sym::kcfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::KCFI, + [sym::memory] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::MEMORY + } + [sym::memory] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::MEMORY + } + [sym::memtag] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::MEMTAG + } + [sym::memtag] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::MEMTAG + } + [sym::shadow_call_stack] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::SHADOWCALLSTACK + } + [sym::shadow_call_stack] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::SHADOWCALLSTACK + } + [sym::thread] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::THREAD + } + [sym::thread] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::THREAD + } + [sym::hwaddress] if set.value_str() == Some(sym::off) => { + result |= SanitizerSet::HWADDRESS + } + [sym::hwaddress] if set.value_str() == Some(sym::on) => { + result &= !SanitizerSet::HWADDRESS + } + _ => { + tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() }); + } + } + } + } + result +} + +fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet { + // Backtrack to the crate root. + let disabled = match tcx.opt_local_parent(did) { + // Check the parent (recursively). + Some(parent) => tcx.disabled_sanitizers_for(parent), + // We reached the crate root without seeing an attribute, so + // there is no sanitizers to exclude. + None => SanitizerSet::empty(), + }; + + // Check for a sanitize annotation directly on this def. + if let Some(attr) = tcx.get_attr(did, sym::sanitize) { + return parse_sanitize_attr(tcx, attr, disabled); + } + disabled +} + /// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller /// applied to the method prototype. fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool { @@ -624,7 +665,7 @@ fn inherited_align<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option { /// placeholder functions. We wrote the rustc_autodiff attributes ourself, so this should never /// panic, unless we introduced a bug when parsing the autodiff macro. //FIXME(jdonszelmann): put in the main loop. No need to have two..... :/ Let's do that when we make autodiff parsed. -fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option { +pub fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option { let attrs = tcx.get_attrs(id, sym::rustc_autodiff); let attrs = attrs.filter(|attr| attr.has_name(sym::rustc_autodiff)).collect::>(); @@ -729,6 +770,11 @@ fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option { } pub(crate) fn provide(providers: &mut Providers) { - *providers = - Providers { codegen_fn_attrs, should_inherit_track_caller, inherited_align, ..*providers }; + *providers = Providers { + codegen_fn_attrs, + should_inherit_track_caller, + inherited_align, + disabled_sanitizers_for, + ..*providers + }; } diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 3d787d8bdbde..209c78ddeda4 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -550,6 +550,18 @@ impl Diagnostic<'_, G> for LinkingFailed<'_> { #[diag(codegen_ssa_link_exe_unexpected_error)] pub(crate) struct LinkExeUnexpectedError; +pub(crate) struct LinkExeStatusStackBufferOverrun; + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LinkExeStatusStackBufferOverrun { + fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + let mut diag = + Diag::new(dcx, level, fluent::codegen_ssa_link_exe_status_stack_buffer_overrun); + diag.note(fluent::codegen_ssa_abort_note); + diag.note(fluent::codegen_ssa_event_log_note); + diag + } +} + #[derive(Diagnostic)] #[diag(codegen_ssa_repair_vs_build_tools)] pub(crate) struct RepairVSBuildTools; @@ -764,6 +776,14 @@ pub enum InvalidMonomorphization<'tcx> { ty: Ty<'tcx>, }, + #[diag(codegen_ssa_invalid_monomorphization_basic_integer_or_ptr_type, code = E0511)] + BasicIntegerOrPtrType { + #[primary_span] + span: Span, + name: Symbol, + ty: Ty<'tcx>, + }, + #[diag(codegen_ssa_invalid_monomorphization_basic_float_type, code = E0511)] BasicFloatType { #[primary_span] @@ -1101,9 +1121,9 @@ impl IntoDiagArg for ExpectedPointerMutability { } #[derive(Diagnostic)] -#[diag(codegen_ssa_invalid_no_sanitize)] +#[diag(codegen_ssa_invalid_sanitize)] #[note] -pub(crate) struct InvalidNoSanitize { +pub(crate) struct InvalidSanitize { #[primary_span] pub span: Span, } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index e96590441fa4..c3dc3e42b83d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -5,6 +5,7 @@ use rustc_ast as ast; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_data_structures::packed::Pu128; use rustc_hir::lang_items::LangItem; +use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER; use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason}; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; @@ -35,7 +36,7 @@ enum MergingSucc { True, } -/// Indicates to the call terminator codegen whether a cal +/// Indicates to the call terminator codegen whether a call /// is a normal call or an explicit tail call. #[derive(Debug, PartialEq)] enum CallKind { @@ -906,7 +907,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { fn_span, ); - let instance = match instance.def { + match instance.def { // We don't need AsyncDropGlueCtorShim here because it is not `noop func`, // it is `func returning noop future` ty::InstanceKind::DropGlue(_, None) => { @@ -995,14 +996,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { intrinsic.name, ); } - instance + (Some(instance), None) } } } - _ => instance, - }; - (Some(instance), None) + _ if kind == CallKind::Tail + && instance.def.requires_caller_location(bx.tcx()) => + { + if let Some(hir_id) = + terminator.source_info.scope.lint_root(&self.mir.source_scopes) + { + let msg = "tail calling a function marked with `#[track_caller]` has no special effect"; + bx.tcx().node_lint(TAIL_CALL_TRACK_CALLER, hir_id, |d| { + _ = d.primary_message(msg).span(fn_span) + }); + } + + let instance = ty::Instance::resolve_for_fn_ptr( + bx.tcx(), + bx.typing_env(), + def_id, + generic_args, + ) + .unwrap(); + + (None, Some(bx.get_fn_addr(instance))) + } + _ => (Some(instance), None), + } } ty::FnPtr(..) => (None, Some(callee.immediate())), _ => bug!("{} is not callable", callee.layout.ty), diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index fc95f62b4a43..3c667b8e8820 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -92,6 +92,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let invalid_monomorphization_int_type = |ty| { bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicIntegerType { span, name, ty }); }; + let invalid_monomorphization_int_or_ptr_type = |ty| { + bx.tcx().dcx().emit_err(InvalidMonomorphization::BasicIntegerOrPtrType { + span, + name, + ty, + }); + }; let parse_atomic_ordering = |ord: ty::Value<'tcx>| { let discr = ord.valtree.unwrap_branch()[0].unwrap_leaf(); @@ -351,7 +358,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { sym::atomic_load => { let ty = fn_args.type_at(0); if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) { - invalid_monomorphization_int_type(ty); + invalid_monomorphization_int_or_ptr_type(ty); return Ok(()); } let ordering = fn_args.const_at(1).to_value(); @@ -367,7 +374,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { sym::atomic_store => { let ty = fn_args.type_at(0); if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) { - invalid_monomorphization_int_type(ty); + invalid_monomorphization_int_or_ptr_type(ty); return Ok(()); } let ordering = fn_args.const_at(1).to_value(); @@ -377,10 +384,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.atomic_store(val, ptr, parse_atomic_ordering(ordering), size); return Ok(()); } + // These are all AtomicRMW ops sym::atomic_cxchg | sym::atomic_cxchgweak => { let ty = fn_args.type_at(0); if !(int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr()) { - invalid_monomorphization_int_type(ty); + invalid_monomorphization_int_or_ptr_type(ty); return Ok(()); } let succ_ordering = fn_args.const_at(1).to_value(); @@ -407,7 +415,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return Ok(()); } - // These are all AtomicRMW ops sym::atomic_max | sym::atomic_min => { let atom_op = if name == sym::atomic_max { AtomicRmwBinOp::AtomicMax @@ -420,7 +427,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let ordering = fn_args.const_at(1).to_value(); let ptr = args[0].immediate(); let val = args[1].immediate(); - bx.atomic_rmw(atom_op, ptr, val, parse_atomic_ordering(ordering)) + bx.atomic_rmw( + atom_op, + ptr, + val, + parse_atomic_ordering(ordering), + /* ret_ptr */ false, + ) } else { invalid_monomorphization_int_type(ty); return Ok(()); @@ -438,21 +451,44 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let ordering = fn_args.const_at(1).to_value(); let ptr = args[0].immediate(); let val = args[1].immediate(); - bx.atomic_rmw(atom_op, ptr, val, parse_atomic_ordering(ordering)) + bx.atomic_rmw( + atom_op, + ptr, + val, + parse_atomic_ordering(ordering), + /* ret_ptr */ false, + ) } else { invalid_monomorphization_int_type(ty); return Ok(()); } } - sym::atomic_xchg - | sym::atomic_xadd + sym::atomic_xchg => { + let ty = fn_args.type_at(0); + let ordering = fn_args.const_at(1).to_value(); + if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr() { + let ptr = args[0].immediate(); + let val = args[1].immediate(); + let atomic_op = AtomicRmwBinOp::AtomicXchg; + bx.atomic_rmw( + atomic_op, + ptr, + val, + parse_atomic_ordering(ordering), + /* ret_ptr */ ty.is_raw_ptr(), + ) + } else { + invalid_monomorphization_int_or_ptr_type(ty); + return Ok(()); + } + } + sym::atomic_xadd | sym::atomic_xsub | sym::atomic_and | sym::atomic_nand | sym::atomic_or | sym::atomic_xor => { let atom_op = match name { - sym::atomic_xchg => AtomicRmwBinOp::AtomicXchg, sym::atomic_xadd => AtomicRmwBinOp::AtomicAdd, sym::atomic_xsub => AtomicRmwBinOp::AtomicSub, sym::atomic_and => AtomicRmwBinOp::AtomicAnd, @@ -462,14 +498,28 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => unreachable!(), }; - let ty = fn_args.type_at(0); - if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_raw_ptr() { - let ordering = fn_args.const_at(1).to_value(); - let ptr = args[0].immediate(); - let val = args[1].immediate(); - bx.atomic_rmw(atom_op, ptr, val, parse_atomic_ordering(ordering)) + // The type of the in-memory data. + let ty_mem = fn_args.type_at(0); + // The type of the 2nd operand, given by-value. + let ty_op = fn_args.type_at(1); + + let ordering = fn_args.const_at(2).to_value(); + // We require either both arguments to have the same integer type, or the first to + // be a pointer and the second to be `usize`. + if (int_type_width_signed(ty_mem, bx.tcx()).is_some() && ty_op == ty_mem) + || (ty_mem.is_raw_ptr() && ty_op == bx.tcx().types.usize) + { + let ptr = args[0].immediate(); // of type "pointer to `ty_mem`" + let val = args[1].immediate(); // of type `ty_op` + bx.atomic_rmw( + atom_op, + ptr, + val, + parse_atomic_ordering(ordering), + /* ret_ptr */ ty_mem.is_raw_ptr(), + ) } else { - invalid_monomorphization_int_type(ty); + invalid_monomorphization_int_or_ptr_type(ty_mem); return Ok(()); } } diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 50d0f9107445..06873313e2ec 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -296,10 +296,6 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // Apply debuginfo to the newly allocated locals. fx.debug_introduce_locals(&mut start_bx, consts_debug_info.unwrap_or_default()); - // If the backend supports coverage, and coverage is enabled for this function, - // do any necessary start-of-function codegen (e.g. locals for MC/DC bitmaps). - start_bx.init_coverage(instance); - // The builders will be created separately for each basic block at `codegen_block`. // So drop the builder of `start_llbb` to avoid having two at the same time. drop(start_bx); diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 2a9b5c9019b9..31784cabf4af 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -1,6 +1,6 @@ use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind}; -use rustc_hir::attrs::InstructionSetAttr; -use rustc_middle::mir::mono::{Linkage, MonoItemData, Visibility}; +use rustc_hir::attrs::{InstructionSetAttr, Linkage}; +use rustc_middle::mir::mono::{MonoItemData, Visibility}; use rustc_middle::mir::{InlineAsmOperand, START_BLOCK}; use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout}; use rustc_middle::ty::{Instance, Ty, TyCtxt, TypeVisitableExt}; diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 5459f95c1860..d851c3329802 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -498,6 +498,35 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { bx.cx().const_uint(cast_to, niche_variants.start().as_u32() as u64); (is_niche, tagged_discr, 0) } else { + // Thanks to parameter attributes and load metadata, LLVM already knows + // the general valid range of the tag. It's possible, though, for there + // to be an impossible value *in the middle*, which those ranges don't + // communicate, so it's worth an `assume` to let the optimizer know. + // Most importantly, this means when optimizing a variant test like + // `SELECT(is_niche, complex, CONST) == CONST` it's ok to simplify that + // to `!is_niche` because the `complex` part can't possibly match. + // + // This was previously asserted on `tagged_discr` below, where the + // impossible value is more obvious, but that caused an intermediate + // value to become multi-use and thus not optimize, so instead this + // assumes on the original input which is always multi-use. See + // + // + // FIXME: If we ever get range assume operand bundles in LLVM (so we + // don't need the `icmp`s in the instruction stream any more), it + // might be worth moving this back to being on the switch argument + // where it's more obviously applicable. + if niche_variants.contains(&untagged_variant) + && bx.cx().sess().opts.optimize != OptLevel::No + { + let impossible = niche_start + .wrapping_add(u128::from(untagged_variant.as_u32())) + .wrapping_sub(u128::from(niche_variants.start().as_u32())); + let impossible = bx.cx().const_uint_big(tag_llty, impossible); + let ne = bx.icmp(IntPredicate::IntNE, tag, impossible); + bx.assume(ne); + } + // With multiple niched variants we'll have to actually compute // the variant index from the stored tag. // @@ -588,20 +617,6 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let untagged_variant_const = bx.cx().const_uint(cast_to, u64::from(untagged_variant.as_u32())); - // Thanks to parameter attributes and load metadata, LLVM already knows - // the general valid range of the tag. It's possible, though, for there - // to be an impossible value *in the middle*, which those ranges don't - // communicate, so it's worth an `assume` to let the optimizer know. - // Most importantly, this means when optimizing a variant test like - // `SELECT(is_niche, complex, CONST) == CONST` it's ok to simplify that - // to `!is_niche` because the `complex` part can't possibly match. - if niche_variants.contains(&untagged_variant) - && bx.cx().sess().opts.optimize != OptLevel::No - { - let ne = bx.icmp(IntPredicate::IntNE, tagged_discr, untagged_variant_const); - bx.assume(ne); - } - let discr = bx.select(is_niche, tagged_discr, untagged_variant_const); // In principle we could insert assumes on the possible range of `discr`, but diff --git a/compiler/rustc_codegen_ssa/src/mono_item.rs b/compiler/rustc_codegen_ssa/src/mono_item.rs index b9040c330fb1..8f03dc1e6b5e 100644 --- a/compiler/rustc_codegen_ssa/src/mono_item.rs +++ b/compiler/rustc_codegen_ssa/src/mono_item.rs @@ -1,5 +1,6 @@ +use rustc_hir::attrs::Linkage; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::mono::{Linkage, MonoItem, MonoItemData, Visibility}; +use rustc_middle::mir::mono::{MonoItem, MonoItemData, Visibility}; use rustc_middle::ty::layout::HasTyCtxt; use tracing::debug; diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index d984156c674c..b5aa50f48512 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -180,6 +180,7 @@ fn parse_rust_feature_flag<'a>( while let Some(new_feature) = new_features.pop() { if features.insert(new_feature) { if let Some(implied_features) = inverse_implied_features.get(&new_feature) { + #[allow(rustc::potential_query_instability)] new_features.extend(implied_features) } } @@ -197,7 +198,10 @@ fn parse_rust_feature_flag<'a>( /// 2nd component of the return value, respectively). /// /// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is -/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. +/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. Note that LLVM +/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely +/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU +/// to target features. /// /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere. pub fn cfg_target_feature( @@ -211,7 +215,15 @@ pub fn cfg_target_feature( .rust_target_features() .iter() .filter(|(feature, _, _)| target_base_has_feature(feature)) - .map(|(feature, _, _)| Symbol::intern(feature)) + .flat_map(|(base_feature, _, _)| { + // Expand the direct base feature into all transitively-implied features. Note that we + // cannot simply use the `implied` field of the tuple since that only contains + // directly-implied features. + // + // Iteration order is irrelevant because we're collecting into an `UnordSet`. + #[allow(rustc::potential_query_instability)] + sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f)) + }) .collect(); // Add enabled and remove disabled features. diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index 4b18146863bf..f417d1a7bf72 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -548,12 +548,15 @@ pub trait BuilderMethods<'a, 'tcx>: failure_order: AtomicOrdering, weak: bool, ) -> (Self::Value, Self::Value); + /// `ret_ptr` indicates whether the return type (which is also the type `dst` points to) + /// is a pointer or the same type as `src`. fn atomic_rmw( &mut self, op: AtomicRmwBinOp, dst: Self::Value, src: Self::Value, order: AtomicOrdering, + ret_ptr: bool, ) -> Self::Value; fn atomic_fence(&mut self, order: AtomicOrdering, scope: SynchronizationScope); fn set_invariant_load(&mut self, load: Self::Value); diff --git a/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs b/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs index 0b513dac5037..31482a53b6d4 100644 --- a/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs +++ b/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs @@ -2,11 +2,6 @@ use rustc_middle::mir::coverage::CoverageKind; use rustc_middle::ty::Instance; pub trait CoverageInfoBuilderMethods<'tcx> { - /// Performs any start-of-function codegen needed for coverage instrumentation. - /// - /// Can be a no-op in backends that don't support coverage instrumentation. - fn init_coverage(&mut self, _instance: Instance<'tcx>) {} - /// Handle the MIR coverage info in a backend-specific way. /// /// This can potentially be a no-op in backends that don't support diff --git a/compiler/rustc_codegen_ssa/src/traits/declare.rs b/compiler/rustc_codegen_ssa/src/traits/declare.rs index 9f735546558b..8d5f0a5b939a 100644 --- a/compiler/rustc_codegen_ssa/src/traits/declare.rs +++ b/compiler/rustc_codegen_ssa/src/traits/declare.rs @@ -1,5 +1,6 @@ +use rustc_hir::attrs::Linkage; use rustc_hir::def_id::DefId; -use rustc_middle::mir::mono::{Linkage, Visibility}; +use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::Instance; pub trait PreDefineCodegenMethods<'tcx> { diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index f391c198e1a1..c29ad90735b7 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -use rustc_ast::expand::autodiff_attrs::AutoDiffItem; use rustc_errors::{DiagCtxtHandle, FatalError}; use rustc_middle::dep_graph::WorkProduct; @@ -23,7 +22,6 @@ pub trait WriteBackendMethods: Clone + 'static { exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, - diff_fncs: Vec, ) -> Result, FatalError>; /// Performs thin LTO by performing necessary global analysis and returning two /// lists, one of the modules that need optimization and another for modules that diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 2985eafb63af..60518dafbf2b 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -57,7 +57,7 @@ const_eval_const_context = {$kind -> } const_eval_const_heap_ptr_in_final = encountered `const_allocate` pointer in final value that was not made global - .note = use `const_make_global` to make allocated pointers immutable before returning + .note = use `const_make_global` to turn allocated pointers into immutable globals before returning const_eval_const_make_global_ptr_already_made_global = attempting to call `const_make_global` twice on the same allocation {$alloc} @@ -231,6 +231,9 @@ const_eval_mutable_borrow_escaping = const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind} +const_eval_partial_pointer_in_final = encountered partial pointer in final value of {const_eval_intern_kind} + .note = while pointers can be broken apart into individual bytes during const-evaluation, only complete pointers (with all their bytes in the right order) are supported in the final value + const_eval_nested_static_in_thread_local = #[thread_local] does not support implicit nested statics, please create explicit static items and refer to them instead const_eval_non_const_await = @@ -299,10 +302,8 @@ const_eval_panic = evaluation panicked: {$msg} const_eval_panic_non_str = argument to `panic!()` in a const context must have type `&str` -const_eval_partial_pointer_copy = - unable to copy parts of a pointer from memory at {$ptr} -const_eval_partial_pointer_overwrite = - unable to overwrite parts of a pointer in memory at {$ptr} +const_eval_partial_pointer_read = + unable to read parts of a pointer from memory at {$ptr} const_eval_pointer_arithmetic_overflow = overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize` diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 8e2c138282af..ca707b50d50d 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -827,7 +827,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // At this point, we are calling a function, `callee`, whose `DefId` is known... - // `begin_panic` and `#[rustc_const_panic_str]` functions accept generic + // `begin_panic` and `panic_display` functions accept generic // types other than str. Check to enforce that only str can be used in // const-eval. @@ -841,8 +841,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { return; } - // const-eval of `#[rustc_const_panic_str]` functions assumes the argument is `&&str` - if tcx.has_attr(callee, sym::rustc_const_panic_str) { + // const-eval of `panic_display` assumes the argument is `&&str` + if tcx.is_lang_item(callee, LangItem::PanicDisplay) { match args[0].node.ty(&self.ccx.body.local_decls, tcx).kind() { ty::Ref(_, ty, _) if matches!(ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) => {} diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 982e640fa92a..79e32dcf105c 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -142,7 +142,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { |err, self_ty, trait_id| { // FIXME(const_trait_impl): Do we need any of this on the non-const codepath? - let trait_ref = TraitRef::from_method(tcx, trait_id, self.args); + let trait_ref = TraitRef::from_assoc(tcx, trait_id, self.args); match self_ty.kind() { Param(param_ty) => { diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 5835660e1c38..4da2663319c3 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -117,6 +117,13 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>( ecx.tcx.dcx().emit_err(errors::ConstHeapPtrInFinal { span: ecx.tcx.span }), ))); } + Err(InternError::PartialPointer) => { + throw_inval!(AlreadyReported(ReportedErrorInfo::non_const_eval_error( + ecx.tcx + .dcx() + .emit_err(errors::PartialPtrInFinal { span: ecx.tcx.span, kind: intern_kind }), + ))); + } } interp_ok(R::make_result(ret, ecx)) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index a18ae79f318d..da954cf4ed74 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -237,7 +237,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> { ) -> InterpResult<'tcx, Option>> { let def_id = instance.def_id(); - if self.tcx.has_attr(def_id, sym::rustc_const_panic_str) + if self.tcx.is_lang_item(def_id, LangItem::PanicDisplay) || self.tcx.is_lang_item(def_id, LangItem::BeginPanic) { let args = self.copy_fn_args(args); diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index a4148cb145ff..2d412ee5ec26 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -51,6 +51,15 @@ pub(crate) struct ConstHeapPtrInFinal { pub span: Span, } +#[derive(Diagnostic)] +#[diag(const_eval_partial_pointer_in_final)] +#[note] +pub(crate) struct PartialPtrInFinal { + #[primary_span] + pub span: Span, + pub kind: InternKind, +} + #[derive(Diagnostic)] #[diag(const_eval_unstable_in_stable_exposed)] pub(crate) struct UnstableInStableExposed { @@ -836,8 +845,7 @@ impl ReportErrorExt for UnsupportedOpInfo { UnsupportedOpInfo::Unsupported(s) => s.clone().into(), UnsupportedOpInfo::ExternTypeField => const_eval_extern_type_field, UnsupportedOpInfo::UnsizedLocal => const_eval_unsized_local, - UnsupportedOpInfo::OverwritePartialPointer(_) => const_eval_partial_pointer_overwrite, - UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_copy, + UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_read, UnsupportedOpInfo::ReadPointerAsInt(_) => const_eval_read_pointer_as_int, UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static, UnsupportedOpInfo::ExternStatic(_) => const_eval_extern_static, @@ -848,7 +856,7 @@ impl ReportErrorExt for UnsupportedOpInfo { use UnsupportedOpInfo::*; use crate::fluent_generated::*; - if let ReadPointerAsInt(_) | OverwritePartialPointer(_) | ReadPartialPointer(_) = self { + if let ReadPointerAsInt(_) | ReadPartialPointer(_) = self { diag.help(const_eval_ptr_as_bytes_1); diag.help(const_eval_ptr_as_bytes_2); } @@ -860,7 +868,7 @@ impl ReportErrorExt for UnsupportedOpInfo { | UnsupportedOpInfo::ExternTypeField | Unsupported(_) | ReadPointerAsInt(_) => {} - OverwritePartialPointer(ptr) | ReadPartialPointer(ptr) => { + ReadPartialPointer(ptr) => { diag.arg("ptr", ptr); } ThreadLocalStatic(did) | ExternStatic(did) => rustc_middle::ty::tls::with(|tcx| { diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index b8a653698258..4cb88d44e1b1 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -27,8 +27,9 @@ use crate::{enter_trace_span, fluent_generated as fluent}; pub enum FnArg<'tcx, Prov: Provenance = CtfeProvenance> { /// Pass a copy of the given operand. Copy(OpTy<'tcx, Prov>), - /// Allow for the argument to be passed in-place: destroy the value originally stored at that place and - /// make the place inaccessible for the duration of the function call. + /// Allow for the argument to be passed in-place: destroy the value originally stored at that + /// place and make the place inaccessible for the duration of the function call. This *must* be + /// an in-memory place so that we can do the proper alias checks. InPlace(MPlaceTy<'tcx, Prov>), } @@ -346,7 +347,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { destination: &PlaceTy<'tcx, M::Provenance>, mut cont: ReturnContinuation, ) -> InterpResult<'tcx> { - let _span = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); + let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); // Compute callee information. // FIXME: for variadic support, do we have to somehow determine callee's extra_args? @@ -379,6 +380,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } + // *Before* pushing the new frame, determine whether the return destination is in memory. + // Need to use `place_to_op` to be *sure* we get the mplace if there is one. + let destination_mplace = self.place_to_op(destination)?.as_mplace_or_imm().left(); + + // Push the "raw" frame -- this leaves locals uninitialized. self.push_stack_frame_raw(instance, body, destination, cont)?; // If an error is raised here, pop the frame again to get an accurate backtrace. @@ -496,7 +502,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Protect return place for in-place return value passing. // We only need to protect anything if this is actually an in-memory place. - if let Left(mplace) = destination.as_mplace_or_local() { + if let Some(mplace) = destination_mplace { M::protect_in_place_function_argument(self, &mplace)?; } @@ -527,7 +533,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { target: Option, unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { - let _span = + let _trace = enter_trace_span!(M, step::init_fn_call, tracing_separate_thread = Empty, ?fn_val) .or_if_tracing_disabled(|| trace!("init_fn_call: {:#?}", fn_val)); @@ -731,18 +737,21 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) { let tcx = *self.tcx; - let trait_def_id = tcx.trait_of_assoc(def_id).unwrap(); - let virtual_trait_ref = ty::TraitRef::from_method(tcx, trait_def_id, virtual_instance.args); + let trait_def_id = tcx.parent(def_id); + let virtual_trait_ref = ty::TraitRef::from_assoc(tcx, trait_def_id, virtual_instance.args); let existential_trait_ref = ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref); let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty); - let concrete_method = Instance::expect_resolve_for_vtable( - tcx, - self.typing_env, - def_id, - virtual_instance.args.rebase_onto(tcx, trait_def_id, concrete_trait_ref.args), - self.cur_span(), - ); + let concrete_method = { + let _trace = enter_trace_span!(M, resolve::expect_resolve_for_vtable, ?def_id); + Instance::expect_resolve_for_vtable( + tcx, + self.typing_env, + def_id, + virtual_instance.args.rebase_onto(tcx, trait_def_id, concrete_trait_ref.args), + self.cur_span(), + ) + }; assert_eq!(concrete_instance, concrete_method); } @@ -825,7 +834,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { place } }; - let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); + let instance = { + let _trace = + enter_trace_span!(M, resolve::resolve_drop_in_place, ty = ?place.layout.ty); + ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty) + }; let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?; let arg = self.mplace_to_ref(&place)?; diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index de4fbc7b4752..e3afeda5b7c9 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -16,8 +16,8 @@ use super::{ FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, err_inval, interp_ok, throw_ub, throw_ub_custom, }; -use crate::fluent_generated as fluent; use crate::interpret::Writeable; +use crate::{enter_trace_span, fluent_generated as fluent}; impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn cast( @@ -81,13 +81,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // The src operand does not matter, just its type match *src.layout.ty.kind() { ty::FnDef(def_id, args) => { - let instance = ty::Instance::resolve_for_fn_ptr( - *self.tcx, - self.typing_env, - def_id, - args, - ) - .ok_or_else(|| err_inval!(TooGeneric))?; + let instance = { + let _trace = enter_trace_span!(M, resolve::resolve_for_fn_ptr, ?def_id); + ty::Instance::resolve_for_fn_ptr( + *self.tcx, + self.typing_env, + def_id, + args, + ) + .ok_or_else(|| err_inval!(TooGeneric))? + }; let fn_ptr = self.fn_ptr(FnVal::Instance(instance)); self.write_pointer(fn_ptr, dest)?; @@ -114,12 +117,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // The src operand does not matter, just its type match *src.layout.ty.kind() { ty::Closure(def_id, args) => { - let instance = ty::Instance::resolve_closure( - *self.tcx, - def_id, - args, - ty::ClosureKind::FnOnce, - ); + let instance = { + let _trace = enter_trace_span!(M, resolve::resolve_closure, ?def_id); + ty::Instance::resolve_closure( + *self.tcx, + def_id, + args, + ty::ClosureKind::FnOnce, + ) + }; let fn_ptr = self.fn_ptr(FnVal::Instance(instance)); self.write_pointer(fn_ptr, dest)?; } diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index c4b705d7124e..9681d89ce35f 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -113,7 +113,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// See [LayoutOf::layout_of] for the original documentation. #[inline(always)] pub fn layout_of(&self, ty: Ty<'tcx>) -> >::LayoutOfResult { - let _span = enter_trace_span!(M, layouting::layout_of, ty = ?ty.kind()); + let _trace = enter_trace_span!(M, layouting::layout_of, ty = ?ty.kind()); LayoutOf::layout_of(self, ty) } @@ -126,7 +126,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sig: ty::PolyFnSig<'tcx>, extra_args: &'tcx ty::List>, ) -> >::FnAbiOfResult { - let _span = enter_trace_span!(M, layouting::fn_abi_of_fn_ptr, ?sig, ?extra_args); + let _trace = enter_trace_span!(M, layouting::fn_abi_of_fn_ptr, ?sig, ?extra_args); FnAbiOf::fn_abi_of_fn_ptr(self, sig, extra_args) } @@ -139,7 +139,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { instance: ty::Instance<'tcx>, extra_args: &'tcx ty::List>, ) -> >::FnAbiOfResult { - let _span = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args); + let _trace = enter_trace_span!(M, layouting::fn_abi_of_instance, ?instance, ?extra_args); FnAbiOf::fn_abi_of_instance(self, instance, extra_args) } } @@ -322,11 +322,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { frame: &Frame<'tcx, M::Provenance, M::FrameExtra>, value: T, ) -> Result { - let _span = enter_trace_span!( + let _trace = enter_trace_span!( M, "instantiate_from_frame_and_normalize_erasing_regions", - "{}", - frame.instance + %frame.instance ); frame .instance @@ -344,6 +343,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { def: DefId, args: GenericArgsRef<'tcx>, ) -> InterpResult<'tcx, ty::Instance<'tcx>> { + let _trace = enter_trace_span!(M, resolve::try_resolve, def = ?def); trace!("resolve: {:?}, {:#?}", def, args); trace!("typing_env: {:#?}", self.typing_env); trace!("args: {:#?}", args); @@ -582,6 +582,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { span: Span, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + let _trace = enter_trace_span!(M, const_eval::eval_mir_constant, ?val); let const_val = val.eval(*self.tcx, self.typing_env, span).map_err(|err| { if M::ALL_CONSTS_ARE_PRECHECKED { match err { diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index bb59b9f54186..5d510a983526 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -19,9 +19,12 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_hir as hir; use rustc_hir::definitions::{DefPathData, DisambiguatorState}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; -use rustc_middle::mir::interpret::{ConstAllocation, CtfeProvenance, InterpResult}; +use rustc_middle::mir::interpret::{ + AllocBytes, ConstAllocation, CtfeProvenance, InterpResult, Provenance, +}; use rustc_middle::query::TyCtxtAt; use rustc_middle::span_bug; +use rustc_middle::ty::TyCtxt; use rustc_middle::ty::layout::TyAndLayout; use rustc_span::def_id::LocalDefId; use tracing::{instrument, trace}; @@ -52,39 +55,30 @@ impl HasStaticRootDefId for const_eval::CompileTimeMachine<'_> { } } -/// Intern an allocation. Returns `Err` if the allocation does not exist in the local memory. -/// -/// `mutability` can be used to force immutable interning: if it is `Mutability::Not`, the -/// allocation is interned immutably; if it is `Mutability::Mut`, then the allocation *must be* -/// already mutable (as a sanity check). -/// -/// Returns an iterator over all relocations referred to by this allocation. -fn intern_shallow<'tcx, M: CompileTimeMachine<'tcx>>( - ecx: &mut InterpCx<'tcx, M>, - alloc_id: AllocId, +fn prepare_alloc<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( + tcx: TyCtxt<'tcx>, + kind: MemoryKind, + alloc: &mut Allocation, mutability: Mutability, - disambiguator: Option<&mut DisambiguatorState>, -) -> Result + 'tcx, InternError> { - trace!("intern_shallow {:?}", alloc_id); - // remove allocation - // FIXME(#120456) - is `swap_remove` correct? - let Some((kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else { - return Err(InternError::DanglingPointer); - }; - +) -> Result<(), InternError> { match kind { MemoryKind::Machine(const_eval::MemoryKind::Heap { was_made_global }) => { if !was_made_global { // Attempting to intern a `const_allocate`d pointer that was not made global via - // `const_make_global`. We want to error here, but we have to first put the - // allocation back into the `alloc_map` to keep things in a consistent state. - ecx.memory.alloc_map.insert(alloc_id, (kind, alloc)); + // `const_make_global`. + tcx.dcx().delayed_bug("non-global heap allocation in const value"); return Err(InternError::ConstAllocNotGlobal); } } MemoryKind::Stack | MemoryKind::CallerLocation => {} } + if !alloc.provenance_merge_bytes(&tcx) { + // Per-byte provenance is not supported by backends, so we cannot accept it here. + tcx.dcx().delayed_bug("partial pointer in const value"); + return Err(InternError::PartialPointer); + } + // Set allocation mutability as appropriate. This is used by LLVM to put things into // read-only memory, and also by Miri when evaluating other globals that // access this one. @@ -97,6 +91,36 @@ fn intern_shallow<'tcx, M: CompileTimeMachine<'tcx>>( assert_eq!(alloc.mutability, Mutability::Mut); } } + Ok(()) +} + +/// Intern an allocation. Returns `Err` if the allocation does not exist in the local memory. +/// +/// `mutability` can be used to force immutable interning: if it is `Mutability::Not`, the +/// allocation is interned immutably; if it is `Mutability::Mut`, then the allocation *must be* +/// already mutable (as a sanity check). +/// +/// Returns an iterator over all relocations referred to by this allocation. +fn intern_shallow<'tcx, M: CompileTimeMachine<'tcx>>( + ecx: &mut InterpCx<'tcx, M>, + alloc_id: AllocId, + mutability: Mutability, + disambiguator: Option<&mut DisambiguatorState>, +) -> Result + 'tcx, InternError> { + trace!("intern_shallow {:?}", alloc_id); + // remove allocation + // FIXME(#120456) - is `swap_remove` correct? + let Some((kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else { + return Err(InternError::DanglingPointer); + }; + + if let Err(err) = prepare_alloc(*ecx.tcx, kind, &mut alloc, mutability) { + // We want to error here, but we have to first put the + // allocation back into the `alloc_map` to keep things in a consistent state. + ecx.memory.alloc_map.insert(alloc_id, (kind, alloc)); + return Err(err); + } + // link the alloc id to the actual allocation let alloc = ecx.tcx.mk_const_alloc(alloc); if let Some(static_id) = ecx.machine.static_def_id() { @@ -166,6 +190,7 @@ pub enum InternError { BadMutablePointer, DanglingPointer, ConstAllocNotGlobal, + PartialPointer, } /// Intern `ret` and everything it references. @@ -221,13 +246,11 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx>>( let mut todo: Vec<_> = if is_static { // Do not steal the root allocation, we need it later to create the return value of `eval_static_initializer`. // But still change its mutability to match the requested one. - let alloc = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap(); - alloc.1.mutability = base_mutability; - alloc.1.provenance().ptrs().iter().map(|&(_, prov)| prov).collect() + let (kind, alloc) = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap(); + prepare_alloc(*ecx.tcx, *kind, alloc, base_mutability)?; + alloc.provenance().ptrs().iter().map(|&(_, prov)| prov).collect() } else { - intern_shallow(ecx, base_alloc_id, base_mutability, Some(&mut disambiguator)) - .unwrap() - .collect() + intern_shallow(ecx, base_alloc_id, base_mutability, Some(&mut disambiguator))?.collect() }; // We need to distinguish "has just been interned" from "was already in `tcx`", // so we track this in a separate set. @@ -235,7 +258,6 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx>>( // Whether we encountered a bad mutable pointer. // We want to first report "dangling" and then "mutable", so we need to delay reporting these // errors. - let mut result = Ok(()); let mut found_bad_mutable_ptr = false; // Keep interning as long as there are things to intern. @@ -310,20 +332,15 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx>>( // okay with losing some potential for immutability here. This can anyway only affect // `static mut`. just_interned.insert(alloc_id); - match intern_shallow(ecx, alloc_id, inner_mutability, Some(&mut disambiguator)) { - Ok(nested) => todo.extend(nested), - Err(err) => { - ecx.tcx.dcx().delayed_bug("error during const interning"); - result = Err(err); - } - } + let next = intern_shallow(ecx, alloc_id, inner_mutability, Some(&mut disambiguator))?; + todo.extend(next); } - if found_bad_mutable_ptr && result.is_ok() { + if found_bad_mutable_ptr { // We found a mutable pointer inside a const where inner allocations should be immutable, // and there was no other error. This should usually never happen! However, this can happen // in unleash-miri mode, so report it as a normal error then. if ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you { - result = Err(InternError::BadMutablePointer); + return Err(InternError::BadMutablePointer); } else { span_bug!( ecx.tcx.span, @@ -331,7 +348,7 @@ pub fn intern_const_alloc_recursive<'tcx, M: CompileTimeMachine<'tcx>>( ); } } - result + Ok(()) } /// Intern `ret`. This function assumes that `ret` references no other allocation. diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 47bebf5371a9..2c1e5087e1c6 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -937,8 +937,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // (both global from `alloc_map` and local from `extra_fn_ptr_map`) if let Some(fn_val) = self.get_fn_alloc(id) { let align = match fn_val { - FnVal::Instance(instance) => { - self.tcx.codegen_instance_attrs(instance.def).alignment.unwrap_or(Align::ONE) + FnVal::Instance(_instance) => { + // FIXME: Until we have a clear design for the effects of align(N) functions + // on the address of function pointers, we don't consider the align(N) + // attribute on functions in the interpreter. + // See for more context. + Align::ONE } // Machine-specific extra functions currently do not support alignment restrictions. FnVal::Other(_) => Align::ONE, @@ -1310,29 +1314,20 @@ impl<'a, 'tcx, Prov: Provenance, Extra, Bytes: AllocBytes> } /// Mark the given sub-range (relative to this allocation reference) as uninitialized. - pub fn write_uninit(&mut self, range: AllocRange) -> InterpResult<'tcx> { + pub fn write_uninit(&mut self, range: AllocRange) { let range = self.range.subrange(range); - self.alloc - .write_uninit(&self.tcx, range) - .map_err(|e| e.to_interp_error(self.alloc_id)) - .into() + self.alloc.write_uninit(&self.tcx, range); } /// Mark the entire referenced range as uninitialized - pub fn write_uninit_full(&mut self) -> InterpResult<'tcx> { - self.alloc - .write_uninit(&self.tcx, self.range) - .map_err(|e| e.to_interp_error(self.alloc_id)) - .into() + pub fn write_uninit_full(&mut self) { + self.alloc.write_uninit(&self.tcx, self.range); } /// Remove all provenance in the reference range. - pub fn clear_provenance(&mut self) -> InterpResult<'tcx> { - self.alloc - .clear_provenance(&self.tcx, self.range) - .map_err(|e| e.to_interp_error(self.alloc_id)) - .into() + pub fn clear_provenance(&mut self) { + self.alloc.clear_provenance(&self.tcx, self.range); } } @@ -1423,11 +1418,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Side-step AllocRef and directly access the underlying bytes more efficiently. // (We are staying inside the bounds here and all bytes do get overwritten so all is good.) - let alloc_id = alloc_ref.alloc_id; - let bytes = alloc_ref - .alloc - .get_bytes_unchecked_for_overwrite(&alloc_ref.tcx, alloc_ref.range) - .map_err(move |e| e.to_interp_error(alloc_id))?; + let bytes = + alloc_ref.alloc.get_bytes_unchecked_for_overwrite(&alloc_ref.tcx, alloc_ref.range); // `zip` would stop when the first iterator ends; we want to definitely // cover all of `bytes`. for dest in bytes { @@ -1509,10 +1501,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // `get_bytes_mut` will clear the provenance, which is correct, // since we don't want to keep any provenance at the target. // This will also error if copying partial provenance is not supported. - let provenance = src_alloc - .provenance() - .prepare_copy(src_range, dest_offset, num_copies, self) - .map_err(|e| e.to_interp_error(src_alloc_id))?; + let provenance = + src_alloc.provenance().prepare_copy(src_range, dest_offset, num_copies, self); // Prepare a copy of the initialization mask. let init = src_alloc.init_mask().prepare_copy(src_range); @@ -1530,10 +1520,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { dest_range, )?; // Yes we do overwrite all bytes in `dest_bytes`. - let dest_bytes = dest_alloc - .get_bytes_unchecked_for_overwrite_ptr(&tcx, dest_range) - .map_err(|e| e.to_interp_error(dest_alloc_id))? - .as_mut_ptr(); + let dest_bytes = + dest_alloc.get_bytes_unchecked_for_overwrite_ptr(&tcx, dest_range).as_mut_ptr(); if init.no_bytes_init() { // Fast path: If all bytes are `uninit` then there is nothing to copy. The target range @@ -1542,9 +1530,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // This also avoids writing to the target bytes so that the backing allocation is never // touched if the bytes stay uninitialized for the whole interpreter execution. On contemporary // operating system this can avoid physically allocating the page. - dest_alloc - .write_uninit(&tcx, dest_range) - .map_err(|e| e.to_interp_error(dest_alloc_id))?; + dest_alloc.write_uninit(&tcx, dest_range); // `write_uninit` also resets the provenance, so we are done. return interp_ok(()); } diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 417134579086..560b0e1ae4e6 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -175,6 +175,16 @@ impl Immediate { } interp_ok(()) } + + pub fn has_provenance(&self) -> bool { + match self { + Immediate::Scalar(scalar) => matches!(scalar, Scalar::Ptr { .. }), + Immediate::ScalarPair(s1, s2) => { + matches!(s1, Scalar::Ptr { .. }) || matches!(s2, Scalar::Ptr { .. }) + } + Immediate::Uninit => false, + } + } } // ScalarPair needs a type to interpret, so we often have an immediate and a type together @@ -188,18 +198,18 @@ pub struct ImmTy<'tcx, Prov: Provenance = CtfeProvenance> { impl std::fmt::Display for ImmTy<'_, Prov> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// Helper function for printing a scalar to a FmtPrinter - fn p<'a, 'tcx, Prov: Provenance>( - cx: &mut FmtPrinter<'a, 'tcx>, + fn print_scalar<'a, 'tcx, Prov: Provenance>( + p: &mut FmtPrinter<'a, 'tcx>, s: Scalar, ty: Ty<'tcx>, ) -> Result<(), std::fmt::Error> { match s { - Scalar::Int(int) => cx.pretty_print_const_scalar_int(int, ty, true), + Scalar::Int(int) => p.pretty_print_const_scalar_int(int, ty, true), Scalar::Ptr(ptr, _sz) => { // Just print the ptr value. `pretty_print_const_scalar_ptr` would also try to // print what is points to, which would fail since it has no access to the local // memory. - cx.pretty_print_const_pointer(ptr, ty) + p.pretty_print_const_pointer(ptr, ty) } } } @@ -207,8 +217,9 @@ impl std::fmt::Display for ImmTy<'_, Prov> { match self.imm { Immediate::Scalar(s) => { if let Some(ty) = tcx.lift(self.layout.ty) { - let s = - FmtPrinter::print_string(tcx, Namespace::ValueNS, |cx| p(cx, s, ty))?; + let s = FmtPrinter::print_string(tcx, Namespace::ValueNS, |p| { + print_scalar(p, s, ty) + })?; f.write_str(&s)?; return Ok(()); } @@ -772,7 +783,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { mir_place: mir::Place<'tcx>, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - let _span = enter_trace_span!( + let _trace = enter_trace_span!( M, step::eval_place_to_op, ?mir_place, @@ -822,7 +833,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { mir_op: &mir::Operand<'tcx>, layout: Option>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - let _span = + let _trace = enter_trace_span!(M, step::eval_operand, ?mir_op, tracing_separate_thread = Empty); use rustc_middle::mir::Operand::*; diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 45c4edb85037..a86fdf80f601 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -234,6 +234,12 @@ impl<'tcx, Prov: Provenance> PlaceTy<'tcx, Prov> { } /// A place is either an mplace or some local. + /// + /// Note that the return value can be different even for logically identical places! + /// Specifically, if a local is stored in-memory, this may return `Local` or `MPlaceTy` + /// depending on how the place was constructed. In other words, seeing `Local` here does *not* + /// imply that this place does not point to memory. Every caller must therefore always handle + /// both cases. #[inline(always)] pub fn as_mplace_or_local( &self, @@ -526,7 +532,7 @@ where &self, mir_place: mir::Place<'tcx>, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - let _span = + let _trace = enter_trace_span!(M, step::eval_place, ?mir_place, tracing_separate_thread = Empty); let mut place = self.local_to_place(mir_place.local)?; @@ -705,7 +711,7 @@ where match value { Immediate::Scalar(scalar) => { - alloc.write_scalar(alloc_range(Size::ZERO, scalar.size()), scalar) + alloc.write_scalar(alloc_range(Size::ZERO, scalar.size()), scalar)?; } Immediate::ScalarPair(a_val, b_val) => { let BackendRepr::ScalarPair(a, b) = layout.backend_repr else { @@ -725,10 +731,10 @@ where alloc.write_scalar(alloc_range(Size::ZERO, a_val.size()), a_val)?; alloc.write_scalar(alloc_range(b_offset, b_val.size()), b_val)?; // We don't have to reset padding here, `write_immediate` will anyway do a validation run. - interp_ok(()) } Immediate::Uninit => alloc.write_uninit_full(), } + interp_ok(()) } pub fn write_uninit( @@ -748,7 +754,7 @@ where // Zero-sized access return interp_ok(()); }; - alloc.write_uninit_full()?; + alloc.write_uninit_full(); } } interp_ok(()) @@ -759,6 +765,13 @@ where &mut self, dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { + // If this is an efficiently represented local variable without provenance, skip the + // `as_mplace_or_mutable_local` that would otherwise force this local into memory. + if let Right(imm) = dest.to_op(self)?.as_mplace_or_imm() { + if !imm.has_provenance() { + return interp_ok(()); + } + } match self.as_mplace_or_mutable_local(&dest.to_place())? { Right((local_val, _local_layout, local)) => { local_val.clear_provenance()?; @@ -772,7 +785,7 @@ where // Zero-sized access return interp_ok(()); }; - alloc.clear_provenance()?; + alloc.clear_provenance(); } } interp_ok(()) diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs index f72c44180814..d05871bfc773 100644 --- a/compiler/rustc_const_eval/src/interpret/projection.rs +++ b/compiler/rustc_const_eval/src/interpret/projection.rs @@ -199,6 +199,15 @@ where base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self) } + /// Projects multiple fields at once. See [`Self::project_field`] for details. + pub fn project_fields, const N: usize>( + &self, + base: &P, + fields: [FieldIdx; N], + ) -> InterpResult<'tcx, [P; N]> { + fields.try_map(|field| self.project_field(base, field)) + } + /// Downcasting to an enum variant. pub fn project_downcast>( &self, diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 73cc87508ef9..7cabfd961212 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -20,7 +20,7 @@ use super::{ MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout, interp_ok, throw_ub, throw_unsup, }; -use crate::errors; +use crate::{enter_trace_span, errors}; // The Phantomdata exists to prevent this type from being `Send`. If it were sent across a thread // boundary and dropped in the other thread, it would exit the span in the other thread. @@ -386,6 +386,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Make sure all the constants required by this frame evaluate successfully (post-monomorphization check). for &const_ in body.required_consts() { + // We can't use `eval_mir_constant` here as that assumes that all required consts have + // already been checked, so we need a separate tracing call. + let _trace = enter_trace_span!(M, const_eval::required_consts, ?const_.const_); let c = self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?; c.eval(*self.tcx, self.typing_env, const_.span).map_err(|err| { diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 9df49c0f4ccd..084d45cf2cbb 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -4,6 +4,7 @@ use either::Either; use rustc_abi::{FIRST_VARIANT, FieldIdx}; +use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexSlice; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::{bug, mir, span_bug}; @@ -76,7 +77,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// /// This does NOT move the statement counter forward, the caller has to do that! pub fn eval_statement(&mut self, stmt: &mir::Statement<'tcx>) -> InterpResult<'tcx> { - let _span = enter_trace_span!( + let _trace = enter_trace_span!( M, step::eval_statement, stmt = ?stmt.kind, @@ -389,8 +390,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Evaluate the arguments of a function call fn eval_fn_call_argument( - &self, + &mut self, op: &mir::Operand<'tcx>, + move_definitely_disjoint: bool, ) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> { interp_ok(match op { mir::Operand::Copy(_) | mir::Operand::Constant(_) => { @@ -399,24 +401,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { FnArg::Copy(op) } mir::Operand::Move(place) => { - // If this place lives in memory, preserve its location. - // We call `place_to_op` which will be an `MPlaceTy` whenever there exists - // an mplace for this place. (This is in contrast to `PlaceTy::as_mplace_or_local` - // which can return a local even if that has an mplace.) let place = self.eval_place(*place)?; - let op = self.place_to_op(&place)?; - - match op.as_mplace_or_imm() { - Either::Left(mplace) => FnArg::InPlace(mplace), - Either::Right(_imm) => { - // This argument doesn't live in memory, so there's no place - // to make inaccessible during the call. - // We rely on there not being any stray `PlaceTy` that would let the - // caller directly access this local! - // This is also crucial for tail calls, where we want the `FnArg` to - // stay valid when the old stack frame gets popped. - FnArg::Copy(op) + if move_definitely_disjoint { + // We still have to ensure that no *other* pointers are used to access this place, + // so *if* it is in memory then we have to treat it as `InPlace`. + // Use `place_to_op` to guarantee that we notice it being in memory. + let op = self.place_to_op(&place)?; + match op.as_mplace_or_imm() { + Either::Left(mplace) => FnArg::InPlace(mplace), + Either::Right(_imm) => FnArg::Copy(op), } + } else { + // We have to force this into memory to detect aliasing among `Move` arguments. + FnArg::InPlace(self.force_allocation(&place)?) } } }) @@ -425,18 +422,46 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Shared part of `Call` and `TailCall` implementation — finding and evaluating all the /// necessary information about callee and arguments to make a call. fn eval_callee_and_args( - &self, + &mut self, terminator: &mir::Terminator<'tcx>, func: &mir::Operand<'tcx>, args: &[Spanned>], ) -> InterpResult<'tcx, EvaluatedCalleeAndArgs<'tcx, M>> { let func = self.eval_operand(func, None)?; + + // Evaluating function call arguments. The tricky part here is dealing with `Move` + // arguments: we have to ensure no two such arguments alias. This would be most easily done + // by just forcing them all into memory and then doing the usual in-place argument + // protection, but then we'd force *a lot* of arguments into memory. So we do some syntactic + // pre-processing here where if all `move` arguments are syntactically distinct local + // variables (and none is indirect), we can skip the in-memory forcing. + let move_definitely_disjoint = 'move_definitely_disjoint: { + let mut previous_locals = FxHashSet::::default(); + for arg in args { + let mir::Operand::Move(place) = arg.node else { + continue; // we can skip non-`Move` arguments. + }; + if place.is_indirect_first_projection() { + // An indirect `Move` argument could alias with anything else... + break 'move_definitely_disjoint false; + } + if !previous_locals.insert(place.local) { + // This local is the base for two arguments! They might overlap. + break 'move_definitely_disjoint false; + } + } + // We found no violation so they are all definitely disjoint. + true + }; let args = args .iter() - .map(|arg| self.eval_fn_call_argument(&arg.node)) + .map(|arg| self.eval_fn_call_argument(&arg.node, move_definitely_disjoint)) .collect::>>()?; - let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); + let fn_sig_binder = { + let _trace = enter_trace_span!(M, "fn_sig", ty = ?func.layout.ty.kind()); + func.layout.ty.fn_sig(*self.tcx) + }; let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.typing_env, fn_sig_binder); let extra_args = &args[fn_sig.inputs().len()..]; let extra_args = @@ -465,7 +490,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } fn eval_terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> InterpResult<'tcx> { - let _span = enter_trace_span!( + let _trace = enter_trace_span!( M, step::eval_terminator, terminator = ?terminator.kind, @@ -560,7 +585,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { "Async Drop must be expanded or reset to sync in runtime MIR" ); let place = self.eval_place(place)?; - let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); + let instance = { + let _trace = + enter_trace_span!(M, resolve::resolve_drop_in_place, ty = ?place.layout.ty); + Instance::resolve_drop_in_place(*self.tcx, place.layout.ty) + }; if let ty::InstanceKind::DropGlue(_, None) = instance.def { // This is the branch we enter if and only if the dropped type has no drop glue // whatsoever. This can happen as a result of monomorphizing a drop of a diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 71800950faab..72bee3454065 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -85,11 +85,11 @@ impl EnteredTraceSpan for tracing::span::EnteredSpan { /// # let my_debug_var = String::new(); /// // logs a span named "hello" with a field named "arg" of value 42 (works only because /// // 42 implements the tracing::Value trait, otherwise use one of the options below) -/// let _span = enter_trace_span!(M, "hello", arg = 42); +/// let _trace = enter_trace_span!(M, "hello", arg = 42); /// // logs a field called "my_display_var" using the Display implementation -/// let _span = enter_trace_span!(M, "hello", %my_display_var); +/// let _trace = enter_trace_span!(M, "hello", %my_display_var); /// // logs a field called "my_debug_var" using the Debug implementation -/// let _span = enter_trace_span!(M, "hello", ?my_debug_var); +/// let _trace = enter_trace_span!(M, "hello", ?my_debug_var); /// ``` /// /// ### `NAME::SUBNAME` syntax @@ -107,8 +107,8 @@ impl EnteredTraceSpan for tracing::span::EnteredSpan { /// # use rustc_const_eval::enter_trace_span; /// # type M = rustc_const_eval::const_eval::CompileTimeMachine<'static>; /// // for example, the first will expand to the second -/// let _span = enter_trace_span!(M, borrow_tracker::on_stack_pop, /* ... */); -/// let _span = enter_trace_span!(M, "borrow_tracker", borrow_tracker = "on_stack_pop", /* ... */); +/// let _trace = enter_trace_span!(M, borrow_tracker::on_stack_pop, /* ... */); +/// let _trace = enter_trace_span!(M, "borrow_tracker", borrow_tracker = "on_stack_pop", /* ... */); /// ``` /// /// ### `tracing_separate_thread` parameter @@ -124,7 +124,7 @@ impl EnteredTraceSpan for tracing::span::EnteredSpan { /// ```rust /// # use rustc_const_eval::enter_trace_span; /// # type M = rustc_const_eval::const_eval::CompileTimeMachine<'static>; -/// let _span = enter_trace_span!(M, step::eval_statement, tracing_separate_thread = tracing::field::Empty); +/// let _trace = enter_trace_span!(M, step::eval_statement, tracing_separate_thread = tracing::field::Empty); /// ``` /// /// ### Executing something else when tracing is disabled @@ -136,7 +136,7 @@ impl EnteredTraceSpan for tracing::span::EnteredSpan { /// # use rustc_const_eval::enter_trace_span; /// # use rustc_const_eval::interpret::EnteredTraceSpan; /// # type M = rustc_const_eval::const_eval::CompileTimeMachine<'static>; -/// let _span = enter_trace_span!(M, step::eval_statement) +/// let _trace = enter_trace_span!(M, step::eval_statement) /// .or_if_tracing_disabled(|| tracing::info!("eval_statement")); /// ``` #[macro_export] diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index ed48f53c3105..02e3d90f4af7 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -949,7 +949,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> { let padding_size = offset - padding_cleared_until; let range = alloc_range(padding_start, padding_size); trace!("reset_padding on {}: resetting padding range {range:?}", mplace.layout.ty); - alloc.write_uninit(range)?; + alloc.write_uninit(range); } padding_cleared_until = offset + size; } @@ -1239,7 +1239,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt, if self.reset_provenance_and_padding { // We can't share this with above as above, we might be looking at read-only memory. let mut alloc = self.ecx.get_ptr_alloc_mut(mplace.ptr(), size)?.expect("we already excluded size 0"); - alloc.clear_provenance()?; + alloc.clear_provenance(); // Also, mark this as containing data, not padding. self.add_data_range(mplace.ptr(), size); } @@ -1415,10 +1415,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { recursive: bool, reset_provenance_and_padding: bool, ) -> InterpResult<'tcx> { - let _span = enter_trace_span!( + let _trace = enter_trace_span!( M, "validate_operand", - "recursive={recursive}, reset_provenance_and_padding={reset_provenance_and_padding}, val={val:?}" + recursive, + reset_provenance_and_padding, + ?val, ); // Note that we *could* actually be in CTFE here with `-Zextra-const-ub-checks`, but it's diff --git a/compiler/rustc_const_eval/src/interpret/visitor.rs b/compiler/rustc_const_eval/src/interpret/visitor.rs index a27b66461315..82c50fac6c0e 100644 --- a/compiler/rustc_const_eval/src/interpret/visitor.rs +++ b/compiler/rustc_const_eval/src/interpret/visitor.rs @@ -121,25 +121,24 @@ pub trait ValueVisitor<'tcx, M: Machine<'tcx>>: Sized { // `Box` has two fields: the pointer we care about, and the allocator. assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields"); - let (unique_ptr, alloc) = ( - self.ecx().project_field(v, FieldIdx::ZERO)?, - self.ecx().project_field(v, FieldIdx::ONE)?, - ); + let [unique_ptr, alloc] = + self.ecx().project_fields(v, [FieldIdx::ZERO, FieldIdx::ONE])?; + // Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`... // (which means another 2 fields, the second of which is a `PhantomData`) assert_eq!(unique_ptr.layout().fields.count(), 2); - let (nonnull_ptr, phantom) = ( - self.ecx().project_field(&unique_ptr, FieldIdx::ZERO)?, - self.ecx().project_field(&unique_ptr, FieldIdx::ONE)?, - ); + let [nonnull_ptr, phantom] = + self.ecx().project_fields(&unique_ptr, [FieldIdx::ZERO, FieldIdx::ONE])?; assert!( phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()), "2nd field of `Unique` should be PhantomData but is {:?}", phantom.layout().ty, ); + // ... that contains a `NonNull`... (gladly, only a single field here) assert_eq!(nonnull_ptr.layout().fields.count(), 1); let raw_ptr = self.ecx().project_field(&nonnull_ptr, FieldIdx::ZERO)?; // the actual raw ptr + // ... whose only field finally is a raw ptr we can dereference. self.visit_box(ty, &raw_ptr)?; diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index bf7a79dcb20f..8ace560d85d1 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -1,7 +1,9 @@ // tidy-alphabetical-start #![allow(internal_features)] #![allow(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(bootstrap, feature(strict_overflow_ops))] #![doc(rust_logo)] +#![feature(array_try_map)] #![feature(assert_matches)] #![feature(box_patterns)] #![feature(decl_macro)] @@ -9,7 +11,6 @@ #![feature(never_type)] #![feature(rustdoc_internals)] #![feature(slice_ptr_get)] -#![feature(strict_overflow_ops)] #![feature(trait_alias)] #![feature(try_blocks)] #![feature(unqualified_local_imports)] diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs index c437934eaabe..5249b32eca46 100644 --- a/compiler/rustc_const_eval/src/util/caller_location.rs +++ b/compiler/rustc_const_eval/src/util/caller_location.rs @@ -42,12 +42,12 @@ fn alloc_caller_location<'tcx>( let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); // Initialize fields. - ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap()) - .expect("writing to memory we just allocated cannot fail"); - ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap()) - .expect("writing to memory we just allocated cannot fail"); - ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap()) + let [filename_field, line_field, col_field] = + ecx.project_fields(&location, [0, 1, 2].map(FieldIdx::from_u32)).unwrap(); + ecx.write_immediate(filename, &filename_field) .expect("writing to memory we just allocated cannot fail"); + ecx.write_scalar(line, &line_field).expect("writing to memory we just allocated cannot fail"); + ecx.write_scalar(col, &col_field).expect("writing to memory we just allocated cannot fail"); location } diff --git a/compiler/rustc_const_eval/src/util/type_name.rs b/compiler/rustc_const_eval/src/util/type_name.rs index e8f2728a7728..2ae6655901b6 100644 --- a/compiler/rustc_const_eval/src/util/type_name.rs +++ b/compiler/rustc_const_eval/src/util/type_name.rs @@ -1,24 +1,27 @@ use std::fmt::Write; use rustc_data_structures::intern::Interned; -use rustc_hir::def_id::CrateNum; +use rustc_hir::def_id::{CrateNum, DefId}; use rustc_hir::definitions::DisambiguatedDefPathData; use rustc_middle::bug; -use rustc_middle::ty::print::{PrettyPrinter, Print, PrintError, Printer}; -use rustc_middle::ty::{self, GenericArg, GenericArgKind, Ty, TyCtxt}; +use rustc_middle::ty::print::{PrettyPrinter, PrintError, Printer}; +use rustc_middle::ty::{self, GenericArg, Ty, TyCtxt}; -struct AbsolutePathPrinter<'tcx> { +struct TypeNamePrinter<'tcx> { tcx: TyCtxt<'tcx>, path: String, } -impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { +impl<'tcx> Printer<'tcx> for TypeNamePrinter<'tcx> { fn tcx(&self) -> TyCtxt<'tcx> { self.tcx } fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> { - Ok(()) + // FIXME: most regions have been erased by the time this code runs. + // Just printing `'_` is a bit hacky but gives mostly good results, and + // doing better is difficult. See `should_print_optional_region`. + write!(self, "'_") } fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> { @@ -73,27 +76,26 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { self.pretty_print_dyn_existential(predicates) } - fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> { + fn print_crate_name(&mut self, cnum: CrateNum) -> Result<(), PrintError> { self.path.push_str(self.tcx.crate_name(cnum).as_str()); Ok(()) } - fn path_qualified( + fn print_path_with_qualified( &mut self, self_ty: Ty<'tcx>, trait_ref: Option>, ) -> Result<(), PrintError> { - self.pretty_path_qualified(self_ty, trait_ref) + self.pretty_print_path_with_qualified(self_ty, trait_ref) } - fn path_append_impl( + fn print_path_with_impl( &mut self, print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, - _disambiguated_data: &DisambiguatedDefPathData, self_ty: Ty<'tcx>, trait_ref: Option>, ) -> Result<(), PrintError> { - self.pretty_path_append_impl( + self.pretty_print_path_with_impl( |cx| { print_prefix(cx)?; @@ -106,7 +108,7 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { ) } - fn path_append( + fn print_path_with_simple( &mut self, print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, disambiguated_data: &DisambiguatedDefPathData, @@ -118,39 +120,60 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { Ok(()) } - fn path_generic_args( + fn print_path_with_generic_args( &mut self, print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, args: &[GenericArg<'tcx>], ) -> Result<(), PrintError> { print_prefix(self)?; - let args = - args.iter().cloned().filter(|arg| !matches!(arg.kind(), GenericArgKind::Lifetime(_))); - if args.clone().next().is_some() { - self.generic_delimiters(|cx| cx.comma_sep(args)) + if !args.is_empty() { + self.generic_delimiters(|cx| cx.comma_sep(args.iter().copied())) } else { Ok(()) } } -} -impl<'tcx> PrettyPrinter<'tcx> for AbsolutePathPrinter<'tcx> { - fn should_print_region(&self, _region: ty::Region<'_>) -> bool { - false - } - fn comma_sep(&mut self, mut elems: impl Iterator) -> Result<(), PrintError> - where - T: Print<'tcx, Self>, - { - if let Some(first) = elems.next() { - first.print(self)?; - for elem in elems { - self.path.push_str(", "); - elem.print(self)?; + fn print_coroutine_with_kind( + &mut self, + def_id: DefId, + parent_args: &'tcx [GenericArg<'tcx>], + kind: Ty<'tcx>, + ) -> Result<(), PrintError> { + self.print_def_path(def_id, parent_args)?; + + let ty::Coroutine(_, args) = self.tcx.type_of(def_id).instantiate_identity().kind() else { + // Could be `ty::Error`. + return Ok(()); + }; + + let default_kind = args.as_coroutine().kind_ty(); + + match kind.to_opt_closure_kind() { + _ if kind == default_kind => { + // No need to mark the closure if it's the deduced coroutine kind. + } + Some(ty::ClosureKind::Fn) | None => { + // Should never happen. Just don't mark anything rather than panicking. } + Some(ty::ClosureKind::FnMut) => self.path.push_str("::{{call_mut}}"), + Some(ty::ClosureKind::FnOnce) => self.path.push_str("::{{call_once}}"), } + Ok(()) } +} + +impl<'tcx> PrettyPrinter<'tcx> for TypeNamePrinter<'tcx> { + fn should_print_optional_region(&self, region: ty::Region<'_>) -> bool { + // Bound regions are always printed (as `'_`), which gives some idea that they are special, + // even though the `for` is omitted by the pretty printer. + // E.g. `for<'a, 'b> fn(&'a u32, &'b u32)` is printed as "fn(&'_ u32, &'_ u32)". + match region.kind() { + ty::ReErased | ty::ReEarlyParam(_) => false, + ty::ReBound(..) => true, + _ => unreachable!(), + } + } fn generic_delimiters( &mut self, @@ -171,7 +194,7 @@ impl<'tcx> PrettyPrinter<'tcx> for AbsolutePathPrinter<'tcx> { } } -impl Write for AbsolutePathPrinter<'_> { +impl Write for TypeNamePrinter<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.path.push_str(s); Ok(()) @@ -179,7 +202,7 @@ impl Write for AbsolutePathPrinter<'_> { } pub fn type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> String { - let mut printer = AbsolutePathPrinter { tcx, path: String::new() }; - printer.print_type(ty).unwrap(); - printer.path + let mut p = TypeNamePrinter { tcx, path: String::new() }; + p.print_type(ty).unwrap(); + p.path } diff --git a/compiler/rustc_error_codes/src/error_codes/E0518.md b/compiler/rustc_error_codes/src/error_codes/E0518.md index f04329bc4e61..87dc231578ac 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0518.md +++ b/compiler/rustc_error_codes/src/error_codes/E0518.md @@ -1,9 +1,11 @@ +#### Note: this error code is no longer emitted by the compiler. + An `#[inline(..)]` attribute was incorrectly placed on something other than a function or method. Example of erroneous code: -```compile_fail,E0518 +```ignore (no longer emitted) #[inline(always)] struct Foo; diff --git a/compiler/rustc_error_codes/src/error_codes/E0562.md b/compiler/rustc_error_codes/src/error_codes/E0562.md index 95f038df56d6..af7b219fb120 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0562.md +++ b/compiler/rustc_error_codes/src/error_codes/E0562.md @@ -1,5 +1,4 @@ -Abstract return types (written `impl Trait` for some trait `Trait`) are only -allowed as function and inherent impl return types. +`impl Trait` is only allowed as a function return and argument type. Erroneous code example: @@ -14,7 +13,7 @@ fn main() { } ``` -Make sure `impl Trait` only appears in return-type position. +Make sure `impl Trait` appears in a function signature. ``` fn count_to_n(n: usize) -> impl Iterator { @@ -28,6 +27,6 @@ fn main() { } ``` -See [RFC 1522] for more details. +See the [reference] for more details on `impl Trait`. -[RFC 1522]: https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md +[reference]: https://doc.rust-lang.org/stable/reference/types/impl-trait.html diff --git a/compiler/rustc_error_codes/src/error_codes/E0578.md b/compiler/rustc_error_codes/src/error_codes/E0578.md index fca89757287f..78fabe855bb6 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0578.md +++ b/compiler/rustc_error_codes/src/error_codes/E0578.md @@ -1,8 +1,10 @@ +#### Note: this error code is no longer emitted by the compiler. + A module cannot be found and therefore, the visibility cannot be determined. Erroneous code example: -```compile_fail,E0578,edition2018 +```ignore (no longer emitted) foo!(); pub (in ::Sea) struct Shark; // error! diff --git a/compiler/rustc_error_codes/src/error_codes/E0701.md b/compiler/rustc_error_codes/src/error_codes/E0701.md index 4965e6431059..e1be0e915f44 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0701.md +++ b/compiler/rustc_error_codes/src/error_codes/E0701.md @@ -1,9 +1,11 @@ +#### Note: this error code is no longer emitted by the compiler. + This error indicates that a `#[non_exhaustive]` attribute was incorrectly placed on something other than a struct or enum. Erroneous code example: -```compile_fail,E0701 +```ignore (no longer emitted) #[non_exhaustive] trait Foo { } ``` diff --git a/compiler/rustc_error_codes/src/error_codes/E0739.md b/compiler/rustc_error_codes/src/error_codes/E0739.md index 406d3d52779d..5403405ca9dc 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0739.md +++ b/compiler/rustc_error_codes/src/error_codes/E0739.md @@ -1,8 +1,10 @@ +#### Note: this error code is no longer emitted by the compiler. + `#[track_caller]` must be applied to a function Erroneous code example: -```compile_fail,E0739 +```ignore (no longer emitted) #[track_caller] struct Bar { a: u8, diff --git a/compiler/rustc_error_codes/src/error_codes/E0755.md b/compiler/rustc_error_codes/src/error_codes/E0755.md index b67f078c78ec..bd93626a8db4 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0755.md +++ b/compiler/rustc_error_codes/src/error_codes/E0755.md @@ -1,8 +1,10 @@ +#### Note: this error code is no longer emitted by the compiler. + The `ffi_pure` attribute was used on a non-foreign function. Erroneous code example: -```compile_fail,E0755 +```ignore (no longer emitted) #![feature(ffi_pure)] #[unsafe(ffi_pure)] // error! diff --git a/compiler/rustc_error_codes/src/error_codes/E0756.md b/compiler/rustc_error_codes/src/error_codes/E0756.md index aadde038d12c..daafc2a5ac09 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0756.md +++ b/compiler/rustc_error_codes/src/error_codes/E0756.md @@ -1,9 +1,11 @@ +#### Note: this error code is no longer emitted by the compiler. + The `ffi_const` attribute was used on something other than a foreign function declaration. Erroneous code example: -```compile_fail,E0756 +```ignore (no longer emitted) #![feature(ffi_const)] #[unsafe(ffi_const)] // error! diff --git a/compiler/rustc_error_codes/src/error_codes/E0788.md b/compiler/rustc_error_codes/src/error_codes/E0788.md index ba138aed2d12..1afa961f9b7c 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0788.md +++ b/compiler/rustc_error_codes/src/error_codes/E0788.md @@ -1,3 +1,5 @@ +#### Note: this error code is no longer emitted by the compiler. + A `#[coverage(off|on)]` attribute was found in a position where it is not allowed. @@ -10,7 +12,7 @@ Coverage attributes can be applied to: Example of erroneous code: -```compile_fail,E0788 +```ignore (no longer emitted) unsafe extern "C" { #[coverage(off)] fn foreign_fn(); diff --git a/compiler/rustc_error_codes/src/error_codes/E0793.md b/compiler/rustc_error_codes/src/error_codes/E0793.md index ccd1b43bd194..2722dac104b7 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0793.md +++ b/compiler/rustc_error_codes/src/error_codes/E0793.md @@ -1,4 +1,9 @@ -An unaligned reference to a field of a [packed] struct got created. +An unaligned reference to a field of a [packed] `struct` or `union` was created. + +The `#[repr(packed)]` attribute removes padding between fields, which can +cause fields to be stored at unaligned memory addresses. Creating references +to such fields violates Rust's memory safety guarantees and can lead to +undefined behavior in optimized code. Erroneous code example: @@ -45,9 +50,36 @@ unsafe { // For formatting, we can create a copy to avoid the direct reference. let copy = foo.field1; println!("{}", copy); + // Creating a copy can be written in a single line with curly braces. // (This is equivalent to the two lines above.) println!("{}", { foo.field1 }); + + // A reference to a field that will always be sufficiently aligned is safe: + println!("{}", foo.field2); +} +``` + +### Unions + +Although creating a reference to a `union` field is `unsafe`, this error +will still be triggered if the referenced field is not sufficiently +aligned. Use `addr_of!` and raw pointers in the same way as for struct fields. + +```compile_fail,E0793 +#[repr(packed)] +pub union Foo { + field1: u64, + field2: u8, +} + +unsafe { + let foo = Foo { field1: 0 }; + // Accessing the field directly is fine. + let val = foo.field1; + + // A reference to a packed union field causes an error. + let val = &foo.field1; // ERROR } ``` diff --git a/compiler/rustc_error_messages/Cargo.toml b/compiler/rustc_error_messages/Cargo.toml index 0951859fa531..552ad672752d 100644 --- a/compiler/rustc_error_messages/Cargo.toml +++ b/compiler/rustc_error_messages/Cargo.toml @@ -11,6 +11,8 @@ icu_list = "1.2" icu_locid = "1.2" icu_provider_adapters = "1.2" intl-memoizer = "0.5.1" +rustc_ast = { path = "../rustc_ast" } +rustc_ast_pretty = { path = "../rustc_ast_pretty" } rustc_baked_icu_data = { path = "../rustc_baked_icu_data" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_error_messages/src/diagnostic_impls.rs b/compiler/rustc_error_messages/src/diagnostic_impls.rs new file mode 100644 index 000000000000..3b664cce5776 --- /dev/null +++ b/compiler/rustc_error_messages/src/diagnostic_impls.rs @@ -0,0 +1,205 @@ +use std::backtrace::Backtrace; +use std::borrow::Cow; +use std::fmt; +use std::num::ParseIntError; +use std::path::{Path, PathBuf}; +use std::process::ExitStatus; + +use rustc_ast as ast; +use rustc_ast_pretty::pprust; +use rustc_span::edition::Edition; + +use crate::{DiagArgValue, IntoDiagArg}; + +pub struct DiagArgFromDisplay<'a>(pub &'a dyn fmt::Display); + +impl IntoDiagArg for DiagArgFromDisplay<'_> { + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { + self.0.to_string().into_diag_arg(path) + } +} + +impl<'a> From<&'a dyn fmt::Display> for DiagArgFromDisplay<'a> { + fn from(t: &'a dyn fmt::Display) -> Self { + DiagArgFromDisplay(t) + } +} + +impl<'a, T: fmt::Display> From<&'a T> for DiagArgFromDisplay<'a> { + fn from(t: &'a T) -> Self { + DiagArgFromDisplay(t) + } +} + +impl<'a, T: Clone + IntoDiagArg> IntoDiagArg for &'a T { + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { + self.clone().into_diag_arg(path) + } +} + +#[macro_export] +macro_rules! into_diag_arg_using_display { + ($( $ty:ty ),+ $(,)?) => { + $( + impl $crate::IntoDiagArg for $ty { + fn into_diag_arg(self, path: &mut Option) -> $crate::DiagArgValue { + self.to_string().into_diag_arg(path) + } + } + )+ + } +} + +macro_rules! into_diag_arg_for_number { + ($( $ty:ty ),+ $(,)?) => { + $( + impl $crate::IntoDiagArg for $ty { + fn into_diag_arg(self, path: &mut Option) -> $crate::DiagArgValue { + // Convert to a string if it won't fit into `Number`. + #[allow(irrefutable_let_patterns)] + if let Ok(n) = TryInto::::try_into(self) { + $crate::DiagArgValue::Number(n) + } else { + self.to_string().into_diag_arg(path) + } + } + } + )+ + } +} + +into_diag_arg_using_display!( + ast::ParamKindOrd, + std::io::Error, + Box, + std::num::NonZero, + Edition, + rustc_span::Ident, + rustc_span::MacroRulesNormalizedIdent, + ParseIntError, + ExitStatus, +); + +into_diag_arg_for_number!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize); + +impl IntoDiagArg for bool { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + if self { + DiagArgValue::Str(Cow::Borrowed("true")) + } else { + DiagArgValue::Str(Cow::Borrowed("false")) + } + } +} + +impl IntoDiagArg for char { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(format!("{self:?}"))) + } +} + +impl IntoDiagArg for Vec { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::StrListSepByAnd( + self.into_iter().map(|c| Cow::Owned(format!("{c:?}"))).collect(), + ) + } +} + +impl IntoDiagArg for rustc_span::Symbol { + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { + self.to_ident_string().into_diag_arg(path) + } +} + +impl<'a> IntoDiagArg for &'a str { + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { + self.to_string().into_diag_arg(path) + } +} + +impl IntoDiagArg for String { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self)) + } +} + +impl<'a> IntoDiagArg for Cow<'a, str> { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.into_owned())) + } +} + +impl<'a> IntoDiagArg for &'a Path { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.display().to_string())) + } +} + +impl IntoDiagArg for PathBuf { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.display().to_string())) + } +} + +impl IntoDiagArg for ast::Expr { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(pprust::expr_to_string(&self))) + } +} + +impl IntoDiagArg for ast::Path { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(pprust::path_to_string(&self))) + } +} + +impl IntoDiagArg for ast::token::Token { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(pprust::token_to_string(&self)) + } +} + +impl IntoDiagArg for ast::token::TokenKind { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(pprust::token_kind_to_string(&self)) + } +} + +impl IntoDiagArg for std::ffi::CString { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned())) + } +} + +impl IntoDiagArg for rustc_data_structures::small_c_str::SmallCStr { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned())) + } +} + +impl IntoDiagArg for ast::Visibility { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + let s = pprust::vis_to_string(&self); + let s = s.trim_end().to_string(); + DiagArgValue::Str(Cow::Owned(s)) + } +} + +impl IntoDiagArg for Backtrace { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::from(self.to_string())) + } +} + +impl IntoDiagArg for ast::util::parser::ExprPrecedence { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Number(self as i32) + } +} + +impl IntoDiagArg for ast::FloatTy { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Borrowed(self.name_str())) + } +} diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 4b3ecad307fe..d8bacbe762b1 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -23,6 +23,9 @@ use rustc_span::Span; use tracing::{instrument, trace}; pub use unic_langid::{LanguageIdentifier, langid}; +mod diagnostic_impls; +pub use diagnostic_impls::DiagArgFromDisplay; + pub type FluentBundle = IntoDynSyncSend>; @@ -589,3 +592,53 @@ pub fn fluent_value_from_str_list_sep_by_and(l: Vec>) -> FluentValu FluentValue::Custom(Box::new(FluentStrListSepByAnd(l))) } + +/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of +/// `DiagArg` are converted to `FluentArgs` (consuming the collection) at the start of diagnostic +/// emission. +pub type DiagArg<'iter> = (&'iter DiagArgName, &'iter DiagArgValue); + +/// Name of a diagnostic argument. +pub type DiagArgName = Cow<'static, str>; + +/// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted +/// to a `FluentValue` by the emitter to be used in diagnostic translation. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] +pub enum DiagArgValue { + Str(Cow<'static, str>), + // This gets converted to a `FluentNumber`, which is an `f64`. An `i32` + // safely fits in an `f64`. Any integers bigger than that will be converted + // to strings in `into_diag_arg` and stored using the `Str` variant. + Number(i32), + StrListSepByAnd(Vec>), +} + +/// Converts a value of a type into a `DiagArg` (typically a field of an `Diag` struct). +/// Implemented as a custom trait rather than `From` so that it is implemented on the type being +/// converted rather than on `DiagArgValue`, which enables types from other `rustc_*` crates to +/// implement this. +pub trait IntoDiagArg { + /// Convert `Self` into a `DiagArgValue` suitable for rendering in a diagnostic. + /// + /// It takes a `path` where "long values" could be written to, if the `DiagArgValue` is too big + /// for displaying on the terminal. This path comes from the `Diag` itself. When rendering + /// values that come from `TyCtxt`, like `Ty<'_>`, they can use `TyCtxt::short_string`. If a + /// value has no shortening logic that could be used, the argument can be safely ignored. + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue; +} + +impl IntoDiagArg for DiagArgValue { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + self + } +} + +impl From for FluentValue<'static> { + fn from(val: DiagArgValue) -> Self { + match val { + DiagArgValue::Str(s) => From::from(s), + DiagArgValue::Number(n) => From::from(n), + DiagArgValue::StrListSepByAnd(l) => fluent_value_from_str_list_sep_by_and(l), + } + } +} diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 3e8cf6207aed..ad6d29e21fc4 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -8,22 +8,18 @@ edition = "2024" annotate-snippets = "0.11" derive_setters = "0.1.6" rustc_abi = { path = "../rustc_abi" } -rustc_ast = { path = "../rustc_ast" } -rustc_ast_pretty = { path = "../rustc_ast_pretty" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_error_codes = { path = "../rustc_error_codes" } rustc_error_messages = { path = "../rustc_error_messages" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hashes = { path = "../rustc_hashes" } -rustc_hir = { path = "../rustc_hir" } +rustc_hir_id = { path = "../rustc_hir_id" } rustc_index = { path = "../rustc_index" } rustc_lexer = { path = "../rustc_lexer" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } -rustc_target = { path = "../rustc_target" } -rustc_type_ir = { path = "../rustc_type_ir" } serde = { version = "1.0.125", features = ["derive"] } serde_json = "1.0.59" termcolor = "1.2.0" diff --git a/compiler/rustc_errors/src/codes.rs b/compiler/rustc_errors/src/codes.rs index 947cf27ca795..787a8af99b1f 100644 --- a/compiler/rustc_errors/src/codes.rs +++ b/compiler/rustc_errors/src/codes.rs @@ -20,6 +20,8 @@ impl fmt::Display for ErrCode { } } +rustc_error_messages::into_diag_arg_using_display!(ErrCode); + macro_rules! define_error_code_constants_and_diagnostics_table { ($($name:ident: $num:literal,)*) => ( $( diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 96c7ba6ed27b..183dceddd2c7 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use std::thread::panicking; use rustc_data_structures::fx::FxIndexMap; -use rustc_error_messages::{FluentValue, fluent_value_from_str_list_sep_by_and}; +use rustc_error_messages::{DiagArgName, DiagArgValue, IntoDiagArg}; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; use rustc_span::source_map::Spanned; @@ -22,26 +22,6 @@ use crate::{ Suggestions, }; -/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of -/// `DiagArg` are converted to `FluentArgs` (consuming the collection) at the start of diagnostic -/// emission. -pub type DiagArg<'iter> = (&'iter DiagArgName, &'iter DiagArgValue); - -/// Name of a diagnostic argument. -pub type DiagArgName = Cow<'static, str>; - -/// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted -/// to a `FluentValue` by the emitter to be used in diagnostic translation. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] -pub enum DiagArgValue { - Str(Cow<'static, str>), - // This gets converted to a `FluentNumber`, which is an `f64`. An `i32` - // safely fits in an `f64`. Any integers bigger than that will be converted - // to strings in `into_diag_arg` and stored using the `Str` variant. - Number(i32), - StrListSepByAnd(Vec>), -} - pub type DiagArgMap = FxIndexMap; /// Trait for types that `Diag::emit` can return as a "guarantee" (or "proof") @@ -143,36 +123,6 @@ where } } -/// Converts a value of a type into a `DiagArg` (typically a field of an `Diag` struct). -/// Implemented as a custom trait rather than `From` so that it is implemented on the type being -/// converted rather than on `DiagArgValue`, which enables types from other `rustc_*` crates to -/// implement this. -pub trait IntoDiagArg { - /// Convert `Self` into a `DiagArgValue` suitable for rendering in a diagnostic. - /// - /// It takes a `path` where "long values" could be written to, if the `DiagArgValue` is too big - /// for displaying on the terminal. This path comes from the `Diag` itself. When rendering - /// values that come from `TyCtxt`, like `Ty<'_>`, they can use `TyCtxt::short_string`. If a - /// value has no shortening logic that could be used, the argument can be safely ignored. - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue; -} - -impl IntoDiagArg for DiagArgValue { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - self - } -} - -impl From for FluentValue<'static> { - fn from(val: DiagArgValue) -> Self { - match val { - DiagArgValue::Str(s) => From::from(s), - DiagArgValue::Number(n) => From::from(n), - DiagArgValue::StrListSepByAnd(l) => fluent_value_from_str_list_sep_by_and(l), - } - } -} - /// Trait implemented by error types. This should not be implemented manually. Instead, use /// `#[derive(Subdiagnostic)]` -- see [rustc_macros::Subdiagnostic]. #[rustc_diagnostic_item = "Subdiagnostic"] @@ -847,17 +797,18 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> { self } + with_fn! { with_span_help, /// Prints the span with some help above it. /// This is like [`Diag::help()`], but it gets its own span. #[rustc_lint_diagnostics] - pub fn span_help>( + pub fn span_help( &mut self, - sp: S, + sp: impl Into, msg: impl Into, ) -> &mut Self { self.sub(Level::Help, msg, sp.into()); self - } + } } /// Disallow attaching suggestions to this diagnostic. /// Any suggestions attached e.g. with the `span_suggestion_*` methods @@ -1112,7 +1063,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> { .map(|snippet| { debug_assert!( !(sp.is_empty() && snippet.is_empty()), - "Span must not be empty and have no suggestion" + "Span `{sp:?}` must not be empty and have no suggestion" ); Substitution { parts: vec![SubstitutionPart { snippet, span: sp }] } }) @@ -1382,6 +1333,11 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> { &mut self.long_ty_path } + pub fn with_long_ty_path(mut self, long_ty_path: Option) -> Self { + self.long_ty_path = long_ty_path; + self + } + /// Most `emit_producing_guarantee` functions use this as a starting point. fn emit_producing_nothing(mut self) { let diag = self.take_diag(); diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs index eca5806fac52..435d16a83806 100644 --- a/compiler/rustc_errors/src/diagnostic_impls.rs +++ b/compiler/rustc_errors/src/diagnostic_impls.rs @@ -1,317 +1,22 @@ -use std::backtrace::Backtrace; use std::borrow::Cow; -use std::fmt; -use std::num::ParseIntError; -use std::path::{Path, PathBuf}; -use std::process::ExitStatus; use rustc_abi::TargetDataLayoutErrors; -use rustc_ast::util::parser::ExprPrecedence; -use rustc_ast_pretty::pprust; -use rustc_hir::RustcVersion; +use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::Subdiagnostic; -use rustc_span::edition::Edition; -use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol}; -use rustc_target::spec::{PanicStrategy, SplitDebuginfo, StackProtector, TargetTuple}; -use rustc_type_ir::{ClosureKind, FloatTy}; -use {rustc_ast as ast, rustc_hir as hir}; +use rustc_span::{Span, Symbol}; use crate::diagnostic::DiagLocation; use crate::{ - Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, ErrCode, IntoDiagArg, Level, - Subdiagnostic, fluent_generated as fluent, + Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, Subdiagnostic, + fluent_generated as fluent, }; -pub struct DiagArgFromDisplay<'a>(pub &'a dyn fmt::Display); - -impl IntoDiagArg for DiagArgFromDisplay<'_> { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.0.to_string().into_diag_arg(path) - } -} - -impl<'a> From<&'a dyn fmt::Display> for DiagArgFromDisplay<'a> { - fn from(t: &'a dyn fmt::Display) -> Self { - DiagArgFromDisplay(t) - } -} - -impl<'a, T: fmt::Display> From<&'a T> for DiagArgFromDisplay<'a> { - fn from(t: &'a T) -> Self { - DiagArgFromDisplay(t) - } -} - -impl<'a, T: Clone + IntoDiagArg> IntoDiagArg for &'a T { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.clone().into_diag_arg(path) - } -} - -#[macro_export] -macro_rules! into_diag_arg_using_display { - ($( $ty:ty ),+ $(,)?) => { - $( - impl IntoDiagArg for $ty { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.to_string().into_diag_arg(path) - } - } - )+ - } -} - -macro_rules! into_diag_arg_for_number { - ($( $ty:ty ),+ $(,)?) => { - $( - impl IntoDiagArg for $ty { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - // Convert to a string if it won't fit into `Number`. - #[allow(irrefutable_let_patterns)] - if let Ok(n) = TryInto::::try_into(self) { - DiagArgValue::Number(n) - } else { - self.to_string().into_diag_arg(path) - } - } - } - )+ - } -} - -into_diag_arg_using_display!( - ast::ParamKindOrd, - std::io::Error, - Box, - std::num::NonZero, - hir::Target, - Edition, - Ident, - MacroRulesNormalizedIdent, - ParseIntError, - StackProtector, - &TargetTuple, - SplitDebuginfo, - ExitStatus, - ErrCode, - rustc_abi::ExternAbi, -); - -impl IntoDiagArg for RustcVersion { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.to_string())) - } -} - -impl IntoDiagArg for rustc_type_ir::TraitRef { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.to_string().into_diag_arg(path) - } -} - -impl IntoDiagArg for rustc_type_ir::ExistentialTraitRef { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.to_string().into_diag_arg(path) - } -} - -impl IntoDiagArg for rustc_type_ir::UnevaluatedConst { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - format!("{self:?}").into_diag_arg(path) - } -} - -impl IntoDiagArg for rustc_type_ir::FnSig { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - format!("{self:?}").into_diag_arg(path) - } -} - -impl IntoDiagArg for rustc_type_ir::Binder -where - T: IntoDiagArg, -{ - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.skip_binder().into_diag_arg(path) - } -} - -into_diag_arg_for_number!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize); - -impl IntoDiagArg for bool { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - if self { - DiagArgValue::Str(Cow::Borrowed("true")) - } else { - DiagArgValue::Str(Cow::Borrowed("false")) - } - } -} - -impl IntoDiagArg for char { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(format!("{self:?}"))) - } -} - -impl IntoDiagArg for Vec { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::StrListSepByAnd( - self.into_iter().map(|c| Cow::Owned(format!("{c:?}"))).collect(), - ) - } -} - -impl IntoDiagArg for Symbol { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.to_ident_string().into_diag_arg(path) - } -} - -impl<'a> IntoDiagArg for &'a str { - fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { - self.to_string().into_diag_arg(path) - } -} - -impl IntoDiagArg for String { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self)) - } -} - -impl<'a> IntoDiagArg for Cow<'a, str> { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.into_owned())) - } -} - -impl<'a> IntoDiagArg for &'a Path { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.display().to_string())) - } -} - -impl IntoDiagArg for PathBuf { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.display().to_string())) - } -} - -impl IntoDiagArg for PanicStrategy { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.desc().to_string())) - } -} - -impl IntoDiagArg for hir::ConstContext { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Borrowed(match self { - hir::ConstContext::ConstFn => "const_fn", - hir::ConstContext::Static(_) => "static", - hir::ConstContext::Const { .. } => "const", - })) - } -} - -impl IntoDiagArg for ast::Expr { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(pprust::expr_to_string(&self))) - } -} - -impl IntoDiagArg for ast::Path { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(pprust::path_to_string(&self))) - } -} - -impl IntoDiagArg for ast::token::Token { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(pprust::token_to_string(&self)) - } -} - -impl IntoDiagArg for ast::token::TokenKind { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(pprust::token_kind_to_string(&self)) - } -} - -impl IntoDiagArg for FloatTy { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Borrowed(self.name_str())) - } -} - -impl IntoDiagArg for std::ffi::CString { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned())) - } -} - -impl IntoDiagArg for rustc_data_structures::small_c_str::SmallCStr { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned())) - } -} - -impl IntoDiagArg for ast::Visibility { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - let s = pprust::vis_to_string(&self); - let s = s.trim_end().to_string(); - DiagArgValue::Str(Cow::Owned(s)) - } -} - -impl IntoDiagArg for rustc_lint_defs::Level { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Borrowed(self.to_cmd_flag())) - } -} - -impl IntoDiagArg for hir::def::Res { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Borrowed(self.descr())) - } -} - impl IntoDiagArg for DiagLocation { fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { DiagArgValue::Str(Cow::from(self.to_string())) } } -impl IntoDiagArg for Backtrace { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::from(self.to_string())) - } -} - -impl IntoDiagArg for Level { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::from(self.to_string())) - } -} - -impl IntoDiagArg for ClosureKind { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(self.as_str().into()) - } -} - -impl IntoDiagArg for hir::def::Namespace { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Str(Cow::Borrowed(self.descr())) - } -} - -impl IntoDiagArg for ExprPrecedence { - fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { - DiagArgValue::Number(self as i32) - } -} - #[derive(Clone)] pub struct DiagSymbolList(Vec); diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 84970e7c162b..749bba5de127 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -17,7 +17,7 @@ use std::path::Path; use std::sync::Arc; use derive_setters::Setters; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; use rustc_error_messages::{FluentArgs, SpanLabel}; use rustc_lexer; @@ -409,7 +409,7 @@ pub trait Emitter { if !redundant_span || always_backtrace { let msg: Cow<'static, _> = match trace.kind { ExpnKind::Macro(MacroKind::Attr, _) => { - "this procedural macro expansion".into() + "this attribute macro expansion".into() } ExpnKind::Macro(MacroKind::Derive, _) => { "this derive macro expansion".into() @@ -1853,7 +1853,7 @@ impl HumanEmitter { && line_idx + 1 == annotated_file.lines.len(), ); - let mut to_add = FxHashMap::default(); + let mut to_add = FxIndexMap::default(); for (depth, style) in depths { // FIXME(#120456) - is `swap_remove` correct? @@ -3546,7 +3546,7 @@ pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> Confu for (f, s) in iter::zip(found.chars(), suggested.chars()) { if f != s { - if f.to_lowercase().to_string() == s.to_lowercase().to_string() { + if f.eq_ignore_ascii_case(&s) { // Check for case differences (any character that differs only in case) if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) { has_case_diff = true; diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 2534cddf1057..a775b70dbee9 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -41,12 +41,11 @@ use std::{fmt, panic}; use Level::*; pub use codes::*; pub use diagnostic::{ - BugAbort, Diag, DiagArg, DiagArgMap, DiagArgName, DiagArgValue, DiagInner, DiagStyledString, - Diagnostic, EmissionGuarantee, FatalAbort, IntoDiagArg, LintDiagnostic, StringPart, Subdiag, - Subdiagnostic, + BugAbort, Diag, DiagArgMap, DiagInner, DiagStyledString, Diagnostic, EmissionGuarantee, + FatalAbort, LintDiagnostic, StringPart, Subdiag, Subdiagnostic, }; pub use diagnostic_impls::{ - DiagArgFromDisplay, DiagSymbolList, ElidedLifetimeInPathSubdiag, ExpectedLifetimeParameter, + DiagSymbolList, ElidedLifetimeInPathSubdiag, ExpectedLifetimeParameter, IndicateAnonymousLifetime, SingleLabelManySpans, }; pub use emitter::ColorConfig; @@ -56,11 +55,12 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::stable_hasher::StableHasher; use rustc_data_structures::sync::{DynSend, Lock}; pub use rustc_error_messages::{ - DiagMessage, FluentBundle, LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, - SubdiagMessage, fallback_fluent_bundle, fluent_bundle, + DiagArg, DiagArgFromDisplay, DiagArgName, DiagArgValue, DiagMessage, FluentBundle, IntoDiagArg, + LanguageIdentifier, LazyFallbackBundle, MultiSpan, SpanLabel, SubdiagMessage, + fallback_fluent_bundle, fluent_bundle, into_diag_arg_using_display, }; use rustc_hashes::Hash128; -use rustc_hir::HirId; +use rustc_hir_id::HirId; pub use rustc_lint_defs::{Applicability, listify, pluralize}; use rustc_lint_defs::{Lint, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; @@ -1999,6 +1999,12 @@ impl Level { } } +impl IntoDiagArg for Level { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::from(self.to_string())) + } +} + // FIXME(eddyb) this doesn't belong here AFAICT, should be moved to callsite. pub fn elided_lifetime_in_path_suggestion( source_map: &SourceMap, diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 5a53670c865d..61ba716d082b 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -70,6 +70,9 @@ expand_invalid_fragment_specifier = invalid fragment specifier `{$fragment}` .help = {$help} +expand_macro_args_bad_delim = `{$rule_kw}` rule argument matchers require parentheses +expand_macro_args_bad_delim_sugg = the delimiters should be `(` and `)` + expand_macro_body_stability = macros cannot have body stability attributes .label = invalid body stability attribute diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 1a9832b2fe26..f2c150715329 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -7,7 +7,6 @@ use std::rc::Rc; use std::sync::Arc; use rustc_ast::attr::{AttributeExt, MarkedAttrs}; -use rustc_ast::ptr::P; use rustc_ast::token::MetaVarKind; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; @@ -18,6 +17,7 @@ use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, PResult}; use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::attrs::{AttributeKind, CfgEntry, Deprecation}; +use rustc_hir::def::MacroKinds; use rustc_hir::{Stability, find_attr}; use rustc_lint_defs::{BufferedEarlyLint, RegisteredTools}; use rustc_parse::MACRO_ARGUMENTS; @@ -45,11 +45,11 @@ use crate::stats::MacroStat; // to use `assign_id!` #[derive(Debug, Clone)] pub enum Annotatable { - Item(P), - AssocItem(P, AssocCtxt), - ForeignItem(P), - Stmt(P), - Expr(P), + Item(Box), + AssocItem(Box, AssocCtxt), + ForeignItem(Box), + Stmt(Box), + Expr(Box), Arm(ast::Arm), ExprField(ast::ExprField), PatField(ast::PatField), @@ -141,28 +141,28 @@ impl Annotatable { } } - pub fn expect_item(self) -> P { + pub fn expect_item(self) -> Box { match self { Annotatable::Item(i) => i, _ => panic!("expected Item"), } } - pub fn expect_trait_item(self) -> P { + pub fn expect_trait_item(self) -> Box { match self { Annotatable::AssocItem(i, AssocCtxt::Trait) => i, _ => panic!("expected Item"), } } - pub fn expect_impl_item(self) -> P { + pub fn expect_impl_item(self) -> Box { match self { Annotatable::AssocItem(i, AssocCtxt::Impl { .. }) => i, _ => panic!("expected Item"), } } - pub fn expect_foreign_item(self) -> P { + pub fn expect_foreign_item(self) -> Box { match self { Annotatable::ForeignItem(i) => i, _ => panic!("expected foreign item"), @@ -176,7 +176,7 @@ impl Annotatable { } } - pub fn expect_expr(self) -> P { + pub fn expect_expr(self) -> Box { match self { Annotatable::Expr(expr) => expr, _ => panic!("expected expression"), @@ -412,37 +412,37 @@ macro_rules! make_stmts_default { /// methods are spliced into the AST at the callsite of the macro. pub trait MacResult { /// Creates an expression. - fn make_expr(self: Box) -> Option> { + fn make_expr(self: Box) -> Option> { None } /// Creates zero or more items. - fn make_items(self: Box) -> Option; 1]>> { + fn make_items(self: Box) -> Option; 1]>> { None } /// Creates zero or more impl items. - fn make_impl_items(self: Box) -> Option; 1]>> { + fn make_impl_items(self: Box) -> Option; 1]>> { None } /// Creates zero or more impl items. - fn make_trait_impl_items(self: Box) -> Option; 1]>> { + fn make_trait_impl_items(self: Box) -> Option; 1]>> { None } /// Creates zero or more trait items. - fn make_trait_items(self: Box) -> Option; 1]>> { + fn make_trait_items(self: Box) -> Option; 1]>> { None } /// Creates zero or more items in an `extern {}` block - fn make_foreign_items(self: Box) -> Option; 1]>> { + fn make_foreign_items(self: Box) -> Option; 1]>> { None } /// Creates a pattern. - fn make_pat(self: Box) -> Option> { + fn make_pat(self: Box) -> Option> { None } @@ -454,7 +454,7 @@ pub trait MacResult { make_stmts_default!(self) } - fn make_ty(self: Box) -> Option> { + fn make_ty(self: Box) -> Option> { None } @@ -521,38 +521,38 @@ macro_rules! make_MacEager { } make_MacEager! { - expr: P, - pat: P, - items: SmallVec<[P; 1]>, - impl_items: SmallVec<[P; 1]>, - trait_items: SmallVec<[P; 1]>, - foreign_items: SmallVec<[P; 1]>, + expr: Box, + pat: Box, + items: SmallVec<[Box; 1]>, + impl_items: SmallVec<[Box; 1]>, + trait_items: SmallVec<[Box; 1]>, + foreign_items: SmallVec<[Box; 1]>, stmts: SmallVec<[ast::Stmt; 1]>, - ty: P, + ty: Box, } impl MacResult for MacEager { - fn make_expr(self: Box) -> Option> { + fn make_expr(self: Box) -> Option> { self.expr } - fn make_items(self: Box) -> Option; 1]>> { + fn make_items(self: Box) -> Option; 1]>> { self.items } - fn make_impl_items(self: Box) -> Option; 1]>> { + fn make_impl_items(self: Box) -> Option; 1]>> { self.impl_items } - fn make_trait_impl_items(self: Box) -> Option; 1]>> { + fn make_trait_impl_items(self: Box) -> Option; 1]>> { self.impl_items } - fn make_trait_items(self: Box) -> Option; 1]>> { + fn make_trait_items(self: Box) -> Option; 1]>> { self.trait_items } - fn make_foreign_items(self: Box) -> Option; 1]>> { + fn make_foreign_items(self: Box) -> Option; 1]>> { self.foreign_items } @@ -563,13 +563,13 @@ impl MacResult for MacEager { } } - fn make_pat(self: Box) -> Option> { + fn make_pat(self: Box) -> Option> { if let Some(p) = self.pat { return Some(p); } if let Some(e) = self.expr { if matches!(e.kind, ast::ExprKind::Lit(_) | ast::ExprKind::IncludedBytes(_)) { - return Some(P(ast::Pat { + return Some(Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, span: e.span, kind: PatKind::Expr(e), @@ -580,7 +580,7 @@ impl MacResult for MacEager { None } - fn make_ty(self: Box) -> Option> { + fn make_ty(self: Box) -> Option> { self.ty } } @@ -608,8 +608,8 @@ impl DummyResult { } /// A plain dummy expression. - pub fn raw_expr(sp: Span, guar: Option) -> P { - P(ast::Expr { + pub fn raw_expr(sp: Span, guar: Option) -> Box { + Box::new(ast::Expr { id: ast::DUMMY_NODE_ID, kind: if let Some(guar) = guar { ast::ExprKind::Err(guar) @@ -624,12 +624,12 @@ impl DummyResult { } impl MacResult for DummyResult { - fn make_expr(self: Box) -> Option> { + fn make_expr(self: Box) -> Option> { Some(DummyResult::raw_expr(self.span, self.guar)) } - fn make_pat(self: Box) -> Option> { - Some(P(ast::Pat { + fn make_pat(self: Box) -> Option> { + Some(Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Wild, span: self.span, @@ -637,23 +637,23 @@ impl MacResult for DummyResult { })) } - fn make_items(self: Box) -> Option; 1]>> { + fn make_items(self: Box) -> Option; 1]>> { Some(SmallVec::new()) } - fn make_impl_items(self: Box) -> Option; 1]>> { + fn make_impl_items(self: Box) -> Option; 1]>> { Some(SmallVec::new()) } - fn make_trait_impl_items(self: Box) -> Option; 1]>> { + fn make_trait_impl_items(self: Box) -> Option; 1]>> { Some(SmallVec::new()) } - fn make_trait_items(self: Box) -> Option; 1]>> { + fn make_trait_items(self: Box) -> Option; 1]>> { Some(SmallVec::new()) } - fn make_foreign_items(self: Box) -> Option; 1]>> { + fn make_foreign_items(self: Box) -> Option; 1]>> { Some(SmallVec::new()) } @@ -665,11 +665,11 @@ impl MacResult for DummyResult { }]) } - fn make_ty(self: Box) -> Option> { + fn make_ty(self: Box) -> Option> { // FIXME(nnethercote): you might expect `ast::TyKind::Dummy` to be used here, but some // values produced here end up being lowered to HIR, which `ast::TyKind::Dummy` does not // support, so we use an empty tuple instead. - Some(P(ast::Ty { + Some(Box::new(ast::Ty { id: ast::DUMMY_NODE_ID, kind: ast::TyKind::Tup(ThinVec::new()), span: self.span, @@ -719,6 +719,9 @@ impl MacResult for DummyResult { /// A syntax extension kind. #[derive(Clone)] pub enum SyntaxExtensionKind { + /// A `macro_rules!` macro that can work as any `MacroKind` + MacroRules(Arc), + /// A token-based function-like macro. Bang( /// An expander with signature TokenStream -> TokenStream. @@ -773,9 +776,39 @@ pub enum SyntaxExtensionKind { ), /// A glob delegation. + /// + /// This is for delegated function implementations, and has nothing to do with glob imports. GlobDelegation(Arc), } +impl SyntaxExtensionKind { + /// Returns `Some(expander)` for a macro usable as a `LegacyBang`; otherwise returns `None` + /// + /// This includes a `MacroRules` with function-like rules. + pub fn as_legacy_bang(&self) -> Option<&(dyn TTMacroExpander + sync::DynSync + sync::DynSend)> { + match self { + SyntaxExtensionKind::LegacyBang(exp) => Some(exp.as_ref()), + SyntaxExtensionKind::MacroRules(exp) if exp.kinds().contains(MacroKinds::BANG) => { + Some(exp.as_ref()) + } + _ => None, + } + } + + /// Returns `Some(expander)` for a macro usable as an `Attr`; otherwise returns `None` + /// + /// This includes a `MacroRules` with `attr` rules. + pub fn as_attr(&self) -> Option<&(dyn AttrProcMacro + sync::DynSync + sync::DynSend)> { + match self { + SyntaxExtensionKind::Attr(exp) => Some(exp.as_ref()), + SyntaxExtensionKind::MacroRules(exp) if exp.kinds().contains(MacroKinds::ATTR) => { + Some(exp.as_ref()) + } + _ => None, + } + } +} + /// A struct representing a macro definition in "lowered" form ready for expansion. pub struct SyntaxExtension { /// A syntax extension kind. @@ -805,18 +838,19 @@ pub struct SyntaxExtension { } impl SyntaxExtension { - /// Returns which kind of macro calls this syntax extension. - pub fn macro_kind(&self) -> MacroKind { + /// Returns which kinds of macro call this syntax extension. + pub fn macro_kinds(&self) -> MacroKinds { match self.kind { SyntaxExtensionKind::Bang(..) | SyntaxExtensionKind::LegacyBang(..) - | SyntaxExtensionKind::GlobDelegation(..) => MacroKind::Bang, + | SyntaxExtensionKind::GlobDelegation(..) => MacroKinds::BANG, SyntaxExtensionKind::Attr(..) | SyntaxExtensionKind::LegacyAttr(..) - | SyntaxExtensionKind::NonMacroAttr => MacroKind::Attr, + | SyntaxExtensionKind::NonMacroAttr => MacroKinds::ATTR, SyntaxExtensionKind::Derive(..) | SyntaxExtensionKind::LegacyDerive(..) => { - MacroKind::Derive + MacroKinds::DERIVE } + SyntaxExtensionKind::MacroRules(ref m) => m.kinds(), } } @@ -905,10 +939,7 @@ impl SyntaxExtension { find_attr!(attrs, AttributeKind::AllowInternalUnstable(i, _) => i) .map(|i| i.as_slice()) .unwrap_or_default(); - // FIXME(jdonszelman): allow_internal_unsafe isn't yet new-style - // let allow_internal_unsafe = find_attr!(attrs, AttributeKind::AllowInternalUnsafe); - let allow_internal_unsafe = - ast::attr::find_by_name(attrs, sym::allow_internal_unsafe).is_some(); + let allow_internal_unsafe = find_attr!(attrs, AttributeKind::AllowInternalUnsafe(_)); let local_inner_macros = ast::attr::find_by_name(attrs, sym::macro_export) .and_then(|macro_export| macro_export.meta_item_list()) @@ -1028,11 +1059,12 @@ impl SyntaxExtension { parent: LocalExpnId, call_site: Span, descr: Symbol, + kind: MacroKind, macro_def_id: Option, parent_module: Option, ) -> ExpnData { ExpnData::new( - ExpnKind::Macro(self.macro_kind(), descr), + ExpnKind::Macro(kind, descr), parent.to_expn_id(), call_site, self.span, @@ -1162,7 +1194,7 @@ pub trait LintStoreExpand { registered_tools: &RegisteredTools, node_id: NodeId, attrs: &[Attribute], - items: &[P], + items: &[Box], name: Symbol, ); } diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 51d6e43ab672..c3e86ec0614c 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::literal; @@ -59,10 +58,10 @@ impl<'a> ExtCtxt<'a> { path: ast::Path, delim: Delimiter, tokens: TokenStream, - ) -> P { - P(ast::MacCall { + ) -> Box { + Box::new(ast::MacCall { path, - args: P(ast::DelimArgs { + args: Box::new(ast::DelimArgs { dspan: tokenstream::DelimSpan { open: span, close: span }, delim, tokens, @@ -70,32 +69,32 @@ impl<'a> ExtCtxt<'a> { }) } - pub fn ty_mt(&self, ty: P, mutbl: ast::Mutability) -> ast::MutTy { + pub fn ty_mt(&self, ty: Box, mutbl: ast::Mutability) -> ast::MutTy { ast::MutTy { ty, mutbl } } - pub fn ty(&self, span: Span, kind: ast::TyKind) -> P { - P(ast::Ty { id: ast::DUMMY_NODE_ID, span, kind, tokens: None }) + pub fn ty(&self, span: Span, kind: ast::TyKind) -> Box { + Box::new(ast::Ty { id: ast::DUMMY_NODE_ID, span, kind, tokens: None }) } - pub fn ty_infer(&self, span: Span) -> P { + pub fn ty_infer(&self, span: Span) -> Box { self.ty(span, ast::TyKind::Infer) } - pub fn ty_path(&self, path: ast::Path) -> P { + pub fn ty_path(&self, path: ast::Path) -> Box { self.ty(path.span, ast::TyKind::Path(None, path)) } // Might need to take bounds as an argument in the future, if you ever want // to generate a bounded existential trait type. - pub fn ty_ident(&self, span: Span, ident: Ident) -> P { + pub fn ty_ident(&self, span: Span, ident: Ident) -> Box { self.ty_path(self.path_ident(span, ident)) } pub fn anon_const(&self, span: Span, kind: ast::ExprKind) -> ast::AnonConst { ast::AnonConst { id: ast::DUMMY_NODE_ID, - value: P(ast::Expr { + value: Box::new(ast::Expr { id: ast::DUMMY_NODE_ID, kind, span, @@ -112,14 +111,14 @@ impl<'a> ExtCtxt<'a> { pub fn ty_ref( &self, span: Span, - ty: P, + ty: Box, lifetime: Option, mutbl: ast::Mutability, - ) -> P { + ) -> Box { self.ty(span, ast::TyKind::Ref(lifetime, self.ty_mt(ty, mutbl))) } - pub fn ty_ptr(&self, span: Span, ty: P, mutbl: ast::Mutability) -> P { + pub fn ty_ptr(&self, span: Span, ty: Box, mutbl: ast::Mutability) -> Box { self.ty(span, ast::TyKind::Ptr(self.ty_mt(ty, mutbl))) } @@ -128,7 +127,7 @@ impl<'a> ExtCtxt<'a> { span: Span, ident: Ident, bounds: ast::GenericBounds, - default: Option>, + default: Option>, ) -> ast::GenericParam { ast::GenericParam { ident: ident.with_span_pos(span), @@ -163,7 +162,7 @@ impl<'a> ExtCtxt<'a> { span: Span, ident: Ident, bounds: ast::GenericBounds, - ty: P, + ty: Box, default: Option, ) -> ast::GenericParam { ast::GenericParam { @@ -211,11 +210,11 @@ impl<'a> ExtCtxt<'a> { self.lifetime(span, Ident::new(kw::StaticLifetime, span)) } - pub fn stmt_expr(&self, expr: P) -> ast::Stmt { + pub fn stmt_expr(&self, expr: Box) -> ast::Stmt { ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) } } - pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: P) -> ast::Stmt { + pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: Box) -> ast::Stmt { self.stmt_let_ty(sp, mutbl, ident, None, ex) } @@ -224,15 +223,15 @@ impl<'a> ExtCtxt<'a> { sp: Span, mutbl: bool, ident: Ident, - ty: Option>, - ex: P, + ty: Option>, + ex: Box, ) -> ast::Stmt { let pat = if mutbl { self.pat_ident_binding_mode(sp, ident, ast::BindingMode::MUT) } else { self.pat_ident(sp, ident) }; - let local = P(ast::Local { + let local = Box::new(ast::Local { super_: None, pat, ty, @@ -247,8 +246,8 @@ impl<'a> ExtCtxt<'a> { } /// Generates `let _: Type;`, which is usually used for type assertions. - pub fn stmt_let_type_only(&self, span: Span, ty: P) -> ast::Stmt { - let local = P(ast::Local { + pub fn stmt_let_type_only(&self, span: Span, ty: Box) -> ast::Stmt { + let local = Box::new(ast::Local { super_: None, pat: self.pat_wild(span), ty: Some(ty), @@ -262,19 +261,19 @@ impl<'a> ExtCtxt<'a> { self.stmt_local(local, span) } - pub fn stmt_semi(&self, expr: P) -> ast::Stmt { + pub fn stmt_semi(&self, expr: Box) -> ast::Stmt { ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Semi(expr) } } - pub fn stmt_local(&self, local: P, span: Span) -> ast::Stmt { + pub fn stmt_local(&self, local: Box, span: Span) -> ast::Stmt { ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Let(local), span } } - pub fn stmt_item(&self, sp: Span, item: P) -> ast::Stmt { + pub fn stmt_item(&self, sp: Span, item: Box) -> ast::Stmt { ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Item(item), span: sp } } - pub fn block_expr(&self, expr: P) -> P { + pub fn block_expr(&self, expr: Box) -> Box { self.block( expr.span, thin_vec![ast::Stmt { @@ -284,8 +283,8 @@ impl<'a> ExtCtxt<'a> { }], ) } - pub fn block(&self, span: Span, stmts: ThinVec) -> P { - P(ast::Block { + pub fn block(&self, span: Span, stmts: ThinVec) -> Box { + Box::new(ast::Block { stmts, id: ast::DUMMY_NODE_ID, rules: BlockCheckMode::Default, @@ -294,22 +293,28 @@ impl<'a> ExtCtxt<'a> { }) } - pub fn expr(&self, span: Span, kind: ast::ExprKind) -> P { - P(ast::Expr { id: ast::DUMMY_NODE_ID, kind, span, attrs: AttrVec::new(), tokens: None }) + pub fn expr(&self, span: Span, kind: ast::ExprKind) -> Box { + Box::new(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind, + span, + attrs: AttrVec::new(), + tokens: None, + }) } - pub fn expr_path(&self, path: ast::Path) -> P { + pub fn expr_path(&self, path: ast::Path) -> Box { self.expr(path.span, ast::ExprKind::Path(None, path)) } - pub fn expr_ident(&self, span: Span, id: Ident) -> P { + pub fn expr_ident(&self, span: Span, id: Ident) -> Box { self.expr_path(self.path_ident(span, id)) } - pub fn expr_self(&self, span: Span) -> P { + pub fn expr_self(&self, span: Span) -> Box { self.expr_ident(span, Ident::with_dummy_span(kw::SelfLower)) } - pub fn expr_macro_call(&self, span: Span, call: P) -> P { + pub fn expr_macro_call(&self, span: Span, call: Box) -> Box { self.expr(span, ast::ExprKind::MacCall(call)) } @@ -317,31 +322,31 @@ impl<'a> ExtCtxt<'a> { &self, sp: Span, op: ast::BinOpKind, - lhs: P, - rhs: P, - ) -> P { + lhs: Box, + rhs: Box, + ) -> Box { self.expr(sp, ast::ExprKind::Binary(Spanned { node: op, span: sp }, lhs, rhs)) } - pub fn expr_deref(&self, sp: Span, e: P) -> P { + pub fn expr_deref(&self, sp: Span, e: Box) -> Box { self.expr(sp, ast::ExprKind::Unary(UnOp::Deref, e)) } - pub fn expr_addr_of(&self, sp: Span, e: P) -> P { + pub fn expr_addr_of(&self, sp: Span, e: Box) -> Box { self.expr(sp, ast::ExprKind::AddrOf(ast::BorrowKind::Ref, ast::Mutability::Not, e)) } - pub fn expr_paren(&self, sp: Span, e: P) -> P { + pub fn expr_paren(&self, sp: Span, e: Box) -> Box { self.expr(sp, ast::ExprKind::Paren(e)) } pub fn expr_method_call( &self, span: Span, - expr: P, + expr: Box, ident: Ident, - args: ThinVec>, - ) -> P { + args: ThinVec>, + ) -> Box { let seg = ast::PathSegment::from_ident(ident); self.expr( span, @@ -357,38 +362,38 @@ impl<'a> ExtCtxt<'a> { pub fn expr_call( &self, span: Span, - expr: P, - args: ThinVec>, - ) -> P { + expr: Box, + args: ThinVec>, + ) -> Box { self.expr(span, ast::ExprKind::Call(expr, args)) } - pub fn expr_loop(&self, sp: Span, block: P) -> P { + pub fn expr_loop(&self, sp: Span, block: Box) -> Box { self.expr(sp, ast::ExprKind::Loop(block, None, sp)) } - pub fn expr_asm(&self, sp: Span, expr: P) -> P { + pub fn expr_asm(&self, sp: Span, expr: Box) -> Box { self.expr(sp, ast::ExprKind::InlineAsm(expr)) } pub fn expr_call_ident( &self, span: Span, id: Ident, - args: ThinVec>, - ) -> P { + args: ThinVec>, + ) -> Box { self.expr(span, ast::ExprKind::Call(self.expr_ident(span, id), args)) } pub fn expr_call_global( &self, sp: Span, fn_path: Vec, - args: ThinVec>, - ) -> P { + args: ThinVec>, + ) -> Box { let pathexpr = self.expr_path(self.path_global(sp, fn_path)); self.expr_call(sp, pathexpr, args) } - pub fn expr_block(&self, b: P) -> P { + pub fn expr_block(&self, b: Box) -> Box { self.expr(b.span, ast::ExprKind::Block(b, None)) } - pub fn field_imm(&self, span: Span, ident: Ident, e: P) -> ast::ExprField { + pub fn field_imm(&self, span: Span, ident: Ident, e: Box) -> ast::ExprField { ast::ExprField { ident: ident.with_span_pos(span), expr: e, @@ -404,10 +409,10 @@ impl<'a> ExtCtxt<'a> { span: Span, path: ast::Path, fields: ThinVec, - ) -> P { + ) -> Box { self.expr( span, - ast::ExprKind::Struct(P(ast::StructExpr { + ast::ExprKind::Struct(Box::new(ast::StructExpr { qself: None, path, fields, @@ -420,61 +425,61 @@ impl<'a> ExtCtxt<'a> { span: Span, id: Ident, fields: ThinVec, - ) -> P { + ) -> Box { self.expr_struct(span, self.path_ident(span, id), fields) } - pub fn expr_usize(&self, span: Span, n: usize) -> P { + pub fn expr_usize(&self, span: Span, n: usize) -> Box { let suffix = Some(ast::UintTy::Usize.name()); let lit = token::Lit::new(token::Integer, sym::integer(n), suffix); self.expr(span, ast::ExprKind::Lit(lit)) } - pub fn expr_u32(&self, span: Span, n: u32) -> P { + pub fn expr_u32(&self, span: Span, n: u32) -> Box { let suffix = Some(ast::UintTy::U32.name()); let lit = token::Lit::new(token::Integer, sym::integer(n), suffix); self.expr(span, ast::ExprKind::Lit(lit)) } - pub fn expr_bool(&self, span: Span, value: bool) -> P { + pub fn expr_bool(&self, span: Span, value: bool) -> Box { let lit = token::Lit::new(token::Bool, if value { kw::True } else { kw::False }, None); self.expr(span, ast::ExprKind::Lit(lit)) } - pub fn expr_str(&self, span: Span, s: Symbol) -> P { + pub fn expr_str(&self, span: Span, s: Symbol) -> Box { let lit = token::Lit::new(token::Str, literal::escape_string_symbol(s), None); self.expr(span, ast::ExprKind::Lit(lit)) } - pub fn expr_byte_str(&self, span: Span, bytes: Vec) -> P { + pub fn expr_byte_str(&self, span: Span, bytes: Vec) -> Box { let lit = token::Lit::new(token::ByteStr, literal::escape_byte_str_symbol(&bytes), None); self.expr(span, ast::ExprKind::Lit(lit)) } /// `[expr1, expr2, ...]` - pub fn expr_array(&self, sp: Span, exprs: ThinVec>) -> P { + pub fn expr_array(&self, sp: Span, exprs: ThinVec>) -> Box { self.expr(sp, ast::ExprKind::Array(exprs)) } /// `&[expr1, expr2, ...]` - pub fn expr_array_ref(&self, sp: Span, exprs: ThinVec>) -> P { + pub fn expr_array_ref(&self, sp: Span, exprs: ThinVec>) -> Box { self.expr_addr_of(sp, self.expr_array(sp, exprs)) } - pub fn expr_some(&self, sp: Span, expr: P) -> P { + pub fn expr_some(&self, sp: Span, expr: Box) -> Box { let some = self.std_path(&[sym::option, sym::Option, sym::Some]); self.expr_call_global(sp, some, thin_vec![expr]) } - pub fn expr_none(&self, sp: Span) -> P { + pub fn expr_none(&self, sp: Span) -> Box { let none = self.std_path(&[sym::option, sym::Option, sym::None]); self.expr_path(self.path_global(sp, none)) } - pub fn expr_tuple(&self, sp: Span, exprs: ThinVec>) -> P { + pub fn expr_tuple(&self, sp: Span, exprs: ThinVec>) -> Box { self.expr(sp, ast::ExprKind::Tup(exprs)) } - pub fn expr_unreachable(&self, span: Span) -> P { + pub fn expr_unreachable(&self, span: Span) -> Box { self.expr_macro_call( span, self.macro_call( @@ -489,12 +494,12 @@ impl<'a> ExtCtxt<'a> { ) } - pub fn expr_ok(&self, sp: Span, expr: P) -> P { + pub fn expr_ok(&self, sp: Span, expr: Box) -> Box { let ok = self.std_path(&[sym::result, sym::Result, sym::Ok]); self.expr_call_global(sp, ok, thin_vec![expr]) } - pub fn expr_try(&self, sp: Span, head: P) -> P { + pub fn expr_try(&self, sp: Span, head: Box) -> Box { let ok = self.std_path(&[sym::result, sym::Result, sym::Ok]); let ok_path = self.path_global(sp, ok); let err = self.std_path(&[sym::result, sym::Result, sym::Err]); @@ -523,16 +528,16 @@ impl<'a> ExtCtxt<'a> { self.expr_match(sp, head, thin_vec![ok_arm, err_arm]) } - pub fn pat(&self, span: Span, kind: PatKind) -> P { - P(ast::Pat { id: ast::DUMMY_NODE_ID, kind, span, tokens: None }) + pub fn pat(&self, span: Span, kind: PatKind) -> Box { + Box::new(ast::Pat { id: ast::DUMMY_NODE_ID, kind, span, tokens: None }) } - pub fn pat_wild(&self, span: Span) -> P { + pub fn pat_wild(&self, span: Span) -> Box { self.pat(span, PatKind::Wild) } - pub fn pat_lit(&self, span: Span, expr: P) -> P { + pub fn pat_lit(&self, span: Span, expr: Box) -> Box { self.pat(span, PatKind::Expr(expr)) } - pub fn pat_ident(&self, span: Span, ident: Ident) -> P { + pub fn pat_ident(&self, span: Span, ident: Ident) -> Box { self.pat_ident_binding_mode(span, ident, ast::BindingMode::NONE) } @@ -541,19 +546,19 @@ impl<'a> ExtCtxt<'a> { span: Span, ident: Ident, ann: ast::BindingMode, - ) -> P { + ) -> Box { let pat = PatKind::Ident(ann, ident.with_span_pos(span), None); self.pat(span, pat) } - pub fn pat_path(&self, span: Span, path: ast::Path) -> P { + pub fn pat_path(&self, span: Span, path: ast::Path) -> Box { self.pat(span, PatKind::Path(None, path)) } pub fn pat_tuple_struct( &self, span: Span, path: ast::Path, - subpats: ThinVec>, - ) -> P { + subpats: ThinVec>, + ) -> Box { self.pat(span, PatKind::TupleStruct(None, path, subpats)) } pub fn pat_struct( @@ -561,20 +566,20 @@ impl<'a> ExtCtxt<'a> { span: Span, path: ast::Path, field_pats: ThinVec, - ) -> P { + ) -> Box { self.pat(span, PatKind::Struct(None, path, field_pats, ast::PatFieldsRest::None)) } - pub fn pat_tuple(&self, span: Span, pats: ThinVec>) -> P { + pub fn pat_tuple(&self, span: Span, pats: ThinVec>) -> Box { self.pat(span, PatKind::Tuple(pats)) } - pub fn pat_some(&self, span: Span, pat: P) -> P { + pub fn pat_some(&self, span: Span, pat: Box) -> Box { let some = self.std_path(&[sym::option, sym::Option, sym::Some]); let path = self.path_global(span, some); self.pat_tuple_struct(span, path, thin_vec![pat]) } - pub fn arm(&self, span: Span, pat: P, expr: P) -> ast::Arm { + pub fn arm(&self, span: Span, pat: Box, expr: Box) -> ast::Arm { ast::Arm { attrs: AttrVec::new(), pat, @@ -590,22 +595,27 @@ impl<'a> ExtCtxt<'a> { self.arm(span, self.pat_wild(span), self.expr_unreachable(span)) } - pub fn expr_match(&self, span: Span, arg: P, arms: ThinVec) -> P { + pub fn expr_match( + &self, + span: Span, + arg: Box, + arms: ThinVec, + ) -> Box { self.expr(span, ast::ExprKind::Match(arg, arms, MatchKind::Prefix)) } pub fn expr_if( &self, span: Span, - cond: P, - then: P, - els: Option>, - ) -> P { + cond: Box, + then: Box, + els: Option>, + ) -> Box { let els = els.map(|x| self.expr_block(self.block_expr(x))); self.expr(span, ast::ExprKind::If(cond, self.block_expr(then), els)) } - pub fn lambda(&self, span: Span, ids: Vec, body: P) -> P { + pub fn lambda(&self, span: Span, ids: Vec, body: Box) -> Box { let fn_decl = self.fn_decl( ids.iter().map(|id| self.param(span, *id, self.ty(span, ast::TyKind::Infer))).collect(), ast::FnRetTy::Default(span), @@ -633,11 +643,11 @@ impl<'a> ExtCtxt<'a> { ) } - pub fn lambda0(&self, span: Span, body: P) -> P { + pub fn lambda0(&self, span: Span, body: Box) -> Box { self.lambda(span, Vec::new(), body) } - pub fn lambda1(&self, span: Span, body: P, ident: Ident) -> P { + pub fn lambda1(&self, span: Span, body: Box, ident: Ident) -> Box { self.lambda(span, vec![ident], body) } @@ -646,11 +656,11 @@ impl<'a> ExtCtxt<'a> { span: Span, stmts: ThinVec, ident: Ident, - ) -> P { + ) -> Box { self.lambda1(span, self.expr_block(self.block(span, stmts)), ident) } - pub fn param(&self, span: Span, ident: Ident, ty: P) -> ast::Param { + pub fn param(&self, span: Span, ident: Ident, ty: Box) -> ast::Param { let arg_pat = self.pat_ident(span, ident); ast::Param { attrs: AttrVec::default(), @@ -663,12 +673,12 @@ impl<'a> ExtCtxt<'a> { } // `self` is unused but keep it as method for the convenience use. - pub fn fn_decl(&self, inputs: ThinVec, output: ast::FnRetTy) -> P { - P(ast::FnDecl { inputs, output }) + pub fn fn_decl(&self, inputs: ThinVec, output: ast::FnRetTy) -> Box { + Box::new(ast::FnDecl { inputs, output }) } - pub fn item(&self, span: Span, attrs: ast::AttrVec, kind: ast::ItemKind) -> P { - P(ast::Item { + pub fn item(&self, span: Span, attrs: ast::AttrVec, kind: ast::ItemKind) -> Box { + Box::new(ast::Item { attrs, id: ast::DUMMY_NODE_ID, kind, @@ -686,10 +696,10 @@ impl<'a> ExtCtxt<'a> { &self, span: Span, ident: Ident, - ty: P, + ty: Box, mutability: ast::Mutability, - expr: P, - ) -> P { + expr: Box, + ) -> Box { self.item( span, AttrVec::new(), @@ -711,9 +721,9 @@ impl<'a> ExtCtxt<'a> { &self, span: Span, ident: Ident, - ty: P, - expr: P, - ) -> P { + ty: Box, + expr: Box, + ) -> Box { let defaultness = ast::Defaultness::Final; self.item( span, diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index fd1391a554a8..ba9d76970f0c 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -482,3 +482,22 @@ mod metavar_exprs { pub key: MacroRulesNormalizedIdent, } } + +#[derive(Diagnostic)] +#[diag(expand_macro_args_bad_delim)] +pub(crate) struct MacroArgsBadDelim { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sugg: MacroArgsBadDelimSugg, + pub rule_kw: Symbol, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(expand_macro_args_bad_delim_sugg, applicability = "machine-applicable")] +pub(crate) struct MacroArgsBadDelimSugg { + #[suggestion_part(code = "(")] + pub open: Span, + #[suggestion_part(code = ")")] + pub close: Span, +} diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index f02aa6c120f9..7fc6ea883d03 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::{iter, mem}; use rustc_ast::mut_visit::*; -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{self, AssocCtxt, Visitor, VisitorResult, try_visit, walk_list}; use rustc_ast::{ @@ -15,8 +14,10 @@ use rustc_ast::{ use rustc_ast_pretty::pprust; use rustc_attr_parsing::{EvalConfigResult, ShouldEmit}; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; +use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::PResult; use rustc_feature::Features; +use rustc_hir::def::MacroKinds; use rustc_parse::parser::{ AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma, token_descr, @@ -65,8 +66,8 @@ macro_rules! ast_fragments { /// A fragment of AST that can be produced by a single macro expansion. /// Can also serve as an input and intermediate result for macro expansion operations. pub enum AstFragment { - OptExpr(Option>), - MethodReceiverExpr(P), + OptExpr(Option>), + MethodReceiverExpr(Box), $($Kind($AstTy),)* } @@ -112,14 +113,14 @@ macro_rules! ast_fragments { } } - pub(crate) fn make_opt_expr(self) -> Option> { + pub(crate) fn make_opt_expr(self) -> Option> { match self { AstFragment::OptExpr(expr) => expr, _ => panic!("AstFragment::make_* called on the wrong kind of fragment"), } } - pub(crate) fn make_method_receiver_expr(self) -> P { + pub(crate) fn make_method_receiver_expr(self) -> Box { match self { AstFragment::MethodReceiverExpr(expr) => expr, _ => panic!("AstFragment::make_* called on the wrong kind of fragment"), @@ -188,17 +189,17 @@ macro_rules! ast_fragments { } ast_fragments! { - Expr(P) { + Expr(Box) { "expression"; one fn visit_expr; fn visit_expr; fn pprust::expr_to_string; fn make_expr; } - Pat(P) { + Pat(Box) { "pattern"; one fn visit_pat; fn visit_pat; fn pprust::pat_to_string; fn make_pat; } - Ty(P) { + Ty(Box) { "type"; one fn visit_ty; fn visit_ty; fn pprust::ty_to_string; fn make_ty; @@ -208,30 +209,30 @@ ast_fragments! { many fn flat_map_stmt; fn visit_stmt(); fn pprust::stmt_to_string; fn make_stmts; } - Items(SmallVec<[P; 1]>) { + Items(SmallVec<[Box; 1]>) { "item"; many fn flat_map_item; fn visit_item(); fn pprust::item_to_string; fn make_items; } - TraitItems(SmallVec<[P; 1]>) { + TraitItems(SmallVec<[Box; 1]>) { "trait item"; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Trait); fn pprust::assoc_item_to_string; fn make_trait_items; } - ImplItems(SmallVec<[P; 1]>) { + ImplItems(SmallVec<[Box; 1]>) { "impl item"; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Impl { of_trait: false }); fn pprust::assoc_item_to_string; fn make_impl_items; } - TraitImplItems(SmallVec<[P; 1]>) { + TraitImplItems(SmallVec<[Box; 1]>) { "impl item"; many fn flat_map_assoc_item; fn visit_assoc_item(AssocCtxt::Impl { of_trait: true }); fn pprust::assoc_item_to_string; fn make_trait_impl_items; } - ForeignItems(SmallVec<[P; 1]>) { + ForeignItems(SmallVec<[Box; 1]>) { "foreign item"; many fn flat_map_foreign_item; fn visit_foreign_item(); fn pprust::foreign_item_to_string; fn make_foreign_items; @@ -392,7 +393,7 @@ pub struct Invocation { pub enum InvocationKind { Bang { - mac: P, + mac: Box, span: Span, }, Attr { @@ -409,7 +410,7 @@ pub enum InvocationKind { item: Annotatable, }, GlobDelegation { - item: P, + item: Box, /// Whether this is a trait impl or an inherent impl of_trait: bool, }, @@ -566,6 +567,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { .map(|DeriveResolution { path, item, exts: _, is_const }| { // FIXME: Consider using the derive resolutions (`_exts`) // instead of enqueuing the derives to be resolved again later. + // Note that this can result in duplicate diagnostics. let expn_id = LocalExpnId::fresh_empty(); derive_invocations.push(( Invocation { @@ -737,8 +739,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { let (fragment_kind, span) = (invoc.fragment_kind, invoc.span()); ExpandResult::Ready(match invoc.kind { - InvocationKind::Bang { mac, span } => match ext { - SyntaxExtensionKind::Bang(expander) => { + InvocationKind::Bang { mac, span } => { + if let SyntaxExtensionKind::Bang(expander) = ext { match expander.expand(self.cx, span, mac.args.tokens.clone()) { Ok(tok_result) => { let fragment = @@ -756,8 +758,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } - } - SyntaxExtensionKind::LegacyBang(expander) => { + } else if let Some(expander) = ext.as_legacy_bang() { let tok_result = match expander.expand(self.cx, span, mac.args.tokens.clone()) { ExpandResult::Ready(tok_result) => tok_result, ExpandResult::Retry(_) => { @@ -777,11 +778,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { let guar = self.error_wrong_fragment_kind(fragment_kind, &mac, span); fragment_kind.dummy(span, guar) } + } else { + unreachable!(); } - _ => unreachable!(), - }, - InvocationKind::Attr { attr, pos, mut item, derives } => match ext { - SyntaxExtensionKind::Attr(expander) => { + } + InvocationKind::Attr { attr, pos, mut item, derives } => { + if let Some(expander) = ext.as_attr() { self.gate_proc_macro_input(&item); self.gate_proc_macro_attr_item(span, &item); let tokens = match &item { @@ -800,7 +802,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ItemKind::Mod( _, _, - ModKind::Unloaded | ModKind::Loaded(_, Inline::No, _, _), + ModKind::Unloaded + | ModKind::Loaded(_, Inline::No { .. }, _), ) ) => { @@ -836,8 +839,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } - } - SyntaxExtensionKind::LegacyAttr(expander) => { + } else if let SyntaxExtensionKind::LegacyAttr(expander) = ext { match validate_attr::parse_meta(&self.cx.sess.psess, &attr) { Ok(meta) => { let item_clone = macro_stats.then(|| item.clone()); @@ -879,15 +881,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fragment_kind.expect_from_annotatables(iter::once(item)) } } - } - SyntaxExtensionKind::NonMacroAttr => { + } else if let SyntaxExtensionKind::NonMacroAttr = ext { // `-Zmacro-stats` ignores these because they don't do any real expansion. self.cx.expanded_inert_attrs.mark(&attr); item.visit_attrs(|attrs| attrs.insert(pos, attr)); fragment_kind.expect_from_annotatables(iter::once(item)) + } else { + unreachable!(); } - _ => unreachable!(), - }, + } InvocationKind::Derive { path, item, is_const } => match ext { SyntaxExtensionKind::Derive(expander) | SyntaxExtensionKind::LegacyDerive(expander) => { @@ -924,6 +926,35 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } fragment } + SyntaxExtensionKind::MacroRules(expander) + if expander.kinds().contains(MacroKinds::DERIVE) => + { + if is_const { + let guar = self + .cx + .dcx() + .span_err(span, "macro `derive` does not support const derives"); + return ExpandResult::Ready(fragment_kind.dummy(span, guar)); + } + let body = item.to_tokens(); + match expander.expand_derive(self.cx, span, &body) { + Ok(tok_result) => { + let fragment = + self.parse_ast_fragment(tok_result, fragment_kind, &path, span); + if macro_stats { + update_derive_macro_stats( + self.cx, + fragment_kind, + span, + &path, + &fragment, + ); + } + fragment + } + Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), + } + } _ => unreachable!(), }, InvocationKind::GlobDelegation { item, of_trait } => { @@ -948,15 +979,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> { _ => unreachable!(), }; - type Node = AstNodeWrapper, ImplItemTag>; + type Node = AstNodeWrapper, ImplItemTag>; let single_delegations = build_single_delegations::( self.cx, deleg, &item, &suffixes, item.span, true, ); // `-Zmacro-stats` ignores these because they don't seem important. - fragment_kind.expect_from_annotatables( - single_delegations - .map(|item| Annotatable::AssocItem(P(item), AssocCtxt::Impl { of_trait })), - ) + fragment_kind.expect_from_annotatables(single_delegations.map(|item| { + Annotatable::AssocItem(Box::new(item), AssocCtxt::Impl { of_trait }) + })) } }) } @@ -1007,7 +1037,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fn visit_item(&mut self, item: &'ast ast::Item) { match &item.kind { ItemKind::Mod(_, _, mod_kind) - if !matches!(mod_kind, ModKind::Loaded(_, Inline::Yes, _, _)) => + if !matches!(mod_kind, ModKind::Loaded(_, Inline::Yes, _)) => { feature_err( self.sess, @@ -1228,7 +1258,7 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { fn is_mac_call(&self) -> bool { false } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { unreachable!() } fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { @@ -1269,7 +1299,7 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { } } -impl InvocationCollectorNode for P { +impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::Items; fn to_annotatable(self) -> Annotatable { Annotatable::Item(self) @@ -1283,7 +1313,7 @@ impl InvocationCollectorNode for P { fn is_mac_call(&self) -> bool { matches!(self.kind, ItemKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { match self.kind { ItemKind::MacCall(mac) => (mac, self.attrs, AddSemicolon::No), _ => unreachable!(), @@ -1299,7 +1329,7 @@ impl InvocationCollectorNode for P { ItemKind::Delegation(deleg) } fn from_item(item: ast::Item) -> Self { - P(item) + Box::new(item) } fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() @@ -1318,7 +1348,7 @@ impl InvocationCollectorNode for P { let ItemKind::Mod(_, ident, ref mut mod_kind) = node.kind else { unreachable!() }; let ecx = &mut collector.cx; let (file_path, dir_path, dir_ownership) = match mod_kind { - ModKind::Loaded(_, inline, _, _) => { + ModKind::Loaded(_, inline, _) => { // Inline `mod foo { ... }`, but we still need to push directories. let (dir_path, dir_ownership) = mod_dir_path( ecx.sess, @@ -1332,7 +1362,7 @@ impl InvocationCollectorNode for P { // This lets `parse_external_mod` catch cycles if it's self-referential. let file_path = match inline { Inline::Yes => None, - Inline::No => mod_file_path_from_attr(ecx.sess, &attrs, &dir_path), + Inline::No { .. } => mod_file_path_from_attr(ecx.sess, &attrs, &dir_path), }; node.attrs = attrs; (file_path, dir_path, dir_ownership) @@ -1368,7 +1398,7 @@ impl InvocationCollectorNode for P { ); } - *mod_kind = ModKind::Loaded(items, Inline::No, spans, had_parse_error); + *mod_kind = ModKind::Loaded(items, Inline::No { had_parse_error }, spans); node.attrs = attrs; if node.attrs.len() > old_attrs_len { // If we loaded an out-of-line module and added some inner attributes, @@ -1421,8 +1451,8 @@ impl InvocationCollectorNode for P { } struct TraitItemTag; -impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> { - type OutputTy = SmallVec<[P; 1]>; +impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> { + type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; const KIND: AstFragmentKind = AstFragmentKind::TraitItems; fn to_annotatable(self) -> Annotatable { @@ -1437,7 +1467,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, AssocItemKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { let item = self.wrapped; match item.kind { AssocItemKind::MacCall(mac) => (mac, item.attrs, AddSemicolon::No), @@ -1454,7 +1484,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> AssocItemKind::Delegation(deleg) } fn from_item(item: ast::Item) -> Self { - AstNodeWrapper::new(P(item), TraitItemTag) + AstNodeWrapper::new(Box::new(item), TraitItemTag) } fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() @@ -1462,8 +1492,8 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> } struct ImplItemTag; -impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> { - type OutputTy = SmallVec<[P; 1]>; +impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> { + type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; const KIND: AstFragmentKind = AstFragmentKind::ImplItems; fn to_annotatable(self) -> Annotatable { @@ -1478,7 +1508,7 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, AssocItemKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { let item = self.wrapped; match item.kind { AssocItemKind::MacCall(mac) => (mac, item.attrs, AddSemicolon::No), @@ -1495,7 +1525,7 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> AssocItemKind::Delegation(deleg) } fn from_item(item: ast::Item) -> Self { - AstNodeWrapper::new(P(item), ImplItemTag) + AstNodeWrapper::new(Box::new(item), ImplItemTag) } fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() @@ -1503,8 +1533,8 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> } struct TraitImplItemTag; -impl InvocationCollectorNode for AstNodeWrapper, TraitImplItemTag> { - type OutputTy = SmallVec<[P; 1]>; +impl InvocationCollectorNode for AstNodeWrapper, TraitImplItemTag> { + type OutputTy = SmallVec<[Box; 1]>; type ItemKind = AssocItemKind; const KIND: AstFragmentKind = AstFragmentKind::TraitImplItems; fn to_annotatable(self) -> Annotatable { @@ -1519,7 +1549,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplItem fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, AssocItemKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { let item = self.wrapped; match item.kind { AssocItemKind::MacCall(mac) => (mac, item.attrs, AddSemicolon::No), @@ -1536,14 +1566,14 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitImplItem AssocItemKind::Delegation(deleg) } fn from_item(item: ast::Item) -> Self { - AstNodeWrapper::new(P(item), TraitImplItemTag) + AstNodeWrapper::new(Box::new(item), TraitImplItemTag) } fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() } } -impl InvocationCollectorNode for P { +impl InvocationCollectorNode for Box { const KIND: AstFragmentKind = AstFragmentKind::ForeignItems; fn to_annotatable(self) -> Annotatable { Annotatable::ForeignItem(self) @@ -1557,7 +1587,7 @@ impl InvocationCollectorNode for P { fn is_mac_call(&self) -> bool { matches!(self.kind, ForeignItemKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { match self.kind { ForeignItemKind::MacCall(mac) => (mac, self.attrs, AddSemicolon::No), _ => unreachable!(), @@ -1672,7 +1702,7 @@ impl InvocationCollectorNode for ast::Arm { impl InvocationCollectorNode for ast::Stmt { const KIND: AstFragmentKind = AstFragmentKind::Stmts; fn to_annotatable(self) -> Annotatable { - Annotatable::Stmt(P(self)) + Annotatable::Stmt(Box::new(self)) } fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy { fragment.make_stmts() @@ -1689,7 +1719,7 @@ impl InvocationCollectorNode for ast::Stmt { StmtKind::Let(..) | StmtKind::Empty => false, } } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { // We pull macro invocations (both attributes and fn-like macro calls) out of their // `StmtKind`s and treat them as statement macro invocations, not as items or expressions. let (add_semicolon, mac, attrs) = match self.kind { @@ -1726,7 +1756,7 @@ impl InvocationCollectorNode for ast::Stmt { ItemKind::Delegation(deleg) } fn from_item(item: ast::Item) -> Self { - ast::Stmt { id: ast::DUMMY_NODE_ID, span: item.span, kind: StmtKind::Item(P(item)) } + ast::Stmt { id: ast::DUMMY_NODE_ID, span: item.span, kind: StmtKind::Item(Box::new(item)) } } fn flatten_outputs(items: impl Iterator) -> Self::OutputTy { items.flatten().collect() @@ -1769,7 +1799,7 @@ impl InvocationCollectorNode for ast::Crate { } impl InvocationCollectorNode for ast::Ty { - type OutputTy = P; + type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Ty; fn to_annotatable(self) -> Annotatable { unreachable!() @@ -1793,7 +1823,7 @@ impl InvocationCollectorNode for ast::Ty { fn is_mac_call(&self) -> bool { matches!(self.kind, ast::TyKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { match self.kind { TyKind::MacCall(mac) => (mac, AttrVec::new(), AddSemicolon::No), _ => unreachable!(), @@ -1802,7 +1832,7 @@ impl InvocationCollectorNode for ast::Ty { } impl InvocationCollectorNode for ast::Pat { - type OutputTy = P; + type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Pat; fn to_annotatable(self) -> Annotatable { unreachable!() @@ -1816,7 +1846,7 @@ impl InvocationCollectorNode for ast::Pat { fn is_mac_call(&self) -> bool { matches!(self.kind, PatKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { match self.kind { PatKind::MacCall(mac) => (mac, AttrVec::new(), AddSemicolon::No), _ => unreachable!(), @@ -1825,10 +1855,10 @@ impl InvocationCollectorNode for ast::Pat { } impl InvocationCollectorNode for ast::Expr { - type OutputTy = P; + type OutputTy = Box; const KIND: AstFragmentKind = AstFragmentKind::Expr; fn to_annotatable(self) -> Annotatable { - Annotatable::Expr(P(self)) + Annotatable::Expr(Box::new(self)) } fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy { fragment.make_expr() @@ -1842,7 +1872,7 @@ impl InvocationCollectorNode for ast::Expr { fn is_mac_call(&self) -> bool { matches!(self.kind, ExprKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { match self.kind { ExprKind::MacCall(mac) => (mac, self.attrs, AddSemicolon::No), _ => unreachable!(), @@ -1851,8 +1881,8 @@ impl InvocationCollectorNode for ast::Expr { } struct OptExprTag; -impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { - type OutputTy = Option>; +impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { + type OutputTy = Option>; const KIND: AstFragmentKind = AstFragmentKind::OptExpr; fn to_annotatable(self) -> Annotatable { Annotatable::Expr(self.wrapped) @@ -1867,7 +1897,7 @@ impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, ast::ExprKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { let node = self.wrapped; match node.kind { ExprKind::MacCall(mac) => (mac, node.attrs, AddSemicolon::No), @@ -1884,13 +1914,13 @@ impl InvocationCollectorNode for AstNodeWrapper, OptExprTag> { struct MethodReceiverTag; impl InvocationCollectorNode for AstNodeWrapper { - type OutputTy = AstNodeWrapper, MethodReceiverTag>; + type OutputTy = AstNodeWrapper, MethodReceiverTag>; const KIND: AstFragmentKind = AstFragmentKind::MethodReceiverExpr; fn descr() -> &'static str { "an expression" } fn to_annotatable(self) -> Annotatable { - Annotatable::Expr(P(self.wrapped)) + Annotatable::Expr(Box::new(self.wrapped)) } fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy { AstNodeWrapper::new(fragment.make_method_receiver_expr(), MethodReceiverTag) @@ -1901,7 +1931,7 @@ impl InvocationCollectorNode for AstNodeWrapper { fn is_mac_call(&self) -> bool { matches!(self.wrapped.kind, ast::ExprKind::MacCall(..)) } - fn take_mac_call(self) -> (P, ast::AttrVec, AddSemicolon) { + fn take_mac_call(self) -> (Box, ast::AttrVec, AddSemicolon) { let node = self.wrapped; match node.kind { ExprKind::MacCall(mac) => (mac, node.attrs, AddSemicolon::No), @@ -2038,7 +2068,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { placeholder(fragment_kind, NodeId::placeholder_from_expn_id(expn_id), vis) } - fn collect_bang(&mut self, mac: P, kind: AstFragmentKind) -> AstFragment { + fn collect_bang(&mut self, mac: Box, kind: AstFragmentKind) -> AstFragment { // cache the macro call span so that it can be // easily adjusted for incremental compilation let span = mac.span(); @@ -2056,7 +2086,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { fn collect_glob_delegation( &mut self, - item: P, + item: Box, of_trait: bool, kind: AstFragmentKind, ) -> AstFragment { @@ -2157,6 +2187,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { attr_name, macro_name: pprust::path_to_string(&call.path), invoc_span: call.path.span, + attr_span: attr.span, }, ); } @@ -2328,15 +2359,15 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { } impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { - fn flat_map_item(&mut self, node: P) -> SmallVec<[P; 1]> { + fn flat_map_item(&mut self, node: Box) -> SmallVec<[Box; 1]> { self.flat_map_node(node) } fn flat_map_assoc_item( &mut self, - node: P, + node: Box, ctxt: AssocCtxt, - ) -> SmallVec<[P; 1]> { + ) -> SmallVec<[Box; 1]> { match ctxt { AssocCtxt::Trait => self.flat_map_node(AstNodeWrapper::new(node, TraitItemTag)), AssocCtxt::Impl { of_trait: false } => { @@ -2350,8 +2381,8 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { fn flat_map_foreign_item( &mut self, - node: P, - ) -> SmallVec<[P; 1]> { + node: Box, + ) -> SmallVec<[Box; 1]> { self.flat_map_node(node) } @@ -2439,14 +2470,14 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { if let Some(attr) = node.attrs.first() { self.cfg().maybe_emit_expr_attr_err(attr); } - self.visit_node(node) + ensure_sufficient_stack(|| self.visit_node(node)) } fn visit_method_receiver_expr(&mut self, node: &mut ast::Expr) { self.visit_node(AstNodeWrapper::from_mut(node, MethodReceiverTag)) } - fn filter_map_expr(&mut self, node: P) -> Option> { + fn filter_map_expr(&mut self, node: Box) -> Option> { self.flat_map_node(AstNodeWrapper::new(node, OptExprTag)) } diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 7a280d671f41..f5edaf50edd5 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -7,29 +7,50 @@ use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; use rustc_span::source_map::SourceMap; -use rustc_span::{ErrorGuaranteed, Ident, Span}; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span}; use tracing::debug; use super::macro_rules::{MacroRule, NoopTracker, parser_from_cx}; use crate::expand::{AstFragmentKind, parse_ast_fragment}; use crate::mbe::macro_parser::ParseResult::*; use crate::mbe::macro_parser::{MatcherLoc, NamedParseResult, TtParser}; -use crate::mbe::macro_rules::{Tracker, try_match_macro}; +use crate::mbe::macro_rules::{ + Tracker, try_match_macro, try_match_macro_attr, try_match_macro_derive, +}; + +pub(super) enum FailedMacro<'a> { + Func, + Attr(&'a TokenStream), + Derive, +} pub(super) fn failed_to_match_macro( psess: &ParseSess, sp: Span, def_span: Span, name: Ident, - arg: TokenStream, + args: FailedMacro<'_>, + body: &TokenStream, rules: &[MacroRule], ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); + let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { + psess.source_map().guess_head_span(def_span) + } else { + DUMMY_SP + }; + // An error occurred, try the expansion again, tracking the expansion closely for better // diagnostics. let mut tracker = CollectTrackerAndEmitter::new(psess.dcx(), sp); - let try_success_result = try_match_macro(psess, name, &arg, rules, &mut tracker); + let try_success_result = match args { + FailedMacro::Func => try_match_macro(psess, name, body, rules, &mut tracker), + FailedMacro::Attr(attr_args) => { + try_match_macro_attr(psess, name, attr_args, body, rules, &mut tracker) + } + FailedMacro::Derive => try_match_macro_derive(psess, name, body, rules, &mut tracker), + }; if try_success_result.is_ok() { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -54,8 +75,8 @@ pub(super) fn failed_to_match_macro( let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); err.span_label(span, label); - if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { - err.span_label(psess.source_map().guess_head_span(def_span), "when calling this macro"); + if !def_head_span.is_dummy() { + err.span_label(def_head_span, "when calling this macro"); } annotate_doc_comment(&mut err, psess.source_map(), span); @@ -79,13 +100,16 @@ pub(super) fn failed_to_match_macro( } // Check whether there's a missing comma in this macro call, like `println!("{}" a);` - if let Some((arg, comma_span)) = arg.add_comma() { + if let FailedMacro::Func = args + && let Some((body, comma_span)) = body.add_comma() + { for rule in rules { - let parser = parser_from_cx(psess, arg.clone(), Recovery::Allowed); + let MacroRule::Func { lhs, .. } = rule else { continue }; + let parser = parser_from_cx(psess, body.clone(), Recovery::Allowed); let mut tt_parser = TtParser::new(name); if let Success(_) = - tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, &mut NoopTracker) + tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, &mut NoopTracker) { if comma_span.is_dummy() { err.note("you might be missing a comma"); @@ -116,13 +140,13 @@ struct CollectTrackerAndEmitter<'dcx, 'matcher> { struct BestFailure { token: Token, - position_in_tokenstream: u32, + position_in_tokenstream: (bool, u32), msg: &'static str, remaining_matcher: MatcherLoc, } impl BestFailure { - fn is_better_position(&self, position: u32) -> bool { + fn is_better_position(&self, position: (bool, u32)) -> bool { position > self.position_in_tokenstream } } @@ -142,7 +166,7 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match } } - fn after_arm(&mut self, result: &NamedParseResult) { + fn after_arm(&mut self, in_body: bool, result: &NamedParseResult) { match result { Success(_) => { // Nonterminal parser recovery might turn failed matches into successful ones, @@ -155,14 +179,15 @@ impl<'dcx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'dcx, 'match Failure((token, approx_position, msg)) => { debug!(?token, ?msg, "a new failure of an arm"); + let position_in_tokenstream = (in_body, *approx_position); if self .best_failure .as_ref() - .is_none_or(|failure| failure.is_better_position(*approx_position)) + .is_none_or(|failure| failure.is_better_position(position_in_tokenstream)) { self.best_failure = Some(BestFailure { token: *token, - position_in_tokenstream: *approx_position, + position_in_tokenstream, msg, remaining_matcher: self .remaining_matcher diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index bbdff866feba..faeae1f494e6 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -193,15 +193,19 @@ struct MacroState<'a> { /// Arguments: /// - `psess` is used to emit diagnostics and lints /// - `node_id` is used to emit lints -/// - `lhs` and `rhs` represent the rule +/// - `args`, `lhs`, and `rhs` represent the rule pub(super) fn check_meta_variables( psess: &ParseSess, node_id: NodeId, + args: Option<&TokenTree>, lhs: &TokenTree, rhs: &TokenTree, ) -> Result<(), ErrorGuaranteed> { let mut guar = None; let mut binders = Binders::default(); + if let Some(args) = args { + check_binders(psess, node_id, args, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); + } check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar); check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar); guar.map_or(Ok(()), Err) @@ -353,10 +357,10 @@ enum NestedMacroState { /// The token `macro_rules` was processed. MacroRules, /// The tokens `macro_rules!` were processed. - MacroRulesNot, + MacroRulesBang, /// The tokens `macro_rules!` followed by a name were processed. The name may be either directly /// an identifier or a meta-variable (that hopefully would be instantiated by an identifier). - MacroRulesNotName, + MacroRulesBangName, /// The keyword `macro` was processed. Macro, /// The keyword `macro` followed by a name was processed. @@ -404,24 +408,24 @@ fn check_nested_occurrences( NestedMacroState::MacroRules, &TokenTree::Token(Token { kind: TokenKind::Bang, .. }), ) => { - state = NestedMacroState::MacroRulesNot; + state = NestedMacroState::MacroRulesBang; } ( - NestedMacroState::MacroRulesNot, + NestedMacroState::MacroRulesBang, &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }), ) => { - state = NestedMacroState::MacroRulesNotName; + state = NestedMacroState::MacroRulesBangName; } - (NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => { - state = NestedMacroState::MacroRulesNotName; + (NestedMacroState::MacroRulesBang, &TokenTree::MetaVar(..)) => { + state = NestedMacroState::MacroRulesBangName; // We check that the meta-variable is correctly used. check_occurrences(psess, node_id, tt, macros, binders, ops, guar); } - (NestedMacroState::MacroRulesNotName, TokenTree::Delimited(.., del)) + (NestedMacroState::MacroRulesBangName, TokenTree::Delimited(.., del)) | (NestedMacroState::MacroName, TokenTree::Delimited(.., del)) if del.delim == Delimiter::Brace => { - let macro_rules = state == NestedMacroState::MacroRulesNotName; + let macro_rules = state == NestedMacroState::MacroRulesBangName; state = NestedMacroState::Empty; let rest = check_nested_macro(psess, node_id, macro_rules, &del.tts, &nested_macros, guar); diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 52d38c35f980..8b43f852b263 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -6,15 +6,16 @@ use std::{mem, slice}; use ast::token::IdentIsRaw; use rustc_ast::token::NtPatKind::*; use rustc_ast::token::TokenKind::*; -use rustc_ast::token::{self, NonterminalKind, Token, TokenKind}; -use rustc_ast::tokenstream::{DelimSpan, TokenStream}; +use rustc_ast::token::{self, Delimiter, NonterminalKind, Token, TokenKind}; +use rustc_ast::tokenstream::{self, DelimSpan, TokenStream}; use rustc_ast::{self as ast, DUMMY_NODE_ID, NodeId}; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; -use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; +use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan}; use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; +use rustc_hir::def::MacroKinds; use rustc_hir::find_attr; use rustc_lint_defs::BuiltinLintDiag; use rustc_lint_defs::builtin::{ @@ -23,23 +24,26 @@ use rustc_lint_defs::builtin::{ use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; use rustc_session::Session; -use rustc_session::parse::ParseSess; +use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::edition::Edition; use rustc_span::hygiene::Transparency; -use rustc_span::{Ident, Span, kw, sym}; +use rustc_span::{Ident, Span, Symbol, kw, sym}; use tracing::{debug, instrument, trace, trace_span}; +use super::diagnostics::{FailedMacro, failed_to_match_macro}; use super::macro_parser::{NamedMatches, NamedParseResult}; use super::{SequenceRepetition, diagnostics}; use crate::base::{ - DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, SyntaxExtension, - SyntaxExtensionKind, TTMacroExpander, + AttrProcMacro, DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult, + SyntaxExtension, SyntaxExtensionKind, TTMacroExpander, }; +use crate::errors; use crate::expand::{AstFragment, AstFragmentKind, ensure_complete_parse, parse_ast_fragment}; +use crate::mbe::macro_check::check_meta_variables; use crate::mbe::macro_parser::{Error, ErrorReported, Failure, MatcherLoc, Success, TtParser}; use crate::mbe::quoted::{RulePart, parse_one_tt}; use crate::mbe::transcribe::transcribe; -use crate::mbe::{self, KleeneOp, macro_check}; +use crate::mbe::{self, KleeneOp}; pub(crate) struct ParserAnyMacro<'a> { parser: Parser<'a>, @@ -123,10 +127,19 @@ impl<'a> ParserAnyMacro<'a> { } } -pub(super) struct MacroRule { - pub(super) lhs: Vec, - lhs_span: Span, - rhs: mbe::TokenTree, +pub(super) enum MacroRule { + /// A function-style rule, for use with `m!()` + Func { lhs: Vec, lhs_span: Span, rhs: mbe::TokenTree }, + /// An attr rule, for use with `#[m]` + Attr { + args: Vec, + args_span: Span, + body: Vec, + body_span: Span, + rhs: mbe::TokenTree, + }, + /// A derive rule, for use with `#[m]` + Derive { body: Vec, body_span: Span, rhs: mbe::TokenTree }, } pub struct MacroRulesMacroExpander { @@ -134,14 +147,82 @@ pub struct MacroRulesMacroExpander { name: Ident, span: Span, transparency: Transparency, + kinds: MacroKinds, rules: Vec, } impl MacroRulesMacroExpander { - pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, Span)> { + pub fn get_unused_rule(&self, rule_i: usize) -> Option<(&Ident, MultiSpan)> { // If the rhs contains an invocation like `compile_error!`, don't report it as unused. - let rule = &self.rules[rule_i]; - if has_compile_error_macro(&rule.rhs) { None } else { Some((&self.name, rule.lhs_span)) } + let (span, rhs) = match self.rules[rule_i] { + MacroRule::Func { lhs_span, ref rhs, .. } => (MultiSpan::from_span(lhs_span), rhs), + MacroRule::Attr { args_span, body_span, ref rhs, .. } => { + (MultiSpan::from_spans(vec![args_span, body_span]), rhs) + } + MacroRule::Derive { body_span, ref rhs, .. } => (MultiSpan::from_span(body_span), rhs), + }; + if has_compile_error_macro(rhs) { None } else { Some((&self.name, span)) } + } + + pub fn kinds(&self) -> MacroKinds { + self.kinds + } + + pub fn expand_derive( + &self, + cx: &mut ExtCtxt<'_>, + sp: Span, + body: &TokenStream, + ) -> Result { + // This is similar to `expand_macro`, but they have very different signatures, and will + // diverge further once derives support arguments. + let Self { name, ref rules, node_id, .. } = *self; + let psess = &cx.sess.psess; + + if cx.trace_macros() { + let msg = format!("expanding `#[derive({name})] {}`", pprust::tts_to_string(body)); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + match try_match_macro_derive(psess, name, body, rules, &mut NoopTracker) { + Ok((rule_index, rule, named_matches)) => { + let MacroRule::Derive { rhs, .. } = rule else { + panic!("try_match_macro_derive returned non-derive rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { + cx.dcx().span_bug(sp, "malformed macro derive rhs"); + }; + + let id = cx.current_expansion.id; + let tts = transcribe(psess, &named_matches, rhs, *rhs_span, self.transparency, id) + .map_err(|e| e.emit())?; + + if cx.trace_macros() { + let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + if is_defined_in_current_crate(node_id) { + cx.resolver.record_macro_rule_usage(node_id, rule_index); + } + + Ok(tts) + } + Err(CanRetry::No(guar)) => Err(guar), + Err(CanRetry::Yes) => { + let (_, guar) = failed_to_match_macro( + cx.psess(), + sp, + self.span, + name, + FailedMacro::Derive, + body, + rules, + ); + cx.macro_error_and_trace_macros_diag(); + Err(guar) + } + } } } @@ -165,6 +246,28 @@ impl TTMacroExpander for MacroRulesMacroExpander { } } +impl AttrProcMacro for MacroRulesMacroExpander { + fn expand( + &self, + cx: &mut ExtCtxt<'_>, + sp: Span, + args: TokenStream, + body: TokenStream, + ) -> Result { + expand_macro_attr( + cx, + sp, + self.span, + self.node_id, + self.name, + self.transparency, + args, + body, + &self.rules, + ) + } +} + struct DummyExpander(ErrorGuaranteed); impl TTMacroExpander for DummyExpander { @@ -197,7 +300,7 @@ pub(super) trait Tracker<'matcher> { /// This is called after an arm has been parsed, either successfully or unsuccessfully. When /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`). - fn after_arm(&mut self, _result: &NamedParseResult) {} + fn after_arm(&mut self, _in_body: bool, _result: &NamedParseResult) {} /// For tracing. fn description() -> &'static str; @@ -245,14 +348,17 @@ fn expand_macro<'cx>( match try_success_result { Ok((rule_index, rule, named_matches)) => { - let mbe::TokenTree::Delimited(rhs_span, _, ref rhs) = rule.rhs else { + let MacroRule::Func { rhs, .. } = rule else { + panic!("try_match_macro returned non-func rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { cx.dcx().span_bug(sp, "malformed macro rhs"); }; - let arm_span = rule.rhs.span(); + let arm_span = rhs_span.entire(); // rhs has holes ( `$id` and `$(...)` that need filled) let id = cx.current_expansion.id; - let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) { + let tts = match transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) { Ok(tts) => tts, Err(err) => { let guar = err.emit(); @@ -279,14 +385,91 @@ fn expand_macro<'cx>( } Err(CanRetry::Yes) => { // Retry and emit a better error. - let (span, guar) = - diagnostics::failed_to_match_macro(cx.psess(), sp, def_span, name, arg, rules); + let (span, guar) = failed_to_match_macro( + cx.psess(), + sp, + def_span, + name, + FailedMacro::Func, + &arg, + rules, + ); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) } } } +/// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`. +#[instrument(skip(cx, transparency, args, body, rules))] +fn expand_macro_attr( + cx: &mut ExtCtxt<'_>, + sp: Span, + def_span: Span, + node_id: NodeId, + name: Ident, + transparency: Transparency, + args: TokenStream, + body: TokenStream, + rules: &[MacroRule], +) -> Result { + let psess = &cx.sess.psess; + // Macros defined in the current crate have a real node id, + // whereas macros from an external crate have a dummy id. + let is_local = node_id != DUMMY_NODE_ID; + + if cx.trace_macros() { + let msg = format!( + "expanding `#[{name}({})] {}`", + pprust::tts_to_string(&args), + pprust::tts_to_string(&body), + ); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + // Track nothing for the best performance. + match try_match_macro_attr(psess, name, &args, &body, rules, &mut NoopTracker) { + Ok((i, rule, named_matches)) => { + let MacroRule::Attr { rhs, .. } = rule else { + panic!("try_macro_match_attr returned non-attr rule"); + }; + let mbe::TokenTree::Delimited(rhs_span, _, rhs) = rhs else { + cx.dcx().span_bug(sp, "malformed macro rhs"); + }; + + let id = cx.current_expansion.id; + let tts = transcribe(psess, &named_matches, rhs, *rhs_span, transparency, id) + .map_err(|e| e.emit())?; + + if cx.trace_macros() { + let msg = format!("to `{}`", pprust::tts_to_string(&tts)); + trace_macros_note(&mut cx.expansions, sp, msg); + } + + if is_local { + cx.resolver.record_macro_rule_usage(node_id, i); + } + + Ok(tts) + } + Err(CanRetry::No(guar)) => Err(guar), + Err(CanRetry::Yes) => { + // Retry and emit a better error. + let (_, guar) = failed_to_match_macro( + cx.psess(), + sp, + def_span, + name, + FailedMacro::Attr(&args), + &body, + rules, + ); + cx.trace_macros_diag(); + Err(guar) + } + } +} + pub(super) enum CanRetry { Yes, /// We are not allowed to retry macro expansion as a fatal error has been emitted already. @@ -327,6 +510,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // Try each arm's matchers. let mut tt_parser = TtParser::new(name); for (i, rule) in rules.iter().enumerate() { + let MacroRule::Func { lhs, .. } = rule else { continue }; let _tracing_span = trace_span!("Matching arm", %i); // Take a snapshot of the state of pre-expansion gating at this point. @@ -335,9 +519,9 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); - let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), &rule.lhs, track); + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&parser), lhs, track); - track.after_arm(&result); + track.after_arm(true, &result); match result { Success(named_matches) => { @@ -372,6 +556,99 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( Err(CanRetry::Yes) } +/// Try expanding the macro attribute. Returns the index of the successful arm and its +/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job +/// to use `track` accordingly to record all errors correctly. +#[instrument(level = "debug", skip(psess, attr_args, attr_body, rules, track), fields(tracking = %T::description()))] +pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( + psess: &ParseSess, + name: Ident, + attr_args: &TokenStream, + attr_body: &TokenStream, + rules: &'matcher [MacroRule], + track: &mut T, +) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { + // This uses the same strategy as `try_match_macro` + let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery()); + let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery()); + let mut tt_parser = TtParser::new(name); + for (i, rule) in rules.iter().enumerate() { + let MacroRule::Attr { args, body, .. } = rule else { continue }; + + let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&args_parser), args, track); + track.after_arm(false, &result); + + let mut named_matches = match result { + Success(named_matches) => named_matches, + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()); + continue; + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + }; + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + track.after_arm(true, &result); + + match result { + Success(body_named_matches) => { + psess.gated_spans.merge(gated_spans_snapshot); + #[allow(rustc::potential_query_instability)] + named_matches.extend(body_named_matches); + return Ok((i, rule, named_matches)); + } + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + } + } + + Err(CanRetry::Yes) +} + +/// Try expanding the macro derive. Returns the index of the successful arm and its +/// named_matches if it was successful, and nothing if it failed. On failure, it's the caller's job +/// to use `track` accordingly to record all errors correctly. +#[instrument(level = "debug", skip(psess, body, rules, track), fields(tracking = %T::description()))] +pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( + psess: &ParseSess, + name: Ident, + body: &TokenStream, + rules: &'matcher [MacroRule], + track: &mut T, +) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { + // This uses the same strategy as `try_match_macro` + let body_parser = parser_from_cx(psess, body.clone(), T::recovery()); + let mut tt_parser = TtParser::new(name); + for (i, rule) in rules.iter().enumerate() { + let MacroRule::Derive { body, .. } = rule else { continue }; + + let mut gated_spans_snapshot = mem::take(&mut *psess.gated_spans.spans.borrow_mut()); + + let result = tt_parser.parse_tt(&mut Cow::Borrowed(&body_parser), body, track); + track.after_arm(true, &result); + + match result { + Success(named_matches) => { + psess.gated_spans.merge(gated_spans_snapshot); + return Ok((i, rule, named_matches)); + } + Failure(_) => { + mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) + } + Error(_, _) => return Err(CanRetry::Yes), + ErrorReported(guar) => return Err(CanRetry::No(guar)), + } + } + + Err(CanRetry::Yes) +} + /// Converts a macro item into a syntax extension. pub fn compile_declarative_macro( sess: &Session, @@ -383,12 +660,12 @@ pub fn compile_declarative_macro( node_id: NodeId, edition: Edition, ) -> (SyntaxExtension, usize) { - let mk_syn_ext = |expander| { - let kind = SyntaxExtensionKind::LegacyBang(expander); + let mk_syn_ext = |kind| { let is_local = is_defined_in_current_crate(node_id); SyntaxExtension::new(sess, kind, span, Vec::new(), edition, ident.name, attrs, is_local) }; - let dummy_syn_ext = |guar| (mk_syn_ext(Arc::new(DummyExpander(guar))), 0); + let dummy_syn_ext = + |guar| (mk_syn_ext(SyntaxExtensionKind::LegacyBang(Arc::new(DummyExpander(guar)))), 0); let macro_rules = macro_def.macro_rules; let exp_sep = if macro_rules { exp!(Semi) } else { exp!(Comma) }; @@ -401,9 +678,61 @@ pub fn compile_declarative_macro( let mut guar = None; let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err()); + let mut kinds = MacroKinds::empty(); let mut rules = Vec::new(); while p.token != token::Eof { + let (args, is_derive) = if p.eat_keyword_noexpect(sym::attr) { + kinds |= MacroKinds::ATTR; + if !features.macro_attr() { + feature_err(sess, sym::macro_attr, span, "`macro_rules!` attributes are unstable") + .emit(); + } + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr args") { + return dummy_syn_ext(guar); + } + let args = p.parse_token_tree(); + check_args_parens(sess, sym::attr, &args); + let args = parse_one_tt(args, RulePart::Pattern, sess, node_id, features, edition); + check_emission(check_lhs(sess, node_id, &args)); + if let Some(guar) = check_no_eof(sess, &p, "expected macro attr body") { + return dummy_syn_ext(guar); + } + (Some(args), false) + } else if p.eat_keyword_noexpect(sym::derive) { + kinds |= MacroKinds::DERIVE; + let derive_keyword_span = p.prev_token.span; + if !features.macro_derive() { + feature_err(sess, sym::macro_attr, span, "`macro_rules!` derives are unstable") + .emit(); + } + if let Some(guar) = check_no_eof(sess, &p, "expected `()` after `derive`") { + return dummy_syn_ext(guar); + } + let args = p.parse_token_tree(); + check_args_parens(sess, sym::derive, &args); + let args_empty_result = check_args_empty(sess, &args); + let args_not_empty = args_empty_result.is_err(); + check_emission(args_empty_result); + if let Some(guar) = check_no_eof(sess, &p, "expected macro derive body") { + return dummy_syn_ext(guar); + } + // If the user has `=>` right after the `()`, they might have forgotten the empty + // parentheses. + if p.token == token::FatArrow { + let mut err = sess + .dcx() + .struct_span_err(p.token.span, "expected macro derive body, got `=>`"); + if args_not_empty { + err.span_label(derive_keyword_span, "need `()` after this `derive`"); + } + return dummy_syn_ext(err.emit()); + } + (None, true) + } else { + kinds |= MacroKinds::BANG; + (None, false) + }; let lhs_tt = p.parse_token_tree(); let lhs_tt = parse_one_tt(lhs_tt, RulePart::Pattern, sess, node_id, features, edition); check_emission(check_lhs(sess, node_id, &lhs_tt)); @@ -416,7 +745,7 @@ pub fn compile_declarative_macro( let rhs_tt = p.parse_token_tree(); let rhs_tt = parse_one_tt(rhs_tt, RulePart::Body, sess, node_id, features, edition); check_emission(check_rhs(sess, &rhs_tt)); - check_emission(macro_check::check_meta_variables(&sess.psess, node_id, &lhs_tt, &rhs_tt)); + check_emission(check_meta_variables(&sess.psess, node_id, args.as_ref(), &lhs_tt, &rhs_tt)); let lhs_span = lhs_tt.span(); // Convert the lhs into `MatcherLoc` form, which is better for doing the // actual matching. @@ -425,7 +754,19 @@ pub fn compile_declarative_macro( } else { return dummy_syn_ext(guar.unwrap()); }; - rules.push(MacroRule { lhs, lhs_span, rhs: rhs_tt }); + if let Some(args) = args { + let args_span = args.span(); + let mbe::TokenTree::Delimited(.., delimited) = args else { + return dummy_syn_ext(guar.unwrap()); + }; + let args = mbe::macro_parser::compute_locs(&delimited.tts); + let body_span = lhs_span; + rules.push(MacroRule::Attr { args, args_span, body: lhs, body_span, rhs: rhs_tt }); + } else if is_derive { + rules.push(MacroRule::Derive { body: lhs, body_span: lhs_span, rhs: rhs_tt }); + } else { + rules.push(MacroRule::Func { lhs, lhs_span, rhs: rhs_tt }); + } if p.token == token::Eof { break; } @@ -438,6 +779,7 @@ pub fn compile_declarative_macro( let guar = sess.dcx().span_err(span, "macros must contain at least one rule"); return dummy_syn_ext(guar); } + assert!(!kinds.is_empty()); let transparency = find_attr!(attrs, AttributeKind::MacroTransparency(x) => *x) .unwrap_or(Transparency::fallback(macro_rules)); @@ -451,9 +793,8 @@ pub fn compile_declarative_macro( // Return the number of rules for unused rule linting, if this is a local macro. let nrules = if is_defined_in_current_crate(node_id) { rules.len() } else { 0 }; - let expander = - Arc::new(MacroRulesMacroExpander { name: ident, span, node_id, transparency, rules }); - (mk_syn_ext(expander), nrules) + let exp = MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, rules }; + (mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules) } fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option { @@ -469,6 +810,29 @@ fn check_no_eof(sess: &Session, p: &Parser<'_>, msg: &'static str) -> Option Result<(), ErrorGuaranteed> { + match args { + tokenstream::TokenTree::Delimited(.., delimited) if delimited.is_empty() => Ok(()), + _ => { + let msg = "`derive` rules do not accept arguments; `derive` must be followed by `()`"; + Err(sess.dcx().span_err(args.span(), msg)) + } + } +} + fn check_lhs(sess: &Session, node_id: NodeId, lhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> { let e1 = check_lhs_nt_follows(sess, node_id, lhs); let e2 = check_lhs_no_empty_seq(sess, slice::from_ref(lhs)); diff --git a/compiler/rustc_expand/src/module.rs b/compiler/rustc_expand/src/module.rs index e925052c6070..6666ea33cd37 100644 --- a/compiler/rustc_expand/src/module.rs +++ b/compiler/rustc_expand/src/module.rs @@ -1,7 +1,6 @@ use std::iter::once; use std::path::{self, Path, PathBuf}; -use rustc_ast::ptr::P; use rustc_ast::{AttrVec, Attribute, Inline, Item, ModSpans}; use rustc_errors::{Diag, ErrorGuaranteed}; use rustc_parse::{exp, new_parser_from_file, unwrap_or_emit_fatal, validate_attr}; @@ -31,7 +30,7 @@ pub struct ModulePathSuccess { } pub(crate) struct ParsedExternalMod { - pub items: ThinVec>, + pub items: ThinVec>, pub spans: ModSpans, pub file_path: PathBuf, pub dir_path: PathBuf, @@ -121,7 +120,7 @@ pub(crate) fn mod_dir_path( (dir_path, dir_ownership) } - Inline::No => { + Inline::No { .. } => { // FIXME: This is a subset of `parse_external_mod` without actual parsing, // check whether the logic for unloaded, loaded and inline modules can be unified. let file_path = mod_file_path(sess, ident, attrs, &module.dir_path, dir_ownership) diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 6e1c6df4bcb4..05f9a5aa43f7 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -1,5 +1,4 @@ use rustc_ast::mut_visit::*; -use rustc_ast::ptr::P; use rustc_ast::token::Delimiter; use rustc_ast::visit::AssocCtxt; use rustc_ast::{self as ast, Safety}; @@ -15,10 +14,10 @@ pub(crate) fn placeholder( id: ast::NodeId, vis: Option, ) -> AstFragment { - fn mac_placeholder() -> P { - P(ast::MacCall { + fn mac_placeholder() -> Box { + Box::new(ast::MacCall { path: ast::Path { span: DUMMY_SP, segments: ThinVec::new(), tokens: None }, - args: P(ast::DelimArgs { + args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, tokens: ast::tokenstream::TokenStream::new(Vec::new()), @@ -35,7 +34,7 @@ pub(crate) fn placeholder( }); let span = DUMMY_SP; let expr_placeholder = || { - P(ast::Expr { + Box::new(ast::Expr { id, span, attrs: ast::AttrVec::new(), @@ -43,10 +42,17 @@ pub(crate) fn placeholder( tokens: None, }) }; - let ty = - || P(ast::Ty { id, kind: ast::TyKind::MacCall(mac_placeholder()), span, tokens: None }); - let pat = - || P(ast::Pat { id, kind: ast::PatKind::MacCall(mac_placeholder()), span, tokens: None }); + let ty = || { + Box::new(ast::Ty { id, kind: ast::TyKind::MacCall(mac_placeholder()), span, tokens: None }) + }; + let pat = || { + Box::new(ast::Pat { + id, + kind: ast::PatKind::MacCall(mac_placeholder()), + span, + tokens: None, + }) + }; match kind { AstFragmentKind::Crate => AstFragment::Crate(ast::Crate { @@ -59,7 +65,7 @@ pub(crate) fn placeholder( AstFragmentKind::Expr => AstFragment::Expr(expr_placeholder()), AstFragmentKind::OptExpr => AstFragment::OptExpr(Some(expr_placeholder())), AstFragmentKind::MethodReceiverExpr => AstFragment::MethodReceiverExpr(expr_placeholder()), - AstFragmentKind::Items => AstFragment::Items(smallvec![P(ast::Item { + AstFragmentKind::Items => AstFragment::Items(smallvec![Box::new(ast::Item { id, span, vis, @@ -67,15 +73,17 @@ pub(crate) fn placeholder( kind: ast::ItemKind::MacCall(mac_placeholder()), tokens: None, })]), - AstFragmentKind::TraitItems => AstFragment::TraitItems(smallvec![P(ast::AssocItem { - id, - span, - vis, - attrs, - kind: ast::AssocItemKind::MacCall(mac_placeholder()), - tokens: None, - })]), - AstFragmentKind::ImplItems => AstFragment::ImplItems(smallvec![P(ast::AssocItem { + AstFragmentKind::TraitItems => { + AstFragment::TraitItems(smallvec![Box::new(ast::AssocItem { + id, + span, + vis, + attrs, + kind: ast::AssocItemKind::MacCall(mac_placeholder()), + tokens: None, + })]) + } + AstFragmentKind::ImplItems => AstFragment::ImplItems(smallvec![Box::new(ast::AssocItem { id, span, vis, @@ -84,7 +92,7 @@ pub(crate) fn placeholder( tokens: None, })]), AstFragmentKind::TraitImplItems => { - AstFragment::TraitImplItems(smallvec![P(ast::AssocItem { + AstFragment::TraitImplItems(smallvec![Box::new(ast::AssocItem { id, span, vis, @@ -94,7 +102,7 @@ pub(crate) fn placeholder( })]) } AstFragmentKind::ForeignItems => { - AstFragment::ForeignItems(smallvec![P(ast::ForeignItem { + AstFragment::ForeignItems(smallvec![Box::new(ast::ForeignItem { id, span, vis, @@ -103,20 +111,20 @@ pub(crate) fn placeholder( tokens: None, })]) } - AstFragmentKind::Pat => AstFragment::Pat(P(ast::Pat { + AstFragmentKind::Pat => AstFragment::Pat(Box::new(ast::Pat { id, span, kind: ast::PatKind::MacCall(mac_placeholder()), tokens: None, })), - AstFragmentKind::Ty => AstFragment::Ty(P(ast::Ty { + AstFragmentKind::Ty => AstFragment::Ty(Box::new(ast::Ty { id, span, kind: ast::TyKind::MacCall(mac_placeholder()), tokens: None, })), AstFragmentKind::Stmts => AstFragment::Stmts(smallvec![{ - let mac = P(ast::MacCallStmt { + let mac = Box::new(ast::MacCallStmt { mac: mac_placeholder(), style: ast::MacStmtStyle::Braces, attrs: ast::AttrVec::new(), @@ -297,7 +305,7 @@ impl MutVisitor for PlaceholderExpander { } } - fn flat_map_item(&mut self, item: P) -> SmallVec<[P; 1]> { + fn flat_map_item(&mut self, item: Box) -> SmallVec<[Box; 1]> { match item.kind { ast::ItemKind::MacCall(_) => self.remove(item.id).make_items(), _ => walk_flat_map_item(self, item), @@ -306,9 +314,9 @@ impl MutVisitor for PlaceholderExpander { fn flat_map_assoc_item( &mut self, - item: P, + item: Box, ctxt: AssocCtxt, - ) -> SmallVec<[P; 1]> { + ) -> SmallVec<[Box; 1]> { match item.kind { ast::AssocItemKind::MacCall(_) => { let it = self.remove(item.id); @@ -324,8 +332,8 @@ impl MutVisitor for PlaceholderExpander { fn flat_map_foreign_item( &mut self, - item: P, - ) -> SmallVec<[P; 1]> { + item: Box, + ) -> SmallVec<[Box; 1]> { match item.kind { ast::ForeignItemKind::MacCall(_) => self.remove(item.id).make_foreign_items(), _ => walk_flat_map_foreign_item(self, item), @@ -346,7 +354,7 @@ impl MutVisitor for PlaceholderExpander { } } - fn filter_map_expr(&mut self, expr: P) -> Option> { + fn filter_map_expr(&mut self, expr: Box) -> Option> { match expr.kind { ast::ExprKind::MacCall(_) => self.remove(expr.id).make_opt_expr(), _ => walk_filter_map_expr(self, expr), diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 84fbbbef061e..9bfda8764f55 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,4 +1,3 @@ -use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_errors::ErrorGuaranteed; use rustc_parse::parser::{ForceCollect, Parser}; @@ -161,7 +160,7 @@ impl MultiItemModifier for DeriveProcMacro { Ok(None) => break, Ok(Some(item)) => { if is_stmt { - items.push(Annotatable::Stmt(P(ecx.stmt_item(span, item)))); + items.push(Annotatable::Stmt(Box::new(ecx.stmt_item(span, item)))); } else { items.push(Annotatable::Item(item)); } diff --git a/compiler/rustc_expand/src/stats.rs b/compiler/rustc_expand/src/stats.rs index b4c4eac028fe..3e40632275b6 100644 --- a/compiler/rustc_expand/src/stats.rs +++ b/compiler/rustc_expand/src/stats.rs @@ -1,6 +1,5 @@ use std::iter; -use rustc_ast::ptr::P; use rustc_ast::{self as ast, DUMMY_NODE_ID, Expr, ExprKind}; use rustc_ast_pretty::pprust; use rustc_span::hygiene::{ExpnKind, MacroKind}; @@ -41,7 +40,7 @@ pub(crate) fn update_bang_macro_stats( ecx: &mut ExtCtxt<'_>, fragment_kind: AstFragmentKind, span: Span, - mac: P, + mac: Box, fragment: &AstFragment, ) { // Does this path match any of the include macros, e.g. `include!`? diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index 83be3241b127..9fe55216f939 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -396,6 +396,8 @@ declare_features! ( (accepted, slice_patterns, "1.42.0", Some(62254)), /// Allows use of `&foo[a..b]` as a slicing syntax. (accepted, slicing_syntax, "1.0.0", None), + /// Allows use of `sse4a` target feature. + (accepted, sse4a_target_feature, "CURRENT_RUSTC_VERSION", Some(44839)), /// Allows elision of `'static` lifetimes in `static`s and `const`s. (accepted, static_in_const, "1.17.0", Some(35897)), /// Allows the definition recursive static items. @@ -408,6 +410,8 @@ declare_features! ( (accepted, target_feature, "1.27.0", None), /// Allows the use of `#[target_feature]` on safe functions. (accepted, target_feature_11, "1.86.0", Some(69098)), + /// Allows use of `tbm` target feature. + (accepted, tbm_target_feature, "CURRENT_RUSTC_VERSION", Some(44839)), /// Allows `fn main()` with return types which implements `Termination` (RFC 1937). (accepted, termination_trait, "1.26.0", Some(43301)), /// Allows `#[test]` functions where the return type implements `Termination` (RFC 1937). diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index ceb7fc5bf6f8..8f632bcebc7d 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -6,6 +6,7 @@ use AttributeDuplicates::*; use AttributeGate::*; use AttributeType::*; use rustc_data_structures::fx::FxHashMap; +use rustc_hir::AttrStyle; use rustc_hir::attrs::EncodeCrossCrate; use rustc_span::edition::Edition; use rustc_span::{Symbol, sym}; @@ -120,28 +121,37 @@ pub struct AttributeTemplate { /// If `true`, the attribute is allowed to be a bare word like `#[test]`. pub word: bool, /// If `Some`, the attribute is allowed to take a list of items like `#[allow(..)]`. - pub list: Option<&'static str>, + pub list: Option<&'static [&'static str]>, /// If non-empty, the attribute is allowed to take a list containing exactly /// one of the listed words, like `#[coverage(off)]`. pub one_of: &'static [Symbol], /// If `Some`, the attribute is allowed to be a name/value pair where the /// value is a string, like `#[must_use = "reason"]`. - pub name_value_str: Option<&'static str>, + pub name_value_str: Option<&'static [&'static str]>, + /// A link to the document for this attribute. + pub docs: Option<&'static str>, } impl AttributeTemplate { - pub fn suggestions(&self, inner: bool, name: impl std::fmt::Display) -> Vec { + pub fn suggestions(&self, style: AttrStyle, name: impl std::fmt::Display) -> Vec { let mut suggestions = vec![]; - let inner = if inner { "!" } else { "" }; + let inner = match style { + AttrStyle::Outer => "", + AttrStyle::Inner => "!", + }; if self.word { suggestions.push(format!("#{inner}[{name}]")); } if let Some(descr) = self.list { - suggestions.push(format!("#{inner}[{name}({descr})]")); + for descr in descr { + suggestions.push(format!("#{inner}[{name}({descr})]")); + } } suggestions.extend(self.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]"))); if let Some(descr) = self.name_value_str { - suggestions.push(format!("#{inner}[{name} = \"{descr}\"]")); + for descr in descr { + suggestions.push(format!("#{inner}[{name} = \"{descr}\"]")); + } } suggestions.sort(); @@ -205,20 +215,33 @@ pub enum AttributeDuplicates { /// supports forms `#[attr]` and `#[attr(description)]`. #[macro_export] macro_rules! template { - (Word) => { $crate::template!(@ true, None, &[], None) }; - (List: $descr: expr) => { $crate::template!(@ false, Some($descr), &[], None) }; - (OneOf: $one_of: expr) => { $crate::template!(@ false, None, $one_of, None) }; - (NameValueStr: $descr: expr) => { $crate::template!(@ false, None, &[], Some($descr)) }; - (Word, List: $descr: expr) => { $crate::template!(@ true, Some($descr), &[], None) }; - (Word, NameValueStr: $descr: expr) => { $crate::template!(@ true, None, &[], Some($descr)) }; + (Word) => { $crate::template!(@ true, None, &[], None, None) }; + (Word, $link: literal) => { $crate::template!(@ true, None, &[], None, Some($link)) }; + (List: $descr: expr) => { $crate::template!(@ false, Some($descr), &[], None, None) }; + (List: $descr: expr, $link: literal) => { $crate::template!(@ false, Some($descr), &[], None, Some($link)) }; + (OneOf: $one_of: expr) => { $crate::template!(@ false, None, $one_of, None, None) }; + (NameValueStr: [$($descr: literal),* $(,)?]) => { $crate::template!(@ false, None, &[], Some(&[$($descr,)*]), None) }; + (NameValueStr: [$($descr: literal),* $(,)?], $link: literal) => { $crate::template!(@ false, None, &[], Some(&[$($descr,)*]), Some($link)) }; + (NameValueStr: $descr: literal) => { $crate::template!(@ false, None, &[], Some(&[$descr]), None) }; + (NameValueStr: $descr: literal, $link: literal) => { $crate::template!(@ false, None, &[], Some(&[$descr]), Some($link)) }; + (Word, List: $descr: expr) => { $crate::template!(@ true, Some($descr), &[], None, None) }; + (Word, List: $descr: expr, $link: literal) => { $crate::template!(@ true, Some($descr), &[], None, Some($link)) }; + (Word, NameValueStr: $descr: expr) => { $crate::template!(@ true, None, &[], Some(&[$descr]), None) }; + (Word, NameValueStr: $descr: expr, $link: literal) => { $crate::template!(@ true, None, &[], Some(&[$descr]), Some($link)) }; (List: $descr1: expr, NameValueStr: $descr2: expr) => { - $crate::template!(@ false, Some($descr1), &[], Some($descr2)) + $crate::template!(@ false, Some($descr1), &[], Some(&[$descr2]), None) + }; + (List: $descr1: expr, NameValueStr: $descr2: expr, $link: literal) => { + $crate::template!(@ false, Some($descr1), &[], Some(&[$descr2]), Some($link)) }; (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => { - $crate::template!(@ true, Some($descr1), &[], Some($descr2)) + $crate::template!(@ true, Some($descr1), &[], Some(&[$descr2]), None) }; - (@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr) => { $crate::AttributeTemplate { - word: $word, list: $list, one_of: $one_of, name_value_str: $name_value_str + (Word, List: $descr1: expr, NameValueStr: $descr2: expr, $link: literal) => { + $crate::template!(@ true, Some($descr1), &[], Some(&[$descr2]), Some($link)) + }; + (@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr, $link: expr) => { $crate::AttributeTemplate { + word: $word, list: $list, one_of: $one_of, name_value_str: $name_value_str, docs: $link, } }; } @@ -391,18 +414,42 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== // Conditional compilation: - ungated!(cfg, Normal, template!(List: "predicate"), DuplicatesOk, EncodeCrossCrate::Yes), - ungated!(cfg_attr, Normal, template!(List: "predicate, attr1, attr2, ..."), DuplicatesOk, EncodeCrossCrate::Yes), + ungated!( + cfg, Normal, + template!( + List: &["predicate"], + "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute" + ), + DuplicatesOk, EncodeCrossCrate::Yes + ), + ungated!( + cfg_attr, Normal, + template!( + List: &["predicate, attr1, attr2, ..."], + "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute" + ), + DuplicatesOk, EncodeCrossCrate::Yes + ), // Testing: ungated!( - ignore, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, - EncodeCrossCrate::No, + ignore, Normal, + template!( + Word, + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute" + ), + WarnFollowing, EncodeCrossCrate::No, ), ungated!( should_panic, Normal, - template!(Word, List: r#"expected = "reason""#, NameValueStr: "reason"), FutureWarnFollowing, - EncodeCrossCrate::No, + template!( + Word, + List: &[r#"expected = "reason""#], + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No, ), // FIXME(Centril): This can be used on stable but shouldn't. ungated!( @@ -411,46 +458,102 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), // Macros: - ungated!(automatically_derived, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), ungated!( - macro_use, Normal, template!(Word, List: "name1, name2, ..."), WarnFollowingWordOnly, - EncodeCrossCrate::No, + automatically_derived, Normal, + template!( + Word, + "https://doc.rust-lang.org/reference/attributes/derive.html#the-automatically_derived-attribute" + ), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + macro_use, Normal, + template!( + Word, + List: &["name1, name2, ..."], + "https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute" + ), + WarnFollowingWordOnly, EncodeCrossCrate::No, ), ungated!(macro_escape, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), // Deprecated synonym for `macro_use`. ungated!( - macro_export, Normal, template!(Word, List: "local_inner_macros"), + macro_export, Normal, + template!( + Word, + List: &["local_inner_macros"], + "https://doc.rust-lang.org/reference/macros-by-example.html#path-based-scope" + ), WarnFollowing, EncodeCrossCrate::Yes ), - ungated!(proc_macro, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No), ungated!( - proc_macro_derive, Normal, template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"), + proc_macro, Normal, + template!( + Word, + "https://doc.rust-lang.org/reference/procedural-macros.html#function-like-procedural-macros"), + ErrorFollowing, EncodeCrossCrate::No + ), + ungated!( + proc_macro_derive, Normal, + template!( + List: &["TraitName", "TraitName, attributes(name1, name2, ...)"], + "https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros" + ), ErrorFollowing, EncodeCrossCrate::No, ), - ungated!(proc_macro_attribute, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No), + ungated!( + proc_macro_attribute, Normal, + template!(Word, "https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros"), + ErrorFollowing, EncodeCrossCrate::No + ), // Lints: ungated!( - warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + warn, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( - allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + allow, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( - expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + expect, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( - forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + forbid, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), DuplicatesOk, EncodeCrossCrate::No ), ungated!( - deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + deny, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), DuplicatesOk, EncodeCrossCrate::No ), ungated!( - must_use, Normal, template!(Word, NameValueStr: "reason"), + must_use, Normal, + template!( + Word, + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute" + ), FutureWarnFollowing, EncodeCrossCrate::Yes ), gated!( @@ -461,52 +564,104 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ deprecated, Normal, template!( Word, - List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#, - NameValueStr: "reason" + List: &[r#"/*opt*/ since = "version", /*opt*/ note = "reason""#], + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute" ), ErrorFollowing, EncodeCrossCrate::Yes ), // Crate properties: ungated!( - crate_name, CrateLevel, template!(NameValueStr: "name"), FutureWarnFollowing, - EncodeCrossCrate::No, + crate_name, CrateLevel, + template!( + NameValueStr: "name", + "https://doc.rust-lang.org/reference/crates-and-source-files.html#the-crate_name-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No, ), ungated!( - crate_type, CrateLevel, template!(NameValueStr: "bin|lib|..."), DuplicatesOk, - EncodeCrossCrate::No, + crate_type, CrateLevel, + template!( + NameValueStr: ["bin", "lib", "dylib", "cdylib", "rlib", "staticlib", "sdylib", "proc-macro"], + "https://doc.rust-lang.org/reference/linkage.html" + ), + DuplicatesOk, EncodeCrossCrate::No, ), // ABI, linking, symbols, and FFI ungated!( link, Normal, - template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated""#), - DuplicatesOk, - EncodeCrossCrate::No, + template!(List: &[ + r#"name = "...""#, + r#"name = "...", kind = "dylib|static|...""#, + r#"name = "...", wasm_import_module = "...""#, + r#"name = "...", import_name_type = "decorated|noprefix|undecorated""#, + r#"name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated""#, + ], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute"), + DuplicatesOk, EncodeCrossCrate::No, ), ungated!( - link_name, Normal, template!(NameValueStr: "name"), + link_name, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute"), FutureWarnPreceding, EncodeCrossCrate::Yes ), - ungated!(no_link, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), - ungated!(repr, Normal, template!(List: "C"), DuplicatesOk, EncodeCrossCrate::No), + ungated!( + no_link, Normal, + template!(Word, "https://doc.rust-lang.org/reference/items/extern-crates.html#the-no_link-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + repr, Normal, + template!( + List: &["C", "Rust", "transparent", "align(...)", "packed(...)", ""], + "https://doc.rust-lang.org/reference/type-layout.html#representations" + ), + DuplicatesOk, EncodeCrossCrate::No + ), // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity - gated!(rustc_align, Normal, template!(List: "alignment"), DuplicatesOk, EncodeCrossCrate::No, fn_align, experimental!(rustc_align)), - ungated!(unsafe(Edition2024) export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding, EncodeCrossCrate::No), - ungated!(unsafe(Edition2024) link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding, EncodeCrossCrate::No), - ungated!(unsafe(Edition2024) no_mangle, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), - ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, EncodeCrossCrate::No), - ungated!(link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding, EncodeCrossCrate::Yes), - ungated!(unsafe naked, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), + gated!(rustc_align, Normal, template!(List: &["alignment"]), DuplicatesOk, EncodeCrossCrate::No, fn_align, experimental!(rustc_align)), + ungated!( + unsafe(Edition2024) export_name, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute"), + FutureWarnPreceding, EncodeCrossCrate::No + ), + ungated!( + unsafe(Edition2024) link_section, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"), + FutureWarnPreceding, EncodeCrossCrate::No + ), + ungated!( + unsafe(Edition2024) no_mangle, Normal, + template!(Word, "https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + used, Normal, + template!(Word, List: &["compiler", "linker"], "https://doc.rust-lang.org/reference/abi.html#the-used-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + link_ordinal, Normal, + template!(List: &["ordinal"], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute"), + ErrorPreceding, EncodeCrossCrate::Yes + ), + ungated!( + unsafe naked, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-naked-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), // Limits: ungated!( - recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing, - EncodeCrossCrate::No + recursion_limit, CrateLevel, + template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No ), ungated!( - type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing, - EncodeCrossCrate::No + type_length_limit, CrateLevel, + template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-type_length_limit-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No ), gated!( move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, @@ -514,36 +669,84 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), // Entry point: - ungated!(no_main, CrateLevel, template!(Word), WarnFollowing, EncodeCrossCrate::No), + ungated!( + no_main, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/crates-and-source-files.html#the-no_main-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), // Modules, prelude, and resolution: - ungated!(path, Normal, template!(NameValueStr: "file"), FutureWarnFollowing, EncodeCrossCrate::No), - ungated!(no_std, CrateLevel, template!(Word), WarnFollowing, EncodeCrossCrate::No), - ungated!(no_implicit_prelude, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), - ungated!(non_exhaustive, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), + ungated!( + path, Normal, + template!(NameValueStr: "file", "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_std, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_implicit_prelude, Normal, + template!(Word, "https://doc.rust-lang.org/reference/names/preludes.html#the-no_implicit_prelude-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + non_exhaustive, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), // Runtime ungated!( windows_subsystem, CrateLevel, - template!(NameValueStr: "windows|console"), FutureWarnFollowing, - EncodeCrossCrate::No + template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( // RFC 2070 + panic_handler, Normal, + template!(Word, "https://doc.rust-lang.org/reference/panic.html#the-panic_handler-attribute"), + WarnFollowing, EncodeCrossCrate::Yes ), - ungated!(panic_handler, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), // RFC 2070 // Code generation: - ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, EncodeCrossCrate::No), - ungated!(cold, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), - ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), ungated!( - target_feature, Normal, template!(List: r#"enable = "name""#), + inline, Normal, + template!( + Word, + List: &["always", "never"], + "https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( + cold, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-cold-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_builtins, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-no_builtins-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + target_feature, Normal, + template!(List: &[r#"enable = "name""#], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute"), DuplicatesOk, EncodeCrossCrate::No, ), - ungated!(track_caller, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), - ungated!(instruction_set, Normal, template!(List: "set"), ErrorPreceding, EncodeCrossCrate::No), + ungated!( + track_caller, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-track_caller-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + instruction_set, Normal, + template!(List: &["set"], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-instruction_set-attribute"), + ErrorPreceding, EncodeCrossCrate::No + ), gated!( - no_sanitize, Normal, - template!(List: "address, kcfi, memory, thread"), DuplicatesOk, - EncodeCrossCrate::No, experimental!(no_sanitize) + sanitize, Normal, template!(List: &[r#"address = "on|off""#, r#"kernel_address = "on|off""#, r#"cfi = "on|off""#, r#"hwaddress = "on|off""#, r#"kcfi = "on|off""#, r#"memory = "on|off""#, r#"memtag = "on|off""#, r#"shadow_call_stack = "on|off""#, r#"thread = "on|off""#]), ErrorPreceding, + EncodeCrossCrate::No, sanitize, experimental!(sanitize), ), gated!( coverage, Normal, template!(OneOf: &[sym::off, sym::on]), @@ -552,18 +755,31 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), ungated!( - doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk, - EncodeCrossCrate::Yes + doc, Normal, + template!( + List: &["hidden", "inline"], + NameValueStr: "string", + "https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html" + ), + DuplicatesOk, EncodeCrossCrate::Yes ), // Debugging ungated!( debugger_visualizer, Normal, - template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), + template!( + List: &[r#"natvis_file = "...", gdb_script_file = "...""#], + "https://doc.rust-lang.org/reference/attributes/debugger.html#the-debugger_visualizer-attribute" + ), DuplicatesOk, EncodeCrossCrate::No ), - ungated!(collapse_debuginfo, Normal, template!(List: "no|external|yes"), ErrorFollowing, - EncodeCrossCrate::Yes + ungated!( + collapse_debuginfo, Normal, + template!( + List: &["no", "external", "yes"], + "https://doc.rust-lang.org/reference/attributes/debugger.html#the-collapse_debuginfo-attribute" + ), + ErrorFollowing, EncodeCrossCrate::Yes ), // ========================================================================== @@ -578,7 +794,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Testing: gated!( - test_runner, CrateLevel, template!(List: "path"), ErrorFollowing, + test_runner, CrateLevel, template!(List: &["path"]), ErrorFollowing, EncodeCrossCrate::Yes, custom_test_frameworks, "custom test frameworks are an unstable feature", ), @@ -597,7 +813,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), // RFC 2412 gated!( - optimize, Normal, template!(List: "none|size|speed"), ErrorPreceding, + optimize, Normal, template!(List: &["none", "size", "speed"]), ErrorPreceding, EncodeCrossCrate::No, optimize_attribute, experimental!(optimize) ), @@ -610,7 +826,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::No, experimental!(ffi_const) ), gated!( - register_tool, CrateLevel, template!(List: "tool1, tool2, ..."), DuplicatesOk, + register_tool, CrateLevel, template!(List: &["tool1, tool2, ..."]), DuplicatesOk, EncodeCrossCrate::No, experimental!(register_tool), ), @@ -624,7 +840,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), // lang-team MCP 147 gated!( - deprecated_safe, Normal, template!(List: r#"since = "version", note = "...""#), ErrorFollowing, + deprecated_safe, Normal, template!(List: &[r#"since = "version", note = "...""#]), ErrorFollowing, EncodeCrossCrate::Yes, experimental!(deprecated_safe), ), @@ -643,7 +859,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // RFC 3543 // `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` gated!( - patchable_function_entry, Normal, template!(List: "prefix_nops = m, entry_nops = n"), ErrorPreceding, + patchable_function_entry, Normal, template!(List: &["prefix_nops = m, entry_nops = n"]), ErrorPreceding, EncodeCrossCrate::Yes, experimental!(patchable_function_entry) ), @@ -673,37 +889,37 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ungated!( feature, CrateLevel, - template!(List: "name1, name2, ..."), DuplicatesOk, EncodeCrossCrate::No, + template!(List: &["name1, name2, ..."]), DuplicatesOk, EncodeCrossCrate::No, ), // DuplicatesOk since it has its own validation ungated!( stable, Normal, - template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, EncodeCrossCrate::No, + template!(List: &[r#"feature = "name", since = "version""#]), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( unstable, Normal, - template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk, + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), DuplicatesOk, EncodeCrossCrate::Yes ), ungated!( - unstable_feature_bound, Normal, template!(Word, List: "feat1, feat2, ..."), + unstable_feature_bound, Normal, template!(Word, List: &["feat1, feat2, ..."]), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( - rustc_const_unstable, Normal, template!(List: r#"feature = "name""#), + rustc_const_unstable, Normal, template!(List: &[r#"feature = "name""#]), DuplicatesOk, EncodeCrossCrate::Yes ), ungated!( rustc_const_stable, Normal, - template!(List: r#"feature = "name""#), DuplicatesOk, EncodeCrossCrate::No, + template!(List: &[r#"feature = "name""#]), DuplicatesOk, EncodeCrossCrate::No, ), ungated!( rustc_default_body_unstable, Normal, - template!(List: r#"feature = "name", reason = "...", issue = "N""#), + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), DuplicatesOk, EncodeCrossCrate::No ), gated!( - allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."), + allow_internal_unstable, Normal, template!(Word, List: &["feat1, feat2, ..."]), DuplicatesOk, EncodeCrossCrate::Yes, "allow_internal_unstable side-steps feature gating and stability checks", ), @@ -718,7 +934,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ through unstable paths" ), rustc_attr!( - rustc_deprecated_safe_2024, Normal, template!(List: r#"audit_that = "...""#), + rustc_deprecated_safe_2024, Normal, template!(List: &[r#"audit_that = "...""#]), ErrorFollowing, EncodeCrossCrate::Yes, "`#[rustc_deprecated_safe_2024]` is used to declare functions unsafe across the edition 2024 boundary", ), @@ -743,7 +959,13 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_never_type_options, Normal, - template!(List: r#"/*opt*/ fallback = "unit|niko|never|no""#), + template!(List: &[ + "", + r#"fallback = "unit""#, + r#"fallback = "niko""#, + r#"fallback = "never""#, + r#"fallback = "no""#, + ]), ErrorFollowing, EncodeCrossCrate::No, "`rustc_never_type_options` is used to experiment with never type fallback and work on \ @@ -774,6 +996,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, ), + rustc_attr!( + rustc_allocator_zeroed_variant, Normal, template!(NameValueStr: "function"), ErrorPreceding, + EncodeCrossCrate::Yes, + ), gated!( default_lib_allocator, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, allocator_internals, experimental!(default_lib_allocator), @@ -808,7 +1034,17 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== gated!( - linkage, Normal, template!(NameValueStr: "external|internal|..."), + linkage, Normal, template!(NameValueStr: [ + "available_externally", + "common", + "extern_weak", + "external", + "internal", + "linkonce", + "linkonce_odr", + "weak", + "weak_odr", + ], "https://doc.rust-lang.org/reference/linkage.html"), ErrorPreceding, EncodeCrossCrate::No, "the `linkage` attribute is experimental and not portable across platforms", ), @@ -823,7 +1059,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_builtin_macro, Normal, - template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, + template!(Word, List: &["name", "name, /*opt*/ attributes(name1, name2, ...)"]), ErrorFollowing, EncodeCrossCrate::Yes, ), rustc_attr!( @@ -832,12 +1068,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_macro_transparency, Normal, - template!(NameValueStr: "transparent|semiopaque|opaque"), ErrorFollowing, + template!(NameValueStr: ["transparent", "semiopaque", "opaque"]), ErrorFollowing, EncodeCrossCrate::Yes, "used internally for testing macro hygiene", ), rustc_attr!( rustc_autodiff, Normal, - template!(Word, List: r#""...""#), DuplicatesOk, + template!(Word, List: &[r#""...""#]), DuplicatesOk, EncodeCrossCrate::Yes, ), // Traces that are left when `cfg` and `cfg_attr` attributes are expanded. @@ -860,7 +1096,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!( rustc_on_unimplemented, Normal, template!( - List: r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#, + List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#], NameValueStr: "message" ), ErrorFollowing, EncodeCrossCrate::Yes, @@ -868,7 +1104,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), rustc_attr!( rustc_confusables, Normal, - template!(List: r#""name1", "name2", ..."#), + template!(List: &[r#""name1", "name2", ..."#]), ErrorFollowing, EncodeCrossCrate::Yes, ), // Enumerates "identity-like" conversion methods to suggest on type mismatch. @@ -909,7 +1145,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Used by the `rustc::bad_opt_access` lint on fields // types (as well as any others in future). rustc_attr!( - rustc_lint_opt_deny_field_access, Normal, template!(List: "message"), + rustc_lint_opt_deny_field_access, Normal, template!(List: &["message"]), WarnFollowing, EncodeCrossCrate::Yes, ), @@ -921,7 +1157,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_promotable, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, ), rustc_attr!( - rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing, + rustc_legacy_const_generics, Normal, template!(List: &["N"]), ErrorFollowing, EncodeCrossCrate::Yes, ), // Do not const-check this function's body. It will always get replaced during CTFE via `hook_special_const_fn`. @@ -929,10 +1165,6 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, "`#[rustc_do_not_const_check]` skips const-check for this function's body", ), - rustc_attr!( - rustc_const_panic_str, Normal, template!(Word), WarnFollowing, - EncodeCrossCrate::Yes, "`#[rustc_const_panic_str]` ensures the argument to this function is &&str during const-check", - ), rustc_attr!( rustc_const_stable_indirect, Normal, template!(Word), @@ -946,7 +1178,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ), gated!( rustc_allow_const_fn_unstable, Normal, - template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, EncodeCrossCrate::No, + template!(Word, List: &["feat1, feat2, ..."]), DuplicatesOk, EncodeCrossCrate::No, "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" ), @@ -955,13 +1187,13 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== rustc_attr!( - rustc_layout_scalar_valid_range_start, Normal, template!(List: "value"), ErrorFollowing, + rustc_layout_scalar_valid_range_start, Normal, template!(List: &["value"]), ErrorFollowing, EncodeCrossCrate::Yes, "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ niche optimizations in the standard library", ), rustc_attr!( - rustc_layout_scalar_valid_range_end, Normal, template!(List: "value"), ErrorFollowing, + rustc_layout_scalar_valid_range_end, Normal, template!(List: &["value"]), ErrorFollowing, EncodeCrossCrate::Yes, "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ niche optimizations in the standard library", @@ -1097,14 +1329,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "the `#[rustc_main]` attribute is used internally to specify test entry point function", ), rustc_attr!( - rustc_skip_during_method_dispatch, Normal, template!(List: "array, boxed_slice"), ErrorFollowing, + rustc_skip_during_method_dispatch, Normal, template!(List: &["array, boxed_slice"]), ErrorFollowing, EncodeCrossCrate::No, "the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \ from method dispatch when the receiver is of the following type, for compatibility in \ editions < 2021 (array) or editions < 2024 (boxed_slice)." ), rustc_attr!( - rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), + rustc_must_implement_one_of, Normal, template!(List: &["function1, function2, ..."]), ErrorFollowing, EncodeCrossCrate::No, "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ definition of a trait. Its syntax and semantics are highly experimental and will be \ @@ -1166,11 +1398,11 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ WarnFollowing, EncodeCrossCrate::No ), rustc_attr!( - TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), + TEST, rustc_layout, Normal, template!(List: &["field1, field2, ..."]), WarnFollowing, EncodeCrossCrate::Yes ), rustc_attr!( - TEST, rustc_abi, Normal, template!(List: "field1, field2, ..."), + TEST, rustc_abi, Normal, template!(List: &["field1, field2, ..."]), WarnFollowing, EncodeCrossCrate::No ), rustc_attr!( @@ -1191,29 +1423,29 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes ), rustc_attr!( - TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk, + TEST, rustc_if_this_changed, Normal, template!(Word, List: &["DepNode"]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( - TEST, rustc_then_this_would_need, Normal, template!(List: "DepNode"), DuplicatesOk, + TEST, rustc_then_this_would_need, Normal, template!(List: &["DepNode"]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( TEST, rustc_clean, Normal, - template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#), + template!(List: &[r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( TEST, rustc_partition_reused, Normal, - template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, EncodeCrossCrate::No + template!(List: &[r#"cfg = "...", module = "...""#]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( TEST, rustc_partition_codegened, Normal, - template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, EncodeCrossCrate::No + template!(List: &[r#"cfg = "...", module = "...""#]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( TEST, rustc_expected_cgu_reuse, Normal, - template!(List: r#"cfg = "...", module = "...", kind = "...""#), DuplicatesOk, + template!(List: &[r#"cfg = "...", module = "...", kind = "...""#]), DuplicatesOk, EncodeCrossCrate::No ), rustc_attr!( @@ -1225,11 +1457,11 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ WarnFollowing, EncodeCrossCrate::No ), rustc_attr!( - TEST, rustc_mir, Normal, template!(List: "arg1, arg2, ..."), + TEST, rustc_mir, Normal, template!(List: &["arg1, arg2, ..."]), DuplicatesOk, EncodeCrossCrate::Yes ), gated!( - custom_mir, Normal, template!(List: r#"dialect = "...", phase = "...""#), + custom_mir, Normal, template!(List: &[r#"dialect = "...", phase = "...""#]), ErrorFollowing, EncodeCrossCrate::No, "the `#[custom_mir]` attribute is just used for the Rust test suite", ), @@ -1257,11 +1489,6 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/), DuplicatesOk, EncodeCrossCrate::No ), - gated!( - omit_gdb_pretty_printer_section, Normal, template!(Word), - WarnFollowing, EncodeCrossCrate::No, - "the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite", - ), rustc_attr!( TEST, pattern_complexity_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, EncodeCrossCrate::No, diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 7e174c465d59..e37fc6b7bfcc 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -54,7 +54,7 @@ declare_features! ( /// Allows using the `amdgpu-kernel` ABI. (removed, abi_amdgpu_kernel, "1.77.0", Some(51575), None, 120495), - (removed, abi_c_cmse_nonsecure_call, "CURRENT_RUSTC_VERSION", Some(81391), Some("renamed to abi_cmse_nonsecure_call"), 142146), + (removed, abi_c_cmse_nonsecure_call, "1.90.0", Some(81391), Some("renamed to abi_cmse_nonsecure_call"), 142146), (removed, advanced_slice_patterns, "1.42.0", Some(62254), Some("merged into `#![feature(slice_patterns)]`"), 67712), (removed, allocator, "1.0.0", None, None), @@ -190,6 +190,9 @@ declare_features! ( (removed, no_coverage, "1.74.0", Some(84605), Some("renamed to `coverage_attribute`"), 114656), /// Allows `#[no_debug]`. (removed, no_debug, "1.43.0", Some(29721), Some("removed due to lack of demand"), 69667), + // Allows the use of `no_sanitize` attribute. + /// The feature was renamed to `sanitize` and the attribute to `#[sanitize(xyz = "on|off")]` + (removed, no_sanitize, "CURRENT_RUSTC_VERSION", Some(39699), Some(r#"renamed to sanitize(xyz = "on|off")"#), 142681), /// Note: this feature was previously recorded in a separate /// `STABLE_REMOVED` list because it, uniquely, was once stable but was /// then removed. But there was no utility storing it separately, so now @@ -199,6 +202,8 @@ declare_features! ( /// Renamed to `dyn_compatible_for_dispatch`. (removed, object_safe_for_dispatch, "1.83.0", Some(43561), Some("renamed to `dyn_compatible_for_dispatch`"), 131511), + /// Allows using `#[omit_gdb_pretty_printer_section]`. + (removed, omit_gdb_pretty_printer_section, "CURRENT_RUSTC_VERSION", None, None, 144738), /// Allows using `#[on_unimplemented(..)]` on traits. /// (Moved to `rustc_attrs`.) (removed, on_unimplemented, "1.40.0", None, None, 65794), @@ -298,7 +303,7 @@ declare_features! ( // FIXME(#141617): we should have a better way to track removed library features, but we reuse // the infrastructure here so users still get hints. The symbols used here can be remove from // `symbol.rs` when that happens. - (removed, concat_idents, "CURRENT_RUSTC_VERSION", Some(29599), + (removed, concat_idents, "1.90.0", Some(29599), Some("use the `${concat(..)}` metavariable expression instead"), 142704), // ------------------------------------------------------------------------- // feature-group-end: removed library features diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e985e04ba330..746871982ce8 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -225,8 +225,6 @@ declare_features! ( (unstable, multiple_supertrait_upcastable, "1.69.0", None), /// Allow negative trait bounds. This is an internal-only feature for testing the trait solver! (internal, negative_bounds, "1.71.0", None), - /// Allows using `#[omit_gdb_pretty_printer_section]`. - (internal, omit_gdb_pretty_printer_section, "1.5.0", None), /// Set the maximum pattern complexity allowed (not limited by default). (internal, pattern_complexity_limit, "1.78.0", None), /// Allows using pattern types. @@ -329,14 +327,13 @@ declare_features! ( (unstable, m68k_target_feature, "1.85.0", Some(134328)), (unstable, mips_target_feature, "1.27.0", Some(44839)), (unstable, movrs_target_feature, "1.88.0", Some(137976)), + (unstable, nvptx_target_feature, "CURRENT_RUSTC_VERSION", Some(44839)), (unstable, powerpc_target_feature, "1.27.0", Some(44839)), (unstable, prfchw_target_feature, "1.78.0", Some(44839)), (unstable, riscv_target_feature, "1.45.0", Some(44839)), (unstable, rtm_target_feature, "1.35.0", Some(44839)), (unstable, s390x_target_feature, "1.82.0", Some(44839)), (unstable, sparc_target_feature, "1.84.0", Some(132783)), - (unstable, sse4a_target_feature, "1.27.0", Some(44839)), - (unstable, tbm_target_feature, "1.27.0", Some(44839)), (unstable, wasm_target_feature, "1.30.0", Some(44839)), (unstable, x87_target_feature, "1.85.0", Some(44839)), // !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! !!!! @@ -354,7 +351,7 @@ declare_features! ( /// Allows `extern "avr-interrupt" fn()` and `extern "avr-non-blocking-interrupt" fn()`. (unstable, abi_avr_interrupt, "1.45.0", Some(69664)), /// Allows `extern "cmse-nonsecure-call" fn()`. - (unstable, abi_cmse_nonsecure_call, "CURRENT_RUSTC_VERSION", Some(81391)), + (unstable, abi_cmse_nonsecure_call, "1.90.0", Some(81391)), /// Allows `extern "custom" fn()`. (unstable, abi_custom, "1.89.0", Some(140829)), /// Allows `extern "gpu-kernel" fn()`. @@ -473,6 +470,8 @@ declare_features! ( (unstable, deprecated_suggestion, "1.61.0", Some(94785)), /// Allows deref patterns. (incomplete, deref_patterns, "1.79.0", Some(87121)), + /// Allows deriving the From trait on single-field structs. + (unstable, derive_from, "CURRENT_RUSTC_VERSION", Some(144889)), /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`. (unstable, doc_auto_cfg, "1.58.0", Some(43781)), /// Allows `#[doc(cfg(...))]`. @@ -546,7 +545,7 @@ declare_features! ( (incomplete, inherent_associated_types, "1.52.0", Some(8995)), /// Allows using `pointer` and `reference` in intra-doc links (unstable, intra_doc_pointers, "1.51.0", Some(80896)), - // Allows setting the threshold for the `large_assignments` lint. + /// Allows setting the threshold for the `large_assignments` lint. (unstable, large_assignments, "1.52.0", Some(83518)), /// Allow to have type alias types for inter-crate use. (incomplete, lazy_type_alias, "1.72.0", Some(112792)), @@ -554,7 +553,11 @@ declare_features! ( /// to pass custom arguments to the linker. (unstable, link_arg_attribute, "1.76.0", Some(99427)), /// Allows fused `loop`/`match` for direct intraprocedural jumps. - (incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(132306)), + (incomplete, loop_match, "1.90.0", Some(132306)), + /// Allow `macro_rules!` attribute rules + (unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)), + /// Allow `macro_rules!` derive rules + (unstable, macro_derive, "CURRENT_RUSTC_VERSION", Some(143549)), /// Give access to additional metadata about declarative macro meta-variables. (unstable, macro_metavar_expr, "1.61.0", Some(83527)), /// Provides a way to concatenate identifiers using metavariable expressions. @@ -591,8 +594,6 @@ declare_features! ( (unstable, new_range, "1.86.0", Some(123741)), /// Allows `#![no_core]`. (unstable, no_core, "1.3.0", Some(29639)), - /// Allows the use of `no_sanitize` attribute. - (unstable, no_sanitize, "1.42.0", Some(39699)), /// Allows using the `non_exhaustive_omitted_patterns` lint. (unstable, non_exhaustive_omitted_patterns_lint, "1.57.0", Some(89554)), /// Allows `for` binders in where-clauses @@ -625,6 +626,8 @@ declare_features! ( (unstable, return_type_notation, "1.70.0", Some(109417)), /// Allows `extern "rust-cold"`. (unstable, rust_cold_cc, "1.63.0", Some(97544)), + /// Allows the use of the `sanitize` attribute. + (unstable, sanitize, "CURRENT_RUSTC_VERSION", Some(39699)), /// Allows the use of SIMD types in functions declared in `extern` blocks. (unstable, simd_ffi, "1.0.0", Some(27731)), /// Allows specialization of implementations (RFC 1210). diff --git a/compiler/rustc_hir/Cargo.toml b/compiler/rustc_hir/Cargo.toml index 539d2e6f0b17..1008a3e787d0 100644 --- a/compiler/rustc_hir/Cargo.toml +++ b/compiler/rustc_hir/Cargo.toml @@ -5,13 +5,16 @@ edition = "2024" [dependencies] # tidy-alphabetical-start +bitflags = "2.9.1" odht = { version = "0.3.1", features = ["nightly"] } rustc_abi = { path = "../rustc_abi" } rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } rustc_data_structures = { path = "../rustc_data_structures" } +rustc_error_messages = { path = "../rustc_error_messages" } rustc_hashes = { path = "../rustc_hashes" } +rustc_hir_id = { path = "../rustc_hir_id" } rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 80f5e6177f75..a17350f03929 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1,7 +1,11 @@ +use std::borrow::Cow; +use std::path::PathBuf; + pub use ReprAttr::*; use rustc_abi::Align; use rustc_ast::token::CommentKind; use rustc_ast::{AttrStyle, ast}; +use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; use rustc_span::def_id::DefId; use rustc_span::hygiene::Transparency; @@ -187,6 +191,62 @@ pub enum CfgEntry { Version(Option, Span), } +/// Possible values for the `#[linkage]` attribute, allowing to specify the +/// linkage type for a `MonoItem`. +/// +/// See for more details about these variants. +#[derive(Encodable, Decodable, Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(HashStable_Generic, PrintAttribute)] +pub enum Linkage { + AvailableExternally, + Common, + ExternalWeak, + External, + Internal, + LinkOnceAny, + LinkOnceODR, + WeakAny, + WeakODR, +} + +#[derive(Clone, Copy, Decodable, Debug, Encodable, PartialEq)] +#[derive(HashStable_Generic, PrintAttribute)] +pub enum MirDialect { + Analysis, + Built, + Runtime, +} + +impl IntoDiagArg for MirDialect { + fn into_diag_arg(self, _path: &mut Option) -> DiagArgValue { + let arg = match self { + MirDialect::Analysis => "analysis", + MirDialect::Built => "built", + MirDialect::Runtime => "runtime", + }; + DiagArgValue::Str(Cow::Borrowed(arg)) + } +} + +#[derive(Clone, Copy, Decodable, Debug, Encodable, PartialEq)] +#[derive(HashStable_Generic, PrintAttribute)] +pub enum MirPhase { + Initial, + PostCleanup, + Optimized, +} + +impl IntoDiagArg for MirPhase { + fn into_diag_arg(self, _path: &mut Option) -> DiagArgValue { + let arg = match self { + MirPhase::Initial => "initial", + MirPhase::PostCleanup => "post-cleanup", + MirPhase::Optimized => "optimized", + }; + DiagArgValue::Str(Cow::Borrowed(arg)) + } +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -249,6 +309,9 @@ pub enum AttributeKind { /// Represents `#[rustc_allow_incoherent_impl]`. AllowIncoherentImpl(Span), + /// Represents `#[allow_internal_unsafe]`. + AllowInternalUnsafe(Span), + /// Represents `#[allow_internal_unstable]`. AllowInternalUnstable(ThinVec<(Symbol, Span)>, Span), @@ -297,9 +360,15 @@ pub enum AttributeKind { /// Represents `#[const_trait]`. ConstTrait(Span), + /// Represents `#[coroutine]`. + Coroutine(Span), + /// Represents `#[coverage(..)]`. Coverage(Span, CoverageAttrKind), + /// Represents `#[custom_mir]`. + CustomMir(Option<(MirDialect, Span)>, Option<(MirPhase, Span)>, Span), + ///Represents `#[rustc_deny_explicit_impl]`. DenyExplicitImpl(Span), @@ -354,6 +423,9 @@ pub enum AttributeKind { /// Represents [`#[link_section]`](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute) LinkSection { name: Symbol, span: Span }, + /// Represents `#[linkage]`. + Linkage(Linkage, Span), + /// Represents `#[loop_match]`. LoopMatch(Span), @@ -391,9 +463,6 @@ pub enum AttributeKind { /// Represents `#[non_exhaustive]` NonExhaustive(Span), - /// Represents `#[omit_gdb_pretty_printer_section]` - OmitGdbPrettyPrinterSection, - /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), @@ -436,6 +505,9 @@ pub enum AttributeKind { /// Represents `#[rustc_object_lifetime_default]`. RustcObjectLifetimeDefault, + /// Represents `#[should_panic]` + ShouldPanic { reason: Option, span: Span }, + /// Represents `#[rustc_skip_during_method_dispatch]`. SkipDuringMethodDispatch { array: bool, boxed_slice: bool, span: Span }, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index bbdecb2986e0..defabdccc023 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -16,6 +16,7 @@ impl AttributeKind { Align { .. } => No, AllowConstFnUnstable(..) => No, AllowIncoherentImpl(..) => No, + AllowInternalUnsafe(..) => Yes, AllowInternalUnstable(..) => Yes, AsPtr(..) => Yes, AutomaticallyDerived(..) => Yes, @@ -28,7 +29,9 @@ impl AttributeKind { ConstStability { .. } => Yes, ConstStabilityIndirect => No, ConstTrait(..) => No, + Coroutine(..) => No, Coverage(..) => No, + CustomMir(_, _, _) => Yes, DenyExplicitImpl(..) => No, Deprecation { .. } => Yes, DoNotImplementViaObject(..) => No, @@ -44,6 +47,7 @@ impl AttributeKind { LinkName { .. } => Yes, // Needed for rustdoc LinkOrdinal { .. } => No, LinkSection { .. } => Yes, // Needed for rustdoc + Linkage(..) => No, LoopMatch(..) => No, MacroEscape(..) => No, MacroTransparency(..) => Yes, @@ -55,7 +59,6 @@ impl AttributeKind { NoImplicitPrelude(..) => No, NoMangle(..) => Yes, // Needed for rustdoc NonExhaustive(..) => Yes, // Needed for rustdoc - OmitGdbPrettyPrinterSection => No, Optimize(..) => No, ParenSugar(..) => No, PassByValue(..) => Yes, @@ -70,6 +73,7 @@ impl AttributeKind { RustcLayoutScalarValidRangeEnd(..) => Yes, RustcLayoutScalarValidRangeStart(..) => Yes, RustcObjectLifetimeDefault => No, + ShouldPanic { .. } => No, SkipDuringMethodDispatch { .. } => No, SpecializationTrait(..) => No, Stability { .. } => Yes, diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs index 3fee9af01b36..8af4740f376b 100644 --- a/compiler/rustc_hir/src/def.rs +++ b/compiler/rustc_hir/src/def.rs @@ -1,10 +1,12 @@ use std::array::IntoIter; +use std::borrow::Cow; use std::fmt::Debug; use rustc_ast as ast; use rustc_ast::NodeId; use rustc_data_structures::stable_hasher::ToStableHashKey; use rustc_data_structures::unord::UnordMap; +use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_span::Symbol; use rustc_span::def_id::{DefId, LocalDefId}; @@ -31,6 +33,53 @@ pub enum CtorKind { Const, } +/// A set of macro kinds, for macros that can have more than one kind +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable, Hash, Debug)] +#[derive(HashStable_Generic)] +pub struct MacroKinds(u8); +bitflags::bitflags! { + impl MacroKinds: u8 { + const BANG = 1 << 0; + const ATTR = 1 << 1; + const DERIVE = 1 << 2; + } +} + +impl From for MacroKinds { + fn from(kind: MacroKind) -> Self { + match kind { + MacroKind::Bang => Self::BANG, + MacroKind::Attr => Self::ATTR, + MacroKind::Derive => Self::DERIVE, + } + } +} + +impl MacroKinds { + /// Convert the MacroKinds to a static string. + /// + /// This hardcodes all the possibilities, in order to return a static string. + pub fn descr(self) -> &'static str { + match self { + // FIXME: change this to "function-like macro" and fix all tests + Self::BANG => "macro", + Self::ATTR => "attribute macro", + Self::DERIVE => "derive macro", + _ if self == (Self::ATTR | Self::BANG) => "attribute/function macro", + _ if self == (Self::DERIVE | Self::BANG) => "derive/function macro", + _ if self == (Self::ATTR | Self::DERIVE) => "attribute/derive macro", + _ if self.is_all() => "attribute/derive/function macro", + _ if self.is_empty() => "useless macro", + _ => unreachable!(), + } + } + + /// Return an indefinite article (a/an) for use with `descr()` + pub fn article(self) -> &'static str { + if self.contains(Self::ATTR) { "an" } else { "a" } + } +} + /// An attribute that is not a macro; e.g., `#[inline]` or `#[rustfmt::skip]`. #[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Hash, Debug, HashStable_Generic)] pub enum NonMacroAttrKind { @@ -101,7 +150,7 @@ pub enum DefKind { AssocConst, // Macro namespace - Macro(MacroKind), + Macro(MacroKinds), // Not namespaced (or they are, but we don't treat them so) ExternCrate, @@ -177,7 +226,7 @@ impl DefKind { DefKind::AssocConst => "associated constant", DefKind::TyParam => "type parameter", DefKind::ConstParam => "const parameter", - DefKind::Macro(macro_kind) => macro_kind.descr(), + DefKind::Macro(kinds) => kinds.descr(), DefKind::LifetimeParam => "lifetime parameter", DefKind::Use => "import", DefKind::ForeignMod => "foreign module", @@ -208,7 +257,7 @@ impl DefKind { | DefKind::Use | DefKind::InlineConst | DefKind::ExternCrate => "an", - DefKind::Macro(macro_kind) => macro_kind.article(), + DefKind::Macro(kinds) => kinds.article(), _ => "a", } } @@ -305,6 +354,11 @@ impl DefKind { matches!(self, DefKind::Mod | DefKind::Enum | DefKind::Trait) } + #[inline] + pub fn is_adt(self) -> bool { + matches!(self, DefKind::Struct | DefKind::Union | DefKind::Enum) + } + #[inline] pub fn is_fn_like(self) -> bool { matches!( @@ -313,6 +367,43 @@ impl DefKind { ) } + /// Whether the corresponding item has generic parameters, ie. the `generics_of` query works. + pub fn has_generics(self) -> bool { + match self { + DefKind::AnonConst + | DefKind::AssocConst + | DefKind::AssocFn + | DefKind::AssocTy + | DefKind::Closure + | DefKind::Const + | DefKind::Ctor(..) + | DefKind::Enum + | DefKind::Field + | DefKind::Fn + | DefKind::ForeignTy + | DefKind::Impl { .. } + | DefKind::InlineConst + | DefKind::OpaqueTy + | DefKind::Static { .. } + | DefKind::Struct + | DefKind::SyntheticCoroutineBody + | DefKind::Trait + | DefKind::TraitAlias + | DefKind::TyAlias + | DefKind::Union + | DefKind::Variant => true, + DefKind::ConstParam + | DefKind::ExternCrate + | DefKind::ForeignMod + | DefKind::GlobalAsm + | DefKind::LifetimeParam + | DefKind::Macro(_) + | DefKind::Mod + | DefKind::TyParam + | DefKind::Use => false, + } + } + /// Whether `query get_codegen_attrs` should be used with this definition. pub fn has_codegen_attrs(self) -> bool { match self { @@ -360,7 +451,7 @@ impl DefKind { /// For example, everything prefixed with `/* Res */` in this example has /// an associated `Res`: /// -/// ``` +/// ```ignore (illustrative) /// fn str_to_string(s: & /* Res */ str) -> /* Res */ String { /// /* Res */ String::from(/* Res */ s) /// } @@ -421,7 +512,7 @@ pub enum Res { /// } /// /// impl Foo for Bar { - /// fn foo() -> Box { // SelfTyAlias + /// fn foo() -> Box { /// let _: Self; // SelfTyAlias /// /// todo!() @@ -497,6 +588,12 @@ pub enum Res { Err, } +impl IntoDiagArg for Res { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Borrowed(self.descr())) + } +} + /// The result of resolving a path before lowering to HIR, /// with "module" segments resolved and associated item /// segments deferred to type checking. @@ -584,6 +681,12 @@ impl Namespace { } } +impl IntoDiagArg for Namespace { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Borrowed(self.descr())) + } +} + impl ToStableHashKey for Namespace { type KeyType = Namespace; @@ -803,10 +906,10 @@ impl Res { ) } - pub fn macro_kind(self) -> Option { + pub fn macro_kinds(self) -> Option { match self { - Res::Def(DefKind::Macro(kind), _) => Some(kind), - Res::NonMacroAttr(..) => Some(MacroKind::Attr), + Res::Def(DefKind::Macro(kinds), _) => Some(kinds), + Res::NonMacroAttr(..) => Some(MacroKinds::ATTR), _ => None, } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 1b1b3ced44db..e397c286de28 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1,4 +1,5 @@ // ignore-tidy-filelength +use std::borrow::Cow; use std::fmt; use rustc_abi::ExternAbi; @@ -17,10 +18,10 @@ pub use rustc_ast::{ use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::tagged_ptr::TaggedRef; +use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_index::IndexVec; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_span::def_id::LocalDefId; -use rustc_span::hygiene::MacroKind; use rustc_span::source_map::Spanned; use rustc_span::{BytePos, DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, sym}; use rustc_target::asm::InlineAsmRegOrRegClass; @@ -30,7 +31,7 @@ use tracing::debug; use crate::LangItem; use crate::attrs::AttributeKind; -use crate::def::{CtorKind, DefKind, PerNS, Res}; +use crate::def::{CtorKind, DefKind, MacroKinds, PerNS, Res}; use crate::def_id::{DefId, LocalDefIdMap}; pub(crate) use crate::hir_id::{HirId, ItemLocalId, ItemLocalMap, OwnerId}; use crate::intravisit::{FnKind, VisitorExt}; @@ -412,10 +413,8 @@ impl<'hir> PathSegment<'hir> { /// that are [just paths](ConstArgKind::Path) (currently just bare const params) /// versus const args that are literals or have arbitrary computations (e.g., `{ 1 + 3 }`). /// -/// The `Unambig` generic parameter represents whether the position this const is from is -/// unambiguously a const or ambiguous as to whether it is a type or a const. When in an -/// ambiguous context the parameter is instantiated with an uninhabited type making the -/// [`ConstArgKind::Infer`] variant unusable and [`GenericArg::Infer`] is used instead. +/// For an explanation of the `Unambig` generic parameter see the dev-guide: +/// #[derive(Clone, Copy, Debug, HashStable_Generic)] #[repr(C)] pub struct ConstArg<'hir, Unambig = ()> { @@ -427,7 +426,7 @@ pub struct ConstArg<'hir, Unambig = ()> { impl<'hir> ConstArg<'hir, AmbigArg> { /// Converts a `ConstArg` in an ambiguous position to one in an unambiguous position. /// - /// Functions accepting an unambiguous consts may expect the [`ConstArgKind::Infer`] variant + /// Functions accepting unambiguous consts may expect the [`ConstArgKind::Infer`] variant /// to be used. Care should be taken to separately handle infer consts when calling this /// function as it cannot be handled by downstream code making use of the returned const. /// @@ -1162,6 +1161,12 @@ pub struct AttrPath { pub span: Span, } +impl IntoDiagArg for AttrPath { + fn into_diag_arg(self, path: &mut Option) -> DiagArgValue { + self.to_string().into_diag_arg(path) + } +} + impl AttrPath { pub fn from_ast(path: &ast::Path) -> Self { AttrPath { @@ -1235,6 +1240,13 @@ impl Attribute { _ => None, } } + + pub fn is_parsed_attr(&self) -> bool { + match self { + Attribute::Parsed(_) => true, + Attribute::Unparsed(_) => false, + } + } } impl AttributeExt for Attribute { @@ -1305,12 +1317,10 @@ impl AttributeExt for Attribute { match &self { Attribute::Unparsed(u) => u.span, // FIXME: should not be needed anymore when all attrs are parsed - Attribute::Parsed(AttributeKind::Deprecation { span, .. }) => *span, Attribute::Parsed(AttributeKind::DocComment { span, .. }) => *span, - Attribute::Parsed(AttributeKind::MacroUse { span, .. }) => *span, - Attribute::Parsed(AttributeKind::MayDangle(span)) => *span, - Attribute::Parsed(AttributeKind::Ignore { span, .. }) => *span, - Attribute::Parsed(AttributeKind::AutomaticallyDerived(span)) => *span, + Attribute::Parsed(AttributeKind::Deprecation { span, .. }) => *span, + Attribute::Parsed(AttributeKind::AllowInternalUnsafe(span)) => *span, + Attribute::Parsed(AttributeKind::Linkage(_, span)) => *span, a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"), } } @@ -2257,8 +2267,15 @@ impl fmt::Display for ConstContext { } } -// NOTE: `IntoDiagArg` impl for `ConstContext` lives in `rustc_errors` -// due to a cyclical dependency between hir and that crate. +impl IntoDiagArg for ConstContext { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Borrowed(match self { + ConstContext::ConstFn => "const_fn", + ConstContext::Static(_) => "static", + ConstContext::Const { .. } => "const", + })) + } +} /// A literal. pub type Lit = Spanned; @@ -3016,7 +3033,7 @@ impl fmt::Display for LoopIdError { } } -#[derive(Copy, Clone, Debug, HashStable_Generic)] +#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)] pub struct Destination { /// This is `Some(_)` iff there is an explicit user-specified 'label pub label: Option Trait for Foo { .. }`. - Impl(&'hir Impl<'hir>), + Impl(Impl<'hir>), } /// Represents an impl block declaration. @@ -4381,6 +4399,14 @@ pub enum ItemKind<'hir> { /// Refer to [`ImplItem`] for an associated item within an impl block. #[derive(Debug, Clone, Copy, HashStable_Generic)] pub struct Impl<'hir> { + pub generics: &'hir Generics<'hir>, + pub of_trait: Option<&'hir TraitImplHeader<'hir>>, + pub self_ty: &'hir Ty<'hir>, + pub items: &'hir [ImplItemId], +} + +#[derive(Debug, Clone, Copy, HashStable_Generic)] +pub struct TraitImplHeader<'hir> { pub constness: Constness, pub safety: Safety, pub polarity: ImplPolarity, @@ -4388,13 +4414,7 @@ pub struct Impl<'hir> { // We do not put a `Span` in `Defaultness` because it breaks foreign crate metadata // decoding as `Span`s cannot be decoded when a `Session` is not available. pub defaultness_span: Option, - pub generics: &'hir Generics<'hir>, - - /// The trait being implemented, if any. - pub of_trait: Option>, - - pub self_ty: &'hir Ty<'hir>, - pub items: &'hir [ImplItemId], + pub trait_ref: TraitRef<'hir>, } impl ItemKind<'_> { @@ -4756,8 +4776,8 @@ impl<'hir> Node<'hir> { /// Get a `hir::Impl` if the node is an impl block for the given `trait_def_id`. pub fn impl_block_of_trait(self, trait_def_id: DefId) -> Option<&'hir Impl<'hir>> { if let Node::Item(Item { kind: ItemKind::Impl(impl_block), .. }) = self - && let Some(trait_ref) = impl_block.of_trait - && let Some(trait_id) = trait_ref.trait_def_id() + && let Some(of_trait) = impl_block.of_trait + && let Some(trait_id) = of_trait.trait_ref.trait_def_id() && trait_id == trait_def_id { Some(impl_block) @@ -4952,7 +4972,7 @@ mod size_asserts { static_assert_size!(GenericArg<'_>, 16); static_assert_size!(GenericBound<'_>, 64); static_assert_size!(Generics<'_>, 56); - static_assert_size!(Impl<'_>, 80); + static_assert_size!(Impl<'_>, 40); static_assert_size!(ImplItem<'_>, 96); static_assert_size!(ImplItemKind<'_>, 40); static_assert_size!(Item<'_>, 88); @@ -4967,6 +4987,7 @@ mod size_asserts { static_assert_size!(Res, 12); static_assert_size!(Stmt<'_>, 32); static_assert_size!(StmtKind<'_>, 16); + static_assert_size!(TraitImplHeader<'_>, 48); static_assert_size!(TraitItem<'_>, 88); static_assert_size!(TraitItemKind<'_>, 48); static_assert_size!(Ty<'_>, 48); diff --git a/compiler/rustc_hir/src/hir_id.rs b/compiler/rustc_hir/src/hir_id.rs deleted file mode 100644 index b48a081d3714..000000000000 --- a/compiler/rustc_hir/src/hir_id.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::fmt::{self, Debug}; - -use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableOrd, ToStableHashKey}; -use rustc_macros::{Decodable, Encodable, HashStable_Generic}; -use rustc_span::HashStableContext; -use rustc_span::def_id::DefPathHash; - -use crate::def_id::{CRATE_DEF_ID, DefId, DefIndex, LocalDefId}; - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] -pub struct OwnerId { - pub def_id: LocalDefId, -} - -impl Debug for OwnerId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Example: DefId(0:1 ~ aa[7697]::{use#0}) - Debug::fmt(&self.def_id, f) - } -} - -impl From for HirId { - fn from(owner: OwnerId) -> HirId { - HirId { owner, local_id: ItemLocalId::ZERO } - } -} - -impl From for DefId { - fn from(value: OwnerId) -> Self { - value.to_def_id() - } -} - -impl OwnerId { - #[inline] - pub fn to_def_id(self) -> DefId { - self.def_id.to_def_id() - } -} - -impl rustc_index::Idx for OwnerId { - #[inline] - fn new(idx: usize) -> Self { - OwnerId { def_id: LocalDefId { local_def_index: DefIndex::from_usize(idx) } } - } - - #[inline] - fn index(self) -> usize { - self.def_id.local_def_index.as_usize() - } -} - -impl HashStable for OwnerId { - #[inline] - fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { - self.to_stable_hash_key(hcx).hash_stable(hcx, hasher); - } -} - -impl ToStableHashKey for OwnerId { - type KeyType = DefPathHash; - - #[inline] - fn to_stable_hash_key(&self, hcx: &CTX) -> DefPathHash { - hcx.def_path_hash(self.to_def_id()) - } -} - -/// Uniquely identifies a node in the HIR of the current crate. It is -/// composed of the `owner`, which is the `LocalDefId` of the directly enclosing -/// `hir::Item`, `hir::TraitItem`, or `hir::ImplItem` (i.e., the closest "item-like"), -/// and the `local_id` which is unique within the given owner. -/// -/// This two-level structure makes for more stable values: One can move an item -/// around within the source code, or add or remove stuff before it, without -/// the `local_id` part of the `HirId` changing, which is a very useful property in -/// incremental compilation where we have to persist things through changes to -/// the code base. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, HashStable_Generic)] -#[rustc_pass_by_value] -pub struct HirId { - pub owner: OwnerId, - pub local_id: ItemLocalId, -} - -// To ensure correctness of incremental compilation, -// `HirId` must not implement `Ord` or `PartialOrd`. -// See https://github.com/rust-lang/rust/issues/90317. -impl !Ord for HirId {} -impl !PartialOrd for HirId {} - -impl Debug for HirId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Example: HirId(DefId(0:1 ~ aa[7697]::{use#0}).10) - // Don't use debug_tuple to always keep this on one line. - write!(f, "HirId({:?}.{:?})", self.owner, self.local_id) - } -} - -impl HirId { - /// Signal local id which should never be used. - pub const INVALID: HirId = - HirId { owner: OwnerId { def_id: CRATE_DEF_ID }, local_id: ItemLocalId::INVALID }; - - #[inline] - pub fn expect_owner(self) -> OwnerId { - assert_eq!(self.local_id.index(), 0); - self.owner - } - - #[inline] - pub fn as_owner(self) -> Option { - if self.local_id.index() == 0 { Some(self.owner) } else { None } - } - - #[inline] - pub fn is_owner(self) -> bool { - self.local_id.index() == 0 - } - - #[inline] - pub fn make_owner(owner: LocalDefId) -> Self { - Self { owner: OwnerId { def_id: owner }, local_id: ItemLocalId::ZERO } - } -} - -impl fmt::Display for HirId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -rustc_data_structures::define_stable_id_collections!(HirIdMap, HirIdSet, HirIdMapEntry, HirId); -rustc_data_structures::define_id_collections!( - ItemLocalMap, - ItemLocalSet, - ItemLocalMapEntry, - ItemLocalId -); - -rustc_index::newtype_index! { - /// An `ItemLocalId` uniquely identifies something within a given "item-like"; - /// that is, within a `hir::Item`, `hir::TraitItem`, or `hir::ImplItem`. There is no - /// guarantee that the numerical value of a given `ItemLocalId` corresponds to - /// the node's position within the owning item in any way, but there is a - /// guarantee that the `ItemLocalId`s within an owner occupy a dense range of - /// integers starting at zero, so a mapping that maps all or most nodes within - /// an "item-like" to something else can be implemented by a `Vec` instead of a - /// tree or hash map. - #[derive(HashStable_Generic)] - #[encodable] - #[orderable] - pub struct ItemLocalId {} -} - -impl ItemLocalId { - /// Signal local id which should never be used. - pub const INVALID: ItemLocalId = ItemLocalId::MAX; -} - -impl StableOrd for ItemLocalId { - const CAN_USE_UNSTABLE_SORT: bool = true; - - // `Ord` is implemented as just comparing the ItemLocalId's numerical - // values and these are not changed by (de-)serialization. - const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = (); -} - -/// The `HirId` corresponding to `CRATE_NODE_ID` and `CRATE_DEF_ID`. -pub const CRATE_HIR_ID: HirId = - HirId { owner: OwnerId { def_id: CRATE_DEF_ID }, local_id: ItemLocalId::ZERO }; - -pub const CRATE_OWNER_ID: OwnerId = OwnerId { def_id: CRATE_DEF_ID }; diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index f33915d5b077..9b2f8ae75fa7 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -364,9 +364,6 @@ pub trait Visitor<'v>: Sized { /// All types are treated as ambiguous types for the purposes of hir visiting in /// order to ensure that visitors can handle infer vars without it being too error-prone. /// - /// See the doc comments on [`Ty`] for an explanation of what it means for a type to be - /// ambiguous. - /// /// The [`Visitor::visit_infer`] method should be overridden in order to handle infer vars. fn visit_ty(&mut self, t: &'v Ty<'v, AmbigArg>) -> Self::Result { walk_ty(self, t) @@ -375,12 +372,9 @@ pub trait Visitor<'v>: Sized { /// All consts are treated as ambiguous consts for the purposes of hir visiting in /// order to ensure that visitors can handle infer vars without it being too error-prone. /// - /// See the doc comments on [`ConstArg`] for an explanation of what it means for a const to be - /// ambiguous. - /// /// The [`Visitor::visit_infer`] method should be overridden in order to handle infer vars. fn visit_const_arg(&mut self, c: &'v ConstArg<'v, AmbigArg>) -> Self::Result { - walk_ambig_const_arg(self, c) + walk_const_arg(self, c) } #[allow(unused_variables)] @@ -522,7 +516,7 @@ pub trait VisitorExt<'v>: Visitor<'v> { /// Named `visit_const_arg_unambig` instead of `visit_unambig_const_arg` to aid in /// discovery by IDes when `v.visit_const_arg` is written. fn visit_const_arg_unambig(&mut self, c: &'v ConstArg<'v>) -> Self::Result { - walk_const_arg(self, c) + walk_unambig_const_arg(self, c) } } impl<'v, V: Visitor<'v>> VisitorExt<'v> for V {} @@ -596,21 +590,21 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V:: try_visit!(visitor.visit_generics(generics)); try_visit!(visitor.visit_enum_def(enum_definition)); } - ItemKind::Impl(Impl { - constness: _, - safety: _, - defaultness: _, - polarity: _, - defaultness_span: _, - generics, - of_trait, - self_ty, - items, - }) => { + ItemKind::Impl(Impl { generics, of_trait, self_ty, items }) => { try_visit!(visitor.visit_generics(generics)); - visit_opt!(visitor, visit_trait_ref, of_trait); + if let Some(TraitImplHeader { + constness: _, + safety: _, + polarity: _, + defaultness: _, + defaultness_span: _, + trait_ref, + }) = of_trait + { + try_visit!(visitor.visit_trait_ref(trait_ref)); + } try_visit!(visitor.visit_ty_unambig(self_ty)); - walk_list!(visitor, visit_impl_item_ref, *items); + walk_list!(visitor, visit_impl_item_ref, items); } ItemKind::Struct(ident, ref generics, ref struct_definition) | ItemKind::Union(ident, ref generics, ref struct_definition) => { @@ -985,7 +979,6 @@ pub fn walk_unambig_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v>) -> Some(ambig_ty) => visitor.visit_ty(ambig_ty), None => { let Ty { hir_id, span, kind: _ } = typ; - try_visit!(visitor.visit_id(*hir_id)); visitor.visit_infer(*hir_id, *span, InferKind::Ty(typ)) } } @@ -1043,7 +1036,7 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v, AmbigArg>) - V::Result::output() } -pub fn walk_const_arg<'v, V: Visitor<'v>>( +pub fn walk_unambig_const_arg<'v, V: Visitor<'v>>( visitor: &mut V, const_arg: &'v ConstArg<'v>, ) -> V::Result { @@ -1051,13 +1044,12 @@ pub fn walk_const_arg<'v, V: Visitor<'v>>( Some(ambig_ct) => visitor.visit_const_arg(ambig_ct), None => { let ConstArg { hir_id, kind: _ } = const_arg; - try_visit!(visitor.visit_id(*hir_id)); visitor.visit_infer(*hir_id, const_arg.span(), InferKind::Const(const_arg)) } } } -pub fn walk_ambig_const_arg<'v, V: Visitor<'v>>( +pub fn walk_const_arg<'v, V: Visitor<'v>>( visitor: &mut V, const_arg: &'v ConstArg<'v, AmbigArg>, ) -> V::Result { diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 75c04b23ed6e..905b84a8cbeb 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -286,6 +286,7 @@ language_item_table! { Panic, sym::panic, panic_fn, Target::Fn, GenericRequirement::Exact(0); PanicNounwind, sym::panic_nounwind, panic_nounwind, Target::Fn, GenericRequirement::Exact(0); PanicFmt, sym::panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None; + PanicDisplay, sym::panic_display, panic_display, Target::Fn, GenericRequirement::None; ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0); PanicMisalignedPointerDereference, sym::panic_misaligned_pointer_dereference, panic_misaligned_pointer_dereference_fn, Target::Fn, GenericRequirement::Exact(0); diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs index f1212d07ff60..78fc63753a2e 100644 --- a/compiler/rustc_hir/src/lib.rs +++ b/compiler/rustc_hir/src/lib.rs @@ -3,14 +3,11 @@ //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/hir.html // tidy-alphabetical-start -#![allow(internal_features)] #![feature(associated_type_defaults)] #![feature(closure_track_caller)] #![feature(debug_closure_helpers)] #![feature(exhaustive_patterns)] -#![feature(negative_impls)] #![feature(never_type)] -#![feature(rustc_attrs)] #![feature(variant_count)] #![recursion_limit = "256"] // tidy-alphabetical-end @@ -25,7 +22,7 @@ pub mod definitions; pub mod diagnostic_items; pub use rustc_span::def_id; mod hir; -pub mod hir_id; +pub use rustc_hir_id::{self as hir_id, *}; pub mod intravisit; pub mod lang_items; pub mod lints; @@ -41,7 +38,6 @@ mod tests; #[doc(no_inline)] pub use hir::*; -pub use hir_id::*; pub use lang_items::{LangItem, LanguageItems}; pub use stability::*; pub use stable_hash_impls::HashStableContext; diff --git a/compiler/rustc_hir/src/lints.rs b/compiler/rustc_hir/src/lints.rs index c55a41eb2b76..061ec786dc80 100644 --- a/compiler/rustc_hir/src/lints.rs +++ b/compiler/rustc_hir/src/lints.rs @@ -2,7 +2,7 @@ use rustc_data_structures::fingerprint::Fingerprint; use rustc_macros::HashStable_Generic; use rustc_span::Span; -use crate::HirId; +use crate::{AttrPath, HirId, Target}; #[derive(Debug)] pub struct DelayedLints { @@ -34,4 +34,5 @@ pub enum AttributeLintKind { UnusedDuplicate { this: Span, other: Span, warning: bool }, IllFormedAttributeInput { suggestions: Vec }, EmptyAttribute { first_span: Span }, + InvalidTarget { name: AttrPath, target: Target, applied: Vec, only: &'static str }, } diff --git a/compiler/rustc_hir/src/stable_hash_impls.rs b/compiler/rustc_hir/src/stable_hash_impls.rs index ecc608d437b8..16e8bac3d8a4 100644 --- a/compiler/rustc_hir/src/stable_hash_impls.rs +++ b/compiler/rustc_hir/src/stable_hash_impls.rs @@ -5,7 +5,7 @@ use crate::HashIgnoredAttrId; use crate::hir::{ AttributeMap, BodyId, Crate, ForeignItemId, ImplItemId, ItemId, OwnerNodes, TraitItemId, }; -use crate::hir_id::{HirId, ItemLocalId}; +use crate::hir_id::ItemLocalId; use crate::lints::DelayedLints; /// Requirements for a `StableHashingContext` to be used in this crate. @@ -15,25 +15,6 @@ pub trait HashStableContext: rustc_ast::HashStableContext + rustc_abi::HashStabl fn hash_attr_id(&mut self, id: &HashIgnoredAttrId, hasher: &mut StableHasher); } -impl ToStableHashKey for HirId { - type KeyType = (DefPathHash, ItemLocalId); - - #[inline] - fn to_stable_hash_key(&self, hcx: &HirCtx) -> (DefPathHash, ItemLocalId) { - let def_path_hash = self.owner.def_id.to_stable_hash_key(hcx); - (def_path_hash, self.local_id) - } -} - -impl ToStableHashKey for ItemLocalId { - type KeyType = ItemLocalId; - - #[inline] - fn to_stable_hash_key(&self, _: &HirCtx) -> ItemLocalId { - *self - } -} - impl ToStableHashKey for BodyId { type KeyType = (DefPathHash, ItemLocalId); diff --git a/compiler/rustc_hir/src/target.rs b/compiler/rustc_hir/src/target.rs index d617f44f8d8e..dcac51b10b49 100644 --- a/compiler/rustc_hir/src/target.rs +++ b/compiler/rustc_hir/src/target.rs @@ -6,23 +6,34 @@ use std::fmt::{self, Display}; +use rustc_ast::visit::AssocCtxt; +use rustc_ast::{AssocItemKind, ForeignItemKind, ast}; +use rustc_macros::HashStable_Generic; + use crate::def::DefKind; use crate::{Item, ItemKind, TraitItem, TraitItemKind, hir}; -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Eq, HashStable_Generic)] pub enum GenericParamKind { Type, Lifetime, Const, } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Eq, HashStable_Generic)] pub enum MethodKind { - Trait { body: bool }, + /// Method in a `trait Trait` block + Trait { + /// Whether a default is provided for this method + body: bool, + }, + /// Method in a `impl Trait for Type` block + TraitImpl, + /// Method in a `impl Type` block Inherent, } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Eq, HashStable_Generic)] pub enum Target { ExternCrate, Use, @@ -57,6 +68,9 @@ pub enum Target { PatField, ExprField, WherePredicate, + MacroCall, + Crate, + Delegation { mac: bool }, } impl Display for Target { @@ -65,6 +79,8 @@ impl Display for Target { } } +rustc_error_messages::into_diag_arg_using_display!(Target); + impl Target { pub fn is_associated_item(self) -> bool { match self { @@ -98,7 +114,10 @@ impl Target { | Target::Param | Target::PatField | Target::ExprField - | Target::WherePredicate => false, + | Target::MacroCall + | Target::Crate + | Target::WherePredicate + | Target::Delegation { .. } => false, } } @@ -146,6 +165,39 @@ impl Target { } } + pub fn from_ast_item(item: &ast::Item) -> Target { + match item.kind { + ast::ItemKind::ExternCrate(..) => Target::ExternCrate, + ast::ItemKind::Use(..) => Target::Use, + ast::ItemKind::Static { .. } => Target::Static, + ast::ItemKind::Const(..) => Target::Const, + ast::ItemKind::Fn { .. } => Target::Fn, + ast::ItemKind::Mod(..) => Target::Mod, + ast::ItemKind::ForeignMod { .. } => Target::ForeignMod, + ast::ItemKind::GlobalAsm { .. } => Target::GlobalAsm, + ast::ItemKind::TyAlias(..) => Target::TyAlias, + ast::ItemKind::Enum(..) => Target::Enum, + ast::ItemKind::Struct(..) => Target::Struct, + ast::ItemKind::Union(..) => Target::Union, + ast::ItemKind::Trait(..) => Target::Trait, + ast::ItemKind::TraitAlias(..) => Target::TraitAlias, + ast::ItemKind::Impl(ref i) => Target::Impl { of_trait: i.of_trait.is_some() }, + ast::ItemKind::MacCall(..) => Target::MacroCall, + ast::ItemKind::MacroDef(..) => Target::MacroDef, + ast::ItemKind::Delegation(..) => Target::Delegation { mac: false }, + ast::ItemKind::DelegationMac(..) => Target::Delegation { mac: true }, + } + } + + pub fn from_foreign_item_kind(kind: &ast::ForeignItemKind) -> Target { + match kind { + ForeignItemKind::Static(_) => Target::ForeignStatic, + ForeignItemKind::Fn(_) => Target::ForeignFn, + ForeignItemKind::TyAlias(_) => Target::ForeignTy, + ForeignItemKind::MacCall(_) => Target::MacroCall, + } + } + pub fn from_trait_item(trait_item: &TraitItem<'_>) -> Target { match trait_item.kind { TraitItemKind::Const(..) => Target::AssocConst, @@ -183,12 +235,40 @@ impl Target { } } + pub fn from_assoc_item_kind(kind: &ast::AssocItemKind, assoc_ctxt: AssocCtxt) -> Target { + match kind { + AssocItemKind::Const(_) => Target::AssocConst, + AssocItemKind::Fn(f) => Target::Method(match assoc_ctxt { + AssocCtxt::Trait => MethodKind::Trait { body: f.body.is_some() }, + AssocCtxt::Impl { of_trait } => { + if of_trait { + MethodKind::TraitImpl + } else { + MethodKind::Inherent + } + } + }), + AssocItemKind::Type(_) => Target::AssocTy, + AssocItemKind::Delegation(_) => Target::Delegation { mac: false }, + AssocItemKind::DelegationMac(_) => Target::Delegation { mac: true }, + AssocItemKind::MacCall(_) => Target::MacroCall, + } + } + + pub fn from_expr(expr: &ast::Expr) -> Self { + match &expr.kind { + ast::ExprKind::Closure(..) | ast::ExprKind::Gen(..) => Self::Closure, + ast::ExprKind::Paren(e) => Self::from_expr(&e), + _ => Self::Expression, + } + } + pub fn name(self) -> &'static str { match self { Target::ExternCrate => "extern crate", Target::Use => "use", - Target::Static => "static item", - Target::Const => "constant item", + Target::Static => "static", + Target::Const => "constant", Target::Fn => "function", Target::Closure => "closure", Target::Mod => "module", @@ -202,8 +282,7 @@ impl Target { Target::Union => "union", Target::Trait => "trait", Target::TraitAlias => "trait alias", - Target::Impl { of_trait: false } => "inherent implementation block", - Target::Impl { of_trait: true } => "trait implementation block", + Target::Impl { .. } => "implementation block", Target::Expression => "expression", Target::Statement => "statement", Target::Arm => "match arm", @@ -212,12 +291,13 @@ impl Target { MethodKind::Inherent => "inherent method", MethodKind::Trait { body: false } => "required trait method", MethodKind::Trait { body: true } => "provided trait method", + MethodKind::TraitImpl => "trait method in an impl block", }, Target::AssocTy => "associated type", Target::ForeignFn => "foreign function", Target::ForeignStatic => "foreign static item", Target::ForeignTy => "foreign type", - Target::GenericParam { kind, has_default: _ } => match kind { + Target::GenericParam { kind, .. } => match kind { GenericParamKind::Type => "type parameter", GenericParamKind::Lifetime => "lifetime parameter", GenericParamKind::Const => "const parameter", @@ -227,6 +307,60 @@ impl Target { Target::PatField => "pattern field", Target::ExprField => "struct field", Target::WherePredicate => "where predicate", + Target::MacroCall => "macro call", + Target::Crate => "crate", + Target::Delegation { .. } => "delegation", + } + } + + pub fn plural_name(self) -> &'static str { + match self { + Target::ExternCrate => "extern crates", + Target::Use => "use statements", + Target::Static => "statics", + Target::Const => "constants", + Target::Fn => "functions", + Target::Closure => "closures", + Target::Mod => "modules", + Target::ForeignMod => "foreign modules", + Target::GlobalAsm => "global asms", + Target::TyAlias => "type aliases", + Target::Enum => "enums", + Target::Variant => "enum variants", + Target::Struct => "structs", + Target::Field => "struct fields", + Target::Union => "unions", + Target::Trait => "traits", + Target::TraitAlias => "trait aliases", + Target::Impl { of_trait: false } => "inherent impl blocks", + Target::Impl { of_trait: true } => "trait impl blocks", + Target::Expression => "expressions", + Target::Statement => "statements", + Target::Arm => "match arms", + Target::AssocConst => "associated consts", + Target::Method(kind) => match kind { + MethodKind::Inherent => "inherent methods", + MethodKind::Trait { body: false } => "required trait methods", + MethodKind::Trait { body: true } => "provided trait methods", + MethodKind::TraitImpl => "trait methods in impl blocks", + }, + Target::AssocTy => "associated types", + Target::ForeignFn => "foreign functions", + Target::ForeignStatic => "foreign statics", + Target::ForeignTy => "foreign types", + Target::GenericParam { kind, has_default: _ } => match kind { + GenericParamKind::Type => "type parameters", + GenericParamKind::Lifetime => "lifetime parameters", + GenericParamKind::Const => "const parameters", + }, + Target::MacroDef => "macro defs", + Target::Param => "function params", + Target::PatField => "pattern fields", + Target::ExprField => "struct fields", + Target::WherePredicate => "where predicates", + Target::MacroCall => "macro calls", + Target::Crate => "crates", + Target::Delegation { .. } => "delegations", } } } diff --git a/compiler/rustc_hir/src/version.rs b/compiler/rustc_hir/src/version.rs index ab5ab026b4ca..bc2c38a49350 100644 --- a/compiler/rustc_hir/src/version.rs +++ b/compiler/rustc_hir/src/version.rs @@ -1,6 +1,8 @@ +use std::borrow::Cow; use std::fmt::{self, Display}; use std::sync::OnceLock; +use rustc_error_messages::{DiagArgValue, IntoDiagArg}; use rustc_macros::{ Decodable, Encodable, HashStable_Generic, PrintAttribute, current_rustc_version, }; @@ -45,3 +47,9 @@ impl Display for RustcVersion { write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch) } } + +impl IntoDiagArg for RustcVersion { + fn into_diag_arg(self, _: &mut Option) -> DiagArgValue { + DiagArgValue::Str(Cow::Owned(self.to_string())) + } +} diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index e24426f9fedc..e48272561939 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -356,14 +356,14 @@ fn compare_method_predicate_entailment<'tcx>( } if !(impl_sig, trait_sig).references_error() { - ocx.register_obligation(traits::Obligation::new( - infcx.tcx, - cause, - param_env, - ty::ClauseKind::WellFormed( - Ty::new_fn_ptr(tcx, ty::Binder::dummy(unnormalized_impl_sig)).into(), - ), - )); + for ty in unnormalized_impl_sig.inputs_and_output { + ocx.register_obligation(traits::Obligation::new( + infcx.tcx, + cause.clone(), + param_env, + ty::ClauseKind::WellFormed(ty.into()), + )); + } } // Check that all obligations are satisfied by the implementation's @@ -425,7 +425,7 @@ impl<'tcx> TypeFolder> for RemapLateParam<'tcx> { /// /// trait Foo { /// fn bar() -> impl Deref; -/// // ^- RPITIT #1 ^- RPITIT #2 +/// // ^- RPITIT #1 ^- RPITIT #2 /// } /// /// impl Foo for () { @@ -445,10 +445,10 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( tcx: TyCtxt<'tcx>, impl_m_def_id: LocalDefId, ) -> Result<&'tcx DefIdMap>>, ErrorGuaranteed> { - let impl_m = tcx.opt_associated_item(impl_m_def_id.to_def_id()).unwrap(); - let trait_m = tcx.opt_associated_item(impl_m.trait_item_def_id.unwrap()).unwrap(); + let impl_m = tcx.associated_item(impl_m_def_id.to_def_id()); + let trait_m = tcx.associated_item(impl_m.trait_item_def_id.unwrap()); let impl_trait_ref = - tcx.impl_trait_ref(impl_m.impl_container(tcx).unwrap()).unwrap().instantiate_identity(); + tcx.impl_trait_ref(tcx.parent(impl_m_def_id.to_def_id())).unwrap().instantiate_identity(); // First, check a few of the same things as `compare_impl_method`, // just so we don't ICE during instantiation later. check_method_is_structurally_compatible(tcx, impl_m, trait_m, impl_trait_ref, true)?; @@ -2498,7 +2498,7 @@ fn param_env_with_gat_bounds<'tcx>( ty::Const::new_bound( tcx, ty::INNERMOST, - ty::BoundVar::from_usize(bound_vars.len() - 1), + ty::BoundConst { var: ty::BoundVar::from_usize(bound_vars.len() - 1) }, ) .into() } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 6e5fe3823ab5..cfc6bc2f3a0a 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -135,6 +135,11 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::round_ties_even_f32 | sym::round_ties_even_f64 | sym::round_ties_even_f128 + | sym::autodiff + | sym::prefetch_read_data + | sym::prefetch_write_data + | sym::prefetch_read_instruction + | sym::prefetch_write_instruction | sym::const_eval_select => hir::Safety::Safe, _ => hir::Safety::Unsafe, }; @@ -198,6 +203,7 @@ pub(crate) fn check_intrinsic_type( let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let n_lts = 0; let (n_tps, n_cts, inputs, output) = match intrinsic_name { + sym::autodiff => (4, 0, vec![param(0), param(1), param(2)], param(3)), sym::abort => (0, 0, vec![], tcx.types.never), sym::unreachable => (0, 0, vec![], tcx.types.never), sym::breakpoint => (0, 0, vec![], tcx.types.unit), @@ -216,7 +222,7 @@ pub(crate) fn check_intrinsic_type( | sym::prefetch_write_data | sym::prefetch_read_instruction | sym::prefetch_write_instruction => { - (1, 0, vec![Ty::new_imm_ptr(tcx, param(0)), tcx.types.i32], tcx.types.unit) + (1, 1, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.unit) } sym::needs_drop => (1, 0, vec![], tcx.types.bool), @@ -652,16 +658,16 @@ pub(crate) fn check_intrinsic_type( sym::atomic_store => (1, 1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], tcx.types.unit), sym::atomic_xchg - | sym::atomic_xadd - | sym::atomic_xsub - | sym::atomic_and - | sym::atomic_nand - | sym::atomic_or - | sym::atomic_xor | sym::atomic_max | sym::atomic_min | sym::atomic_umax | sym::atomic_umin => (1, 1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], param(0)), + sym::atomic_xadd + | sym::atomic_xsub + | sym::atomic_and + | sym::atomic_nand + | sym::atomic_or + | sym::atomic_xor => (2, 1, vec![Ty::new_mut_ptr(tcx, param(0)), param(1)], param(0)), sym::atomic_fence | sym::atomic_singlethreadfence => (0, 1, Vec::new(), tcx.types.unit), other => { diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index b2cfab37c1f6..85445cb3c004 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -493,7 +493,7 @@ fn suggestion_signature<'tcx>( let args = ty::GenericArgs::identity_for_item(tcx, assoc.def_id).rebase_onto( tcx, assoc.container_id(tcx), - impl_trait_ref.with_self_ty(tcx, tcx.types.self_param).args, + impl_trait_ref.with_replaced_self_ty(tcx, tcx.types.self_param).args, ); match assoc.kind { diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 95f6fba6487a..f5770b7312dd 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -199,6 +199,11 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: resolve_pat(visitor, arm.pat); if let Some(guard) = arm.guard { + // We introduce a new scope to contain bindings and temporaries from `if let` guards, to + // ensure they're dropped before the arm's pattern's bindings. This extends to the end of + // the arm body and is the scope of its locals as well. + visitor.enter_scope(Scope { local_id: arm.hir_id.local_id, data: ScopeData::MatchGuard }); + visitor.cx.var_parent = visitor.cx.parent; resolve_cond(visitor, guard); } resolve_expr(visitor, arm.body, false); diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index a62efed13bc7..e6a1f6d8d8bb 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -244,48 +244,48 @@ pub(super) fn check_item<'tcx>( // // won't be allowed unless there's an *explicit* implementation of `Send` // for `T` - hir::ItemKind::Impl(impl_) => { - let header = tcx.impl_trait_header(def_id); - let is_auto = header - .is_some_and(|header| tcx.trait_is_auto(header.trait_ref.skip_binder().def_id)); - + hir::ItemKind::Impl(ref impl_) => { crate::impl_wf_check::check_impl_wf(tcx, def_id)?; let mut res = Ok(()); - if let (hir::Defaultness::Default { .. }, true) = (impl_.defaultness, is_auto) { - let sp = impl_.of_trait.as_ref().map_or(item.span, |t| t.path.span); - res = Err(tcx - .dcx() - .struct_span_err(sp, "impls of auto traits cannot be default") - .with_span_labels(impl_.defaultness_span, "default because of this") - .with_span_label(sp, "auto trait") - .emit()); - } - // We match on both `ty::ImplPolarity` and `ast::ImplPolarity` just to get the `!` span. - match header.map(|h| h.polarity) { - // `None` means this is an inherent impl - Some(ty::ImplPolarity::Positive) | None => { - res = res.and(check_impl(tcx, item, impl_.self_ty, &impl_.of_trait)); - } - Some(ty::ImplPolarity::Negative) => { - let ast::ImplPolarity::Negative(span) = impl_.polarity else { - bug!("impl_polarity query disagrees with impl's polarity in HIR"); - }; - // FIXME(#27579): what amount of WF checking do we need for neg impls? - if let hir::Defaultness::Default { .. } = impl_.defaultness { - let mut spans = vec![span]; - spans.extend(impl_.defaultness_span); - res = Err(struct_span_code_err!( - tcx.dcx(), - spans, - E0750, - "negative impls cannot be default impls" - ) + if let Some(of_trait) = impl_.of_trait { + let header = tcx.impl_trait_header(def_id).unwrap(); + let is_auto = tcx.trait_is_auto(header.trait_ref.skip_binder().def_id); + if let (hir::Defaultness::Default { .. }, true) = (of_trait.defaultness, is_auto) { + let sp = of_trait.trait_ref.path.span; + res = Err(tcx + .dcx() + .struct_span_err(sp, "impls of auto traits cannot be default") + .with_span_labels(of_trait.defaultness_span, "default because of this") + .with_span_label(sp, "auto trait") .emit()); - } } - Some(ty::ImplPolarity::Reservation) => { - // FIXME: what amount of WF checking do we need for reservation impls? + match header.polarity { + ty::ImplPolarity::Positive => { + res = res.and(check_impl(tcx, item, impl_)); + } + ty::ImplPolarity::Negative => { + let ast::ImplPolarity::Negative(span) = of_trait.polarity else { + bug!("impl_polarity query disagrees with impl's polarity in HIR"); + }; + // FIXME(#27579): what amount of WF checking do we need for neg impls? + if let hir::Defaultness::Default { .. } = of_trait.defaultness { + let mut spans = vec![span]; + spans.extend(of_trait.defaultness_span); + res = Err(struct_span_code_err!( + tcx.dcx(), + spans, + E0750, + "negative impls cannot be default impls" + ) + .emit()); + } + } + ty::ImplPolarity::Reservation => { + // FIXME: what amount of WF checking do we need for reservation impls? + } } + } else { + res = res.and(check_impl(tcx, item, impl_)); } res } @@ -1258,16 +1258,15 @@ pub(crate) fn check_const_item(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<() }) } -#[instrument(level = "debug", skip(tcx, hir_self_ty, hir_trait_ref))] +#[instrument(level = "debug", skip(tcx, impl_))] fn check_impl<'tcx>( tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>, - hir_self_ty: &hir::Ty<'_>, - hir_trait_ref: &Option>, + impl_: &hir::Impl<'_>, ) -> Result<(), ErrorGuaranteed> { enter_wf_checking_ctxt(tcx, item.owner_id.def_id, |wfcx| { - match hir_trait_ref { - Some(hir_trait_ref) => { + match impl_.of_trait { + Some(of_trait) => { // `#[rustc_reservation_impl]` impls are not real impls and // therefore don't need to be WF (the trait's `Self: Trait` predicate // won't hold). @@ -1275,7 +1274,7 @@ fn check_impl<'tcx>( // Avoid bogus "type annotations needed `Foo: Bar`" errors on `impl Bar for Foo` in case // other `Foo` impls are incoherent. tcx.ensure_ok().coherent_trait(trait_ref.def_id)?; - let trait_span = hir_trait_ref.path.span; + let trait_span = of_trait.trait_ref.path.span; let trait_ref = wfcx.deeply_normalize( trait_span, Some(WellFormedLoc::Ty(item.hir_id().expect_owner().def_id)), @@ -1299,12 +1298,12 @@ fn check_impl<'tcx>( if let Some(pred) = obligation.predicate.as_trait_clause() && pred.skip_binder().self_ty() == trait_ref.self_ty() { - obligation.cause.span = hir_self_ty.span; + obligation.cause.span = impl_.self_ty.span; } if let Some(pred) = obligation.predicate.as_projection_clause() && pred.skip_binder().self_ty() == trait_ref.self_ty() { - obligation.cause.span = hir_self_ty.span; + obligation.cause.span = impl_.self_ty.span; } } @@ -1321,7 +1320,7 @@ fn check_impl<'tcx>( wfcx.register_obligation(Obligation::new( tcx, ObligationCause::new( - hir_self_ty.span, + impl_.self_ty.span, wfcx.body_def_id, ObligationCauseCode::WellFormed(None), ), @@ -1342,7 +1341,7 @@ fn check_impl<'tcx>( self_ty, ); wfcx.register_wf_obligation( - hir_self_ty.span, + impl_.self_ty.span, Some(WellFormedLoc::Ty(item.hir_id().expect_owner().def_id)), self_ty.into(), ); @@ -2288,8 +2287,7 @@ fn lint_redundant_lifetimes<'tcx>( // Proceed } DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => { - let parent_def_id = tcx.local_parent(owner_id); - if matches!(tcx.def_kind(parent_def_id), DefKind::Impl { of_trait: true }) { + if tcx.trait_impl_of_assoc(owner_id.to_def_id()).is_some() { // Don't check for redundant lifetimes for associated items of trait // implementations, since the signature is required to be compatible // with the trait, even if the implementation implies some lifetimes diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 27948f50a4ad..32b175611ceb 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -531,8 +531,10 @@ pub(crate) fn coerce_unsized_info<'tcx>( })); } else if diff_fields.len() > 1 { let item = tcx.hir_expect_item(impl_did); - let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(t), .. }) = &item.kind { - t.path.span + let span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = + &item.kind + { + of_trait.trait_ref.path.span } else { tcx.def_span(impl_did) }; diff --git a/compiler/rustc_hir_analysis/src/coherence/orphan.rs b/compiler/rustc_hir_analysis/src/coherence/orphan.rs index c75fef9f716d..f707196c8163 100644 --- a/compiler/rustc_hir_analysis/src/coherence/orphan.rs +++ b/compiler/rustc_hir_analysis/src/coherence/orphan.rs @@ -384,7 +384,7 @@ fn emit_orphan_check_error<'tcx>( traits::OrphanCheckErr::NonLocalInputType(tys) => { let item = tcx.hir_expect_item(impl_def_id); let impl_ = item.expect_impl(); - let hir_trait_ref = impl_.of_trait.as_ref().unwrap(); + let of_trait = impl_.of_trait.unwrap(); let span = tcx.def_span(impl_def_id); let mut diag = tcx.dcx().create_err(match trait_ref.self_ty().kind() { @@ -401,7 +401,7 @@ fn emit_orphan_check_error<'tcx>( impl_.self_ty.span } else { // Point at `C` in `impl for C in D` - hir_trait_ref.path.span + of_trait.trait_ref.path.span }; ty = tcx.erase_regions(ty); diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 8ccbfbbb3b49..b72e743f95b0 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1295,18 +1295,22 @@ fn impl_trait_header(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option, - def_id: LocalDefId, - impl_: &hir::Impl<'_>, - span: Span, + of_trait: &hir::TraitImplHeader<'_>, + is_rustc_reservation: bool, ) -> ty::ImplPolarity { - let is_rustc_reservation = tcx.has_attr(def_id, sym::rustc_reservation_impl); - match &impl_ { - hir::Impl { polarity: hir::ImplPolarity::Negative(span), of_trait, .. } => { + match of_trait.polarity { + hir::ImplPolarity::Negative(span) => { if is_rustc_reservation { - let span = span.to(of_trait.as_ref().map_or(*span, |t| t.path.span)); + let span = span.to(of_trait.trait_ref.path.span); tcx.dcx().span_err(span, "reservation impls can't be negative"); } ty::ImplPolarity::Negative } - hir::Impl { polarity: hir::ImplPolarity::Positive, of_trait: None, .. } => { - if is_rustc_reservation { - tcx.dcx().span_err(span, "reservation impls can't be inherent"); - } - ty::ImplPolarity::Positive - } - hir::Impl { polarity: hir::ImplPolarity::Positive, of_trait: Some(_), .. } => { + hir::ImplPolarity::Positive => { if is_rustc_reservation { ty::ImplPolarity::Reservation } else { diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index e2462c2d4659..ce0e51f106f5 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -223,60 +223,27 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { "synthetic HIR should have its `generics_of` explicitly fed" ), - _ => span_bug!(tcx.def_span(def_id), "unhandled node {node:?}"), + _ => span_bug!(tcx.def_span(def_id), "generics_of: unexpected node kind {node:?}"), }; - enum Defaults { - Allowed, - // See #36887 - FutureCompatDisallowed, - Deny, - } - - let hir_generics = node.generics().unwrap_or(hir::Generics::empty()); - let (opt_self, allow_defaults) = match node { - Node::Item(item) => { - match item.kind { - ItemKind::Trait(..) | ItemKind::TraitAlias(..) => { - // Add in the self type parameter. - // - // Something of a hack: use the node id for the trait, also as - // the node id for the Self type parameter. - let opt_self = Some(ty::GenericParamDef { - index: 0, - name: kw::SelfUpper, - def_id: def_id.to_def_id(), - pure_wrt_drop: false, - kind: ty::GenericParamDefKind::Type { - has_default: false, - synthetic: false, - }, - }); - - (opt_self, Defaults::Allowed) - } - ItemKind::TyAlias(..) - | ItemKind::Enum(..) - | ItemKind::Struct(..) - | ItemKind::Union(..) => (None, Defaults::Allowed), - ItemKind::Const(..) => (None, Defaults::Deny), - _ => (None, Defaults::FutureCompatDisallowed), - } - } - - Node::OpaqueTy(..) => (None, Defaults::Allowed), - - // GATs - Node::TraitItem(item) if matches!(item.kind, TraitItemKind::Type(..)) => { - (None, Defaults::Deny) - } - Node::ImplItem(item) if matches!(item.kind, ImplItemKind::Type(..)) => { - (None, Defaults::Deny) - } - - _ => (None, Defaults::FutureCompatDisallowed), + // Add in the self type parameter. + let opt_self = if let Node::Item(item) = node + && let ItemKind::Trait(..) | ItemKind::TraitAlias(..) = item.kind + { + // Something of a hack: We reuse the node ID of the trait for the self type parameter. + Some(ty::GenericParamDef { + index: 0, + name: kw::SelfUpper, + def_id: def_id.to_def_id(), + pure_wrt_drop: false, + kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false }, + }) + } else { + None }; + let param_default_policy = param_default_policy(node); + let hir_generics = node.generics().unwrap_or(hir::Generics::empty()); let has_self = opt_self.is_some(); let mut parent_has_self = false; let mut own_start = has_self as u32; @@ -312,60 +279,53 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { prev + type_start }; - const TYPE_DEFAULT_NOT_ALLOWED: &'static str = "defaults for type parameters are only allowed in \ - `struct`, `enum`, `type`, or `trait` definitions"; - - own_params.extend(hir_generics.params.iter().filter_map(|param| match param.kind { - GenericParamKind::Lifetime { .. } => None, - GenericParamKind::Type { default, synthetic, .. } => { - if default.is_some() { - match allow_defaults { - Defaults::Allowed => {} - Defaults::FutureCompatDisallowed => { - tcx.node_span_lint( - lint::builtin::INVALID_TYPE_PARAM_DEFAULT, - param.hir_id, - param.span, - |lint| { - lint.primary_message(TYPE_DEFAULT_NOT_ALLOWED); - }, - ); - } - Defaults::Deny => { - tcx.dcx().span_err(param.span, TYPE_DEFAULT_NOT_ALLOWED); + own_params.extend(hir_generics.params.iter().filter_map(|param| { + const MESSAGE: &str = "defaults for generic parameters are not allowed here"; + let kind = match param.kind { + GenericParamKind::Lifetime { .. } => return None, + GenericParamKind::Type { default, synthetic } => { + if default.is_some() { + match param_default_policy.expect("no policy for generic param default") { + ParamDefaultPolicy::Allowed => {} + ParamDefaultPolicy::FutureCompatForbidden => { + tcx.node_span_lint( + lint::builtin::INVALID_TYPE_PARAM_DEFAULT, + param.hir_id, + param.span, + |lint| { + lint.primary_message(MESSAGE); + }, + ); + } + ParamDefaultPolicy::Forbidden => { + tcx.dcx().span_err(param.span, MESSAGE); + } } } - } - - let kind = ty::GenericParamDefKind::Type { has_default: default.is_some(), synthetic }; - Some(ty::GenericParamDef { - index: next_index(), - name: param.name.ident().name, - def_id: param.def_id.to_def_id(), - pure_wrt_drop: param.pure_wrt_drop, - kind, - }) - } - GenericParamKind::Const { ty: _, default, synthetic } => { - if !matches!(allow_defaults, Defaults::Allowed) && default.is_some() { - tcx.dcx().span_err( - param.span, - "defaults for const parameters are only allowed in \ - `struct`, `enum`, `type`, or `trait` definitions", - ); + ty::GenericParamDefKind::Type { has_default: default.is_some(), synthetic } } + GenericParamKind::Const { ty: _, default, synthetic } => { + if default.is_some() { + match param_default_policy.expect("no policy for generic param default") { + ParamDefaultPolicy::Allowed => {} + ParamDefaultPolicy::FutureCompatForbidden + | ParamDefaultPolicy::Forbidden => { + tcx.dcx().span_err(param.span, MESSAGE); + } + } + } - let index = next_index(); - - Some(ty::GenericParamDef { - index, - name: param.name.ident().name, - def_id: param.def_id.to_def_id(), - pure_wrt_drop: param.pure_wrt_drop, - kind: ty::GenericParamDefKind::Const { has_default: default.is_some(), synthetic }, - }) - } + ty::GenericParamDefKind::Const { has_default: default.is_some(), synthetic } + } + }; + Some(ty::GenericParamDef { + index: next_index(), + name: param.name.ident().name, + def_id: param.def_id.to_def_id(), + pure_wrt_drop: param.pure_wrt_drop, + kind, + }) })); // provide junk type parameter defs - the only place that @@ -438,6 +398,48 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { } } +#[derive(Clone, Copy)] +enum ParamDefaultPolicy { + Allowed, + /// Tracked in . + FutureCompatForbidden, + Forbidden, +} + +fn param_default_policy(node: Node<'_>) -> Option { + use rustc_hir::*; + + Some(match node { + Node::Item(item) => match item.kind { + ItemKind::Trait(..) + | ItemKind::TraitAlias(..) + | ItemKind::TyAlias(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) => ParamDefaultPolicy::Allowed, + ItemKind::Fn { .. } | ItemKind::Impl(_) => ParamDefaultPolicy::FutureCompatForbidden, + // Re. GCI, we're not bound by backward compatibility. + ItemKind::Const(..) => ParamDefaultPolicy::Forbidden, + _ => return None, + }, + Node::TraitItem(item) => match item.kind { + // Re. GATs and GACs (generic_const_items), we're not bound by backward compatibility. + TraitItemKind::Const(..) | TraitItemKind::Type(..) => ParamDefaultPolicy::Forbidden, + TraitItemKind::Fn(..) => ParamDefaultPolicy::FutureCompatForbidden, + }, + Node::ImplItem(item) => match item.kind { + // Re. GATs and GACs (generic_const_items), we're not bound by backward compatibility. + ImplItemKind::Const(..) | ImplItemKind::Type(..) => ParamDefaultPolicy::Forbidden, + ImplItemKind::Fn(..) => ParamDefaultPolicy::FutureCompatForbidden, + }, + // Generic params are (semantically) invalid on foreign items. Still, for maximum forward + // compatibility, let's hard-reject defaults on them. + Node::ForeignItem(_) => ParamDefaultPolicy::Forbidden, + Node::OpaqueTy(..) => ParamDefaultPolicy::Allowed, + _ => return None, + }) +} + fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option { struct LateBoundRegionsDetector<'tcx> { tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index 548ba343aaee..ba54fa8cc0db 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -189,7 +189,7 @@ fn remap_gat_vars_and_recurse_into_nested_projections<'tcx>( } ty::GenericArgKind::Const(ct) => { if let ty::ConstKind::Bound(ty::INNERMOST, bv) = ct.kind() { - mapping.insert(bv, tcx.mk_param_from_def(param)) + mapping.insert(bv.var, tcx.mk_param_from_def(param)) } else { return None; } @@ -307,16 +307,16 @@ impl<'tcx> TypeFolder> for MapAndCompressBoundVars<'tcx> { return ct; } - if let ty::ConstKind::Bound(binder, old_var) = ct.kind() + if let ty::ConstKind::Bound(binder, old_bound) = ct.kind() && self.binder == binder { - let mapped = if let Some(mapped) = self.mapping.get(&old_var) { + let mapped = if let Some(mapped) = self.mapping.get(&old_bound.var) { mapped.expect_const() } else { let var = ty::BoundVar::from_usize(self.still_bound_vars.len()); self.still_bound_vars.push(ty::BoundVariableKind::Const); - let mapped = ty::Const::new_bound(self.tcx, ty::INNERMOST, var); - self.mapping.insert(old_var, mapped.into()); + let mapped = ty::Const::new_bound(self.tcx, ty::INNERMOST, ty::BoundConst { var }); + self.mapping.insert(old_bound.var, mapped.into()); mapped }; diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index 522409a5ee52..b59dc4bd132f 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -158,7 +158,9 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen if let Node::Item(item) = node { match item.kind { ItemKind::Impl(impl_) => { - if impl_.defaultness.is_default() { + if let Some(of_trait) = impl_.of_trait + && of_trait.defaultness.is_default() + { is_default_impl_trait = tcx .impl_trait_ref(def_id) .map(|t| ty::Binder::dummy(t.instantiate_identity())); @@ -517,8 +519,7 @@ pub(super) fn explicit_predicates_of<'tcx>( projection.args == trait_identity_args // FIXME(return_type_notation): This check should be more robust && !tcx.is_impl_trait_in_trait(projection.def_id) - && tcx.associated_item(projection.def_id).container_id(tcx) - == def_id.to_def_id() + && tcx.parent(projection.def_id) == def_id.to_def_id() } else { false } diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index eb3492f5de6e..8133f9f68234 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -604,13 +604,10 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { #[instrument(level = "debug", skip(self))] fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { - match &item.kind { - hir::ItemKind::Impl(hir::Impl { of_trait, .. }) => { - if let Some(of_trait) = of_trait { - self.record_late_bound_vars(of_trait.hir_ref_id, Vec::default()); - } - } - _ => {} + if let hir::ItemKind::Impl(impl_) = item.kind + && let Some(of_trait) = impl_.of_trait + { + self.record_late_bound_vars(of_trait.trait_ref.hir_ref_id, Vec::default()); } match item.kind { hir::ItemKind::Fn { generics, .. } => { @@ -636,7 +633,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { | hir::ItemKind::Union(_, generics, _) | hir::ItemKind::Trait(_, _, _, _, generics, ..) | hir::ItemKind::TraitAlias(_, generics, ..) - | hir::ItemKind::Impl(&hir::Impl { generics, .. }) => { + | hir::ItemKind::Impl(hir::Impl { generics, .. }) => { // These kinds of items have only early-bound lifetime parameters. self.visit_early(item.hir_id(), generics, |this| intravisit::walk_item(this, item)); } @@ -2106,7 +2103,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { // If we have a self type alias (in an impl), try to resolve an // associated item from one of the supertraits of the impl's trait. Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. } => { - let hir::ItemKind::Impl(hir::Impl { of_trait: Some(trait_ref), .. }) = self + let hir::ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = self .tcx .hir_node_by_def_id(impl_def_id.expect_local()) .expect_item() @@ -2114,7 +2111,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { else { return; }; - let Some(trait_def_id) = trait_ref.trait_def_id() else { + let Some(trait_def_id) = of_trait.trait_ref.trait_def_id() else { return; }; let Some((bound_vars, assoc_item)) = BoundVarContext::supertrait_hrtb_vars( diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 22fb02714dd7..62125c99d802 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -251,7 +251,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ .emit_err(crate::errors::SelfInImplSelf { span: spans.into(), note: () }); Ty::new_error(tcx, guar) } - _ => icx.lower_ty(*self_ty), + _ => icx.lower_ty(self_ty), }, ItemKind::Fn { .. } => { let args = ty::GenericArgs::identity_for_item(tcx, def_id); diff --git a/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs index 835f8e8cdaee..8a9f9130feac 100644 --- a/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs +++ b/compiler/rustc_hir_analysis/src/errors/wrong_number_of_generic_args.rs @@ -147,7 +147,11 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(hir::Impl { - of_trait: Some(hir::TraitRef { hir_ref_id: id_in_of_trait, .. }), + of_trait: + Some(hir::TraitImplHeader { + trait_ref: hir::TraitRef { hir_ref_id: id_in_of_trait, .. }, + .. + }), .. }), .. diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 7760642d8fb0..d14aef8ace46 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -199,12 +199,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // However, this can easily get out of sync! Ideally, we would perform this step // where we are guaranteed to catch *all* bounds like in // `Self::lower_poly_trait_ref`. List of concrete issues: - // FIXME(more_maybe_bounds): We don't call this for e.g., trait object tys or - // supertrait bounds! + // FIXME(more_maybe_bounds): We don't call this for trait object tys, supertrait + // bounds or associated type bounds (ATB)! // FIXME(trait_alias, #143122): We don't call it for the RHS. Arguably however, - // AST lowering should reject them outright. - // FIXME(associated_type_bounds): We don't call this for them. However, AST - // lowering should reject them outright (#135229). + // AST lowering should reject them outright. let bounds = collect_relaxed_bounds(hir_bounds, self_ty_where_predicates); self.check_and_report_invalid_relaxed_bounds(bounds); } @@ -1077,7 +1075,7 @@ impl<'tcx> TypeVisitor> for GenericParamAndBoundVarCollector<'_, 't ty::ConstKind::Param(param) => { self.params.insert(param.index); } - ty::ConstKind::Bound(db, ty::BoundVar { .. }) if db >= self.depth => { + ty::ConstKind::Bound(db, _) if db >= self.depth => { let guar = self.cx.dcx().delayed_bug("unexpected escaping late-bound const var"); return ControlFlow::Break(guar); } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index f73442fdebd4..93b82acf6212 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -755,7 +755,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let limit = if candidates.len() == 5 { 5 } else { 4 }; for (index, &item) in candidates.iter().take(limit).enumerate() { - let impl_ = tcx.impl_of_assoc(item).unwrap(); + let impl_ = tcx.parent(item); let note_span = if item.is_local() { Some(tcx.def_span(item)) @@ -888,8 +888,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => { // `::Item = String`. let projection_term = pred.projection_term; - let quiet_projection_term = - projection_term.with_self_ty(tcx, Ty::new_var(tcx, ty::TyVid::ZERO)); + let quiet_projection_term = projection_term + .with_replaced_self_ty(tcx, Ty::new_var(tcx, ty::TyVid::ZERO)); let term = pred.term; let obligation = format!("{projection_term} = {term}"); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs index 646ff3ca08d2..56998b5b53cc 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs @@ -200,7 +200,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) = tcx.hir_node_by_def_id(parent_id) && self_ty.hir_id == impl_self_ty.hir_id { - let Some(of_trait_ref) = of_trait else { + let Some(of_trait) = of_trait else { diag.span_suggestion_verbose( impl_self_ty.span.shrink_to_hi(), "you might have intended to implement this trait for a given type", @@ -209,10 +209,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ); return; }; - if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) { + if !of_trait.trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) { return; } - let of_trait_span = of_trait_ref.path.span; + let of_trait_span = of_trait.trait_ref.path.span; // make sure that we are not calling unwrap to abort during the compilation let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else { return; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 3daeb548a799..c7b984d9b259 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1135,9 +1135,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ); } } else { + let trait_ = + tcx.short_string(bound.print_only_trait_path(), err.long_ty_path()); err.note(format!( - "associated {assoc_kind_str} `{assoc_ident}` could derive from `{}`", - bound.print_only_trait_path(), + "associated {assoc_kind_str} `{assoc_ident}` could derive from `{trait_}`", )); } } @@ -2107,9 +2108,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let name = tcx.item_name(param_def_id); ty::Const::new_param(tcx, ty::ParamConst::new(index, name)) } - Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => { - ty::Const::new_bound(tcx, debruijn, ty::BoundVar::from_u32(index)) - } + Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => ty::Const::new_bound( + tcx, + debruijn, + ty::BoundConst { var: ty::BoundVar::from_u32(index) }, + ), Some(rbv::ResolvedArg::Error(guar)) => ty::Const::new_error(tcx, guar), arg => bug!("unexpected bound var resolution for {:?}: {arg:?}", path_hir_id), } @@ -2729,7 +2732,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }; let i = tcx.parent_hir_node(fn_hir_id).expect_item().expect_impl(); - let trait_ref = self.lower_impl_trait_ref(i.of_trait.as_ref()?, self.lower_ty(i.self_ty)); + let trait_ref = self.lower_impl_trait_ref(&i.of_trait?.trait_ref, self.lower_ty(i.self_ty)); let assoc = tcx.associated_items(trait_ref.def_id).find_by_ident_and_kind( tcx, diff --git a/compiler/rustc_hir_analysis/src/hir_wf_check.rs b/compiler/rustc_hir_analysis/src/hir_wf_check.rs index 3fddaee8cef4..d8578970adc9 100644 --- a/compiler/rustc_hir_analysis/src/hir_wf_check.rs +++ b/compiler/rustc_hir_analysis/src/hir_wf_check.rs @@ -154,8 +154,9 @@ pub(super) fn diagnostic_hir_wf_check<'tcx>( hir::ItemKind::TyAlias(_, _, ty) | hir::ItemKind::Static(_, _, ty, _) | hir::ItemKind::Const(_, _, ty, _) => vec![ty], - hir::ItemKind::Impl(impl_) => match &impl_.of_trait { - Some(t) => t + hir::ItemKind::Impl(impl_) => match impl_.of_trait { + Some(of_trait) => of_trait + .trait_ref .path .segments .last() diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index 0043f0c71176..b38639ed8c62 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -390,45 +390,13 @@ fn check_predicates<'tcx>( let mut res = Ok(()); for (clause, span) in impl1_predicates { - if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(clause.as_predicate(), *pred2)) - { + if !impl2_predicates.iter().any(|&pred2| clause.as_predicate() == pred2) { res = res.and(check_specialization_on(tcx, clause, span)) } } res } -/// Checks if some predicate on the specializing impl (`predicate1`) is the same -/// as some predicate on the base impl (`predicate2`). -/// -/// This basically just checks syntactic equivalence, but is a little more -/// forgiving since we want to equate `T: Tr` with `T: [const] Tr` so this can work: -/// -/// ```ignore (illustrative) -/// #[rustc_specialization_trait] -/// trait Specialize { } -/// -/// impl Tr for T { } -/// impl const Tr for T { } -/// ``` -/// -/// However, we *don't* want to allow the reverse, i.e., when the bound on the -/// specializing impl is not as const as the bound on the base impl: -/// -/// ```ignore (illustrative) -/// impl const Tr for T { } -/// impl const Tr for T { } // should be T: [const] Bound -/// ``` -/// -/// So we make that check in this function and try to raise a helpful error message. -fn trait_predicates_eq<'tcx>( - predicate1: ty::Predicate<'tcx>, - predicate2: ty::Predicate<'tcx>, -) -> bool { - // FIXME(const_trait_impl) - predicate1 == predicate2 -} - #[instrument(level = "debug", skip(tcx))] fn check_specialization_on<'tcx>( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_hir_id/Cargo.toml b/compiler/rustc_hir_id/Cargo.toml new file mode 100644 index 000000000000..c357a4f62d96 --- /dev/null +++ b/compiler/rustc_hir_id/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rustc_hir_id" +version = "0.0.0" +edition = "2024" + +[dependencies] +# tidy-alphabetical-start +rustc_data_structures = { path = "../rustc_data_structures" } +rustc_index = { path = "../rustc_index" } +rustc_macros = { path = "../rustc_macros" } +rustc_serialize = { path = "../rustc_serialize" } +rustc_span = { path = "../rustc_span" } +# tidy-alphabetical-end diff --git a/compiler/rustc_hir_id/src/lib.rs b/compiler/rustc_hir_id/src/lib.rs new file mode 100644 index 000000000000..d07bc88e66af --- /dev/null +++ b/compiler/rustc_hir_id/src/lib.rs @@ -0,0 +1,196 @@ +//! Library containing Id types from `rustc_hir`, split out so crates can use it without depending +//! on all of `rustc_hir` (which is large and depends on other large things like `rustc_target`). +#![allow(internal_features)] +#![feature(negative_impls)] +#![feature(rustc_attrs)] + +use std::fmt::{self, Debug}; + +use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableOrd, ToStableHashKey}; +use rustc_macros::{Decodable, Encodable, HashStable_Generic}; +pub use rustc_span::HashStableContext; +use rustc_span::def_id::{CRATE_DEF_ID, DefId, DefIndex, DefPathHash, LocalDefId}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] +pub struct OwnerId { + pub def_id: LocalDefId, +} + +impl Debug for OwnerId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Example: DefId(0:1 ~ aa[7697]::{use#0}) + Debug::fmt(&self.def_id, f) + } +} + +impl From for HirId { + fn from(owner: OwnerId) -> HirId { + HirId { owner, local_id: ItemLocalId::ZERO } + } +} + +impl From for DefId { + fn from(value: OwnerId) -> Self { + value.to_def_id() + } +} + +impl OwnerId { + #[inline] + pub fn to_def_id(self) -> DefId { + self.def_id.to_def_id() + } +} + +impl rustc_index::Idx for OwnerId { + #[inline] + fn new(idx: usize) -> Self { + OwnerId { def_id: LocalDefId { local_def_index: DefIndex::from_usize(idx) } } + } + + #[inline] + fn index(self) -> usize { + self.def_id.local_def_index.as_usize() + } +} + +impl HashStable for OwnerId { + #[inline] + fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { + self.to_stable_hash_key(hcx).hash_stable(hcx, hasher); + } +} + +impl ToStableHashKey for OwnerId { + type KeyType = DefPathHash; + + #[inline] + fn to_stable_hash_key(&self, hcx: &CTX) -> DefPathHash { + hcx.def_path_hash(self.to_def_id()) + } +} + +/// Uniquely identifies a node in the HIR of the current crate. It is +/// composed of the `owner`, which is the `LocalDefId` of the directly enclosing +/// `hir::Item`, `hir::TraitItem`, or `hir::ImplItem` (i.e., the closest "item-like"), +/// and the `local_id` which is unique within the given owner. +/// +/// This two-level structure makes for more stable values: One can move an item +/// around within the source code, or add or remove stuff before it, without +/// the `local_id` part of the `HirId` changing, which is a very useful property in +/// incremental compilation where we have to persist things through changes to +/// the code base. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable, HashStable_Generic)] +#[rustc_pass_by_value] +pub struct HirId { + pub owner: OwnerId, + pub local_id: ItemLocalId, +} + +// To ensure correctness of incremental compilation, +// `HirId` must not implement `Ord` or `PartialOrd`. +// See https://github.com/rust-lang/rust/issues/90317. +impl !Ord for HirId {} +impl !PartialOrd for HirId {} + +impl Debug for HirId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Example: HirId(DefId(0:1 ~ aa[7697]::{use#0}).10) + // Don't use debug_tuple to always keep this on one line. + write!(f, "HirId({:?}.{:?})", self.owner, self.local_id) + } +} + +impl HirId { + /// Signal local id which should never be used. + pub const INVALID: HirId = + HirId { owner: OwnerId { def_id: CRATE_DEF_ID }, local_id: ItemLocalId::INVALID }; + + #[inline] + pub fn expect_owner(self) -> OwnerId { + assert_eq!(self.local_id.index(), 0); + self.owner + } + + #[inline] + pub fn as_owner(self) -> Option { + if self.local_id.index() == 0 { Some(self.owner) } else { None } + } + + #[inline] + pub fn is_owner(self) -> bool { + self.local_id.index() == 0 + } + + #[inline] + pub fn make_owner(owner: LocalDefId) -> Self { + Self { owner: OwnerId { def_id: owner }, local_id: ItemLocalId::ZERO } + } +} + +impl fmt::Display for HirId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +rustc_data_structures::define_stable_id_collections!(HirIdMap, HirIdSet, HirIdMapEntry, HirId); +rustc_data_structures::define_id_collections!( + ItemLocalMap, + ItemLocalSet, + ItemLocalMapEntry, + ItemLocalId +); + +rustc_index::newtype_index! { + /// An `ItemLocalId` uniquely identifies something within a given "item-like"; + /// that is, within a `hir::Item`, `hir::TraitItem`, or `hir::ImplItem`. There is no + /// guarantee that the numerical value of a given `ItemLocalId` corresponds to + /// the node's position within the owning item in any way, but there is a + /// guarantee that the `ItemLocalId`s within an owner occupy a dense range of + /// integers starting at zero, so a mapping that maps all or most nodes within + /// an "item-like" to something else can be implemented by a `Vec` instead of a + /// tree or hash map. + #[derive(HashStable_Generic)] + #[encodable] + #[orderable] + pub struct ItemLocalId {} +} + +impl ItemLocalId { + /// Signal local id which should never be used. + pub const INVALID: ItemLocalId = ItemLocalId::MAX; +} + +impl StableOrd for ItemLocalId { + const CAN_USE_UNSTABLE_SORT: bool = true; + + // `Ord` is implemented as just comparing the ItemLocalId's numerical + // values and these are not changed by (de-)serialization. + const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = (); +} + +/// The `HirId` corresponding to `CRATE_NODE_ID` and `CRATE_DEF_ID`. +pub const CRATE_HIR_ID: HirId = + HirId { owner: OwnerId { def_id: CRATE_DEF_ID }, local_id: ItemLocalId::ZERO }; + +pub const CRATE_OWNER_ID: OwnerId = OwnerId { def_id: CRATE_DEF_ID }; + +impl ToStableHashKey for HirId { + type KeyType = (DefPathHash, ItemLocalId); + + #[inline] + fn to_stable_hash_key(&self, hcx: &CTX) -> (DefPathHash, ItemLocalId) { + let def_path_hash = self.owner.def_id.to_stable_hash_key(hcx); + (def_path_hash, self.local_id) + } +} + +impl ToStableHashKey for ItemLocalId { + type KeyType = ItemLocalId; + + #[inline] + fn to_stable_hash_key(&self, _: &CTX) -> ItemLocalId { + *self + } +} diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 235eec96d74f..be5859b57c5e 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -690,39 +690,44 @@ impl<'a> State<'a> { let (cb, ib) = self.head("union"); self.print_struct(ident.name, generics, struct_def, item.span, true, cb, ib); } - hir::ItemKind::Impl(&hir::Impl { - constness, - safety, - polarity, - defaultness, - defaultness_span: _, - generics, - ref of_trait, - self_ty, - items, - }) => { + hir::ItemKind::Impl(hir::Impl { generics, of_trait, self_ty, items }) => { let (cb, ib) = self.head(""); - self.print_defaultness(defaultness); - self.print_safety(safety); - self.word_nbsp("impl"); - if let hir::Constness::Const = constness { - self.word_nbsp("const"); - } + let impl_generics = |this: &mut Self| { + this.word_nbsp("impl"); + if !generics.params.is_empty() { + this.print_generic_params(generics.params); + this.space(); + } + }; - if !generics.params.is_empty() { - self.print_generic_params(generics.params); - self.space(); - } + match of_trait { + None => impl_generics(self), + Some(&hir::TraitImplHeader { + constness, + safety, + polarity, + defaultness, + defaultness_span: _, + ref trait_ref, + }) => { + self.print_defaultness(defaultness); + self.print_safety(safety); + + impl_generics(self); + + if let hir::Constness::Const = constness { + self.word_nbsp("const"); + } - if let hir::ImplPolarity::Negative(_) = polarity { - self.word("!"); - } + if let hir::ImplPolarity::Negative(_) = polarity { + self.word("!"); + } - if let Some(t) = of_trait { - self.print_trait_ref(t); - self.space(); - self.word_space("for"); + self.print_trait_ref(trait_ref); + self.space(); + self.word_space("for"); + } } self.print_type(self_ty); diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index bac4d70103c3..1ed0756fdd6a 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -159,7 +159,7 @@ hir_typeck_lossy_provenance_ptr2int = .suggestion = use `.addr()` to obtain the address of a pointer .help = if you can't comply with strict provenance and need to expose the pointer provenance you can use `.expose_provenance()` instead -hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}` +hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty}` hir_typeck_naked_asm_outside_naked_fn = the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` @@ -184,7 +184,7 @@ hir_typeck_never_type_fallback_flowing_into_unsafe_path = never type fallback af hir_typeck_never_type_fallback_flowing_into_unsafe_union_field = never type fallback affects this union access .help = specify the type explicitly -hir_typeck_no_associated_item = no {$item_kind} named `{$item_ident}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method -> +hir_typeck_no_associated_item = no {$item_kind} named `{$item_ident}` found for {$ty_prefix} `{$ty}`{$trait_missing_method -> [true] {""} *[other] {" "}in the current scope } diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 48bb45de53eb..7afc555b5980 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -910,7 +910,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // const stability checking here too, I guess. if self.tcx.is_conditionally_const(callee_did) { let q = self.tcx.const_conditions(callee_did); - // FIXME(const_trait_impl): Use this span with a better cause code. for (idx, (cond, pred_span)) in q.instantiate(self.tcx, callee_args).into_iter().enumerate() { diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 9a4c6f98a480..e66601631fcc 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1800,11 +1800,13 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { .kind() .map_bound(|clause| match clause { ty::ClauseKind::Trait(trait_pred) => Some(ty::ClauseKind::Trait( - trait_pred.with_self_ty(fcx.tcx, ty), + trait_pred.with_replaced_self_ty(fcx.tcx, ty), )), - ty::ClauseKind::Projection(proj_pred) => Some( - ty::ClauseKind::Projection(proj_pred.with_self_ty(fcx.tcx, ty)), - ), + ty::ClauseKind::Projection(proj_pred) => { + Some(ty::ClauseKind::Projection( + proj_pred.with_replaced_self_ty(fcx.tcx, ty), + )) + } _ => None, }) .transpose()?; diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index e5684f8cbe66..fb6ebe066a8f 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -698,7 +698,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { match (self.tcx.parent_hir_node(expr.hir_id), error) { (hir::Node::LetStmt(hir::LetStmt { ty: Some(ty), init: Some(init), .. }), _) - if init.hir_id == expr.hir_id => + if init.hir_id == expr.hir_id && !ty.span.source_equal(init.span) => { // Point at `let` assignment type. err.span_label(ty.span, "expected due to this"); diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index a8bb6956f101..d15d092b7d3d 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -200,11 +200,11 @@ pub(crate) enum ExplicitDestructorCallSugg { #[derive(Diagnostic)] #[diag(hir_typeck_missing_parentheses_in_range, code = E0689)] -pub(crate) struct MissingParenthesesInRange { +pub(crate) struct MissingParenthesesInRange<'tcx> { #[primary_span] #[label(hir_typeck_missing_parentheses_in_range)] pub span: Span, - pub ty_str: String, + pub ty: Ty<'tcx>, pub method_name: String, #[subdiagnostic] pub add_missing_parentheses: Option, @@ -828,13 +828,13 @@ pub(crate) struct UnlabeledCfInWhileCondition<'a> { #[derive(Diagnostic)] #[diag(hir_typeck_no_associated_item, code = E0599)] -pub(crate) struct NoAssociatedItem { +pub(crate) struct NoAssociatedItem<'tcx> { #[primary_span] pub span: Span, pub item_kind: &'static str, pub item_ident: Ident, pub ty_prefix: Cow<'static, str>, - pub ty_str: String, + pub ty: Ty<'tcx>, pub trait_missing_method: bool, } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 454ec7ddcafb..940f0e3708d0 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -290,6 +290,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | ExprKind::Let(..) | ExprKind::Loop(..) | ExprKind::Match(..) => {} + // Do not warn on `as` casts from never to any, + // they are sometimes required to appeal typeck. + ExprKind::Cast(_, _) => {} // If `expr` is a result of desugaring the try block and is an ok-wrapped // diverging expression (e.g. it arose from desugaring of `try { return }`), // we skip issuing a warning because it is autogenerated code. @@ -2745,6 +2748,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let available_field_names = self.available_field_names(variant, expr, skip_fields); if let Some(field_name) = find_best_match_for_name(&available_field_names, field.ident.name, None) + && !(field.ident.name.as_str().parse::().is_ok() + && field_name.as_str().parse::().is_ok()) { err.span_label(field.ident.span, "unknown field"); err.span_suggestion_verbose( @@ -3321,18 +3326,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { (base_ty, "") }; - for (found_fields, args) in + for found_fields in self.get_field_candidates_considering_privacy_for_diag(span, ty, mod_id, expr.hir_id) { - let field_names = found_fields.iter().map(|field| field.name).collect::>(); + let field_names = found_fields.iter().map(|field| field.0.name).collect::>(); let mut candidate_fields: Vec<_> = found_fields .into_iter() .filter_map(|candidate_field| { self.check_for_nested_field_satisfying_condition_for_diag( span, - &|candidate_field, _| candidate_field.ident(self.tcx()) == field, + &|candidate_field, _| candidate_field == field, candidate_field, - args, vec![], mod_id, expr.hir_id, @@ -3361,6 +3365,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } else if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) + && !(field.name.as_str().parse::().is_ok() + && field_name.as_str().parse::().is_ok()) { err.span_suggestion_verbose( field.span, @@ -3396,7 +3402,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { base_ty: Ty<'tcx>, mod_id: DefId, hir_id: HirId, - ) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> { + ) -> Vec)>> { debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); let mut autoderef = self.autoderef(span, base_ty).silence_errors(); @@ -3422,7 +3428,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) { return None; } - return Some(( + return Some( fields .iter() .filter(move |field| { @@ -3431,9 +3437,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) // For compile-time reasons put a limit on number of fields we search .take(100) + .map(|field_def| { + ( + field_def.ident(self.tcx).normalize_to_macros_2_0(), + field_def.ty(self.tcx, args), + ) + }) + .collect::>(), + ); + } + ty::Tuple(types) => { + return Some( + types + .iter() + .enumerate() + // For compile-time reasons put a limit on number of fields we search + .take(100) + .map(|(i, ty)| (Ident::from_str(&i.to_string()), ty)) .collect::>(), - *args, - )); + ); } _ => None, } @@ -3443,56 +3465,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// This method is called after we have encountered a missing field error to recursively /// search for the field + #[instrument(skip(self, matches, mod_id, hir_id), level = "debug")] pub(crate) fn check_for_nested_field_satisfying_condition_for_diag( &self, span: Span, - matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, - candidate_field: &ty::FieldDef, - subst: GenericArgsRef<'tcx>, + matches: &impl Fn(Ident, Ty<'tcx>) -> bool, + (candidate_name, candidate_ty): (Ident, Ty<'tcx>), mut field_path: Vec, mod_id: DefId, hir_id: HirId, ) -> Option> { - debug!( - "check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}", - span, candidate_field, field_path - ); - if field_path.len() > 3 { // For compile-time reasons and to avoid infinite recursion we only check for fields // up to a depth of three - None - } else { - field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0()); - let field_ty = candidate_field.ty(self.tcx, subst); - if matches(candidate_field, field_ty) { - return Some(field_path); - } else { - for (nested_fields, subst) in self - .get_field_candidates_considering_privacy_for_diag( - span, field_ty, mod_id, hir_id, - ) - { - // recursively search fields of `candidate_field` if it's a ty::Adt - for field in nested_fields { - if let Some(field_path) = self - .check_for_nested_field_satisfying_condition_for_diag( - span, - matches, - field, - subst, - field_path.clone(), - mod_id, - hir_id, - ) - { - return Some(field_path); - } - } + return None; + } + field_path.push(candidate_name); + if matches(candidate_name, candidate_ty) { + return Some(field_path); + } + for nested_fields in self.get_field_candidates_considering_privacy_for_diag( + span, + candidate_ty, + mod_id, + hir_id, + ) { + // recursively search fields of `candidate_field` if it's a ty::Adt + for field in nested_fields { + if let Some(field_path) = self.check_for_nested_field_satisfying_condition_for_diag( + span, + matches, + field, + field_path.clone(), + mod_id, + hir_id, + ) { + return Some(field_path); } } - None } + None } fn check_expr_index( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 36abd7c85550..b80a2af31007 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1589,26 +1589,64 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // e.g. `reuse HasSelf::method;` should suggest `reuse HasSelf::method($args);`. full_call_span.shrink_to_hi() }; + + // Controls how the arguments should be listed in the suggestion. + enum ArgumentsFormatting { + SingleLine, + Multiline { fallback_indent: String, brace_indent: String }, + } + let arguments_formatting = { + let mut provided_inputs = matched_inputs.iter().filter_map(|a| *a); + if let Some(brace_indent) = source_map.indentation_before(suggestion_span) + && let Some(first_idx) = provided_inputs.by_ref().next() + && let Some(last_idx) = provided_inputs.by_ref().next() + && let (_, first_span) = provided_arg_tys[first_idx] + && let (_, last_span) = provided_arg_tys[last_idx] + && source_map.is_multiline(first_span.to(last_span)) + && let Some(fallback_indent) = source_map.indentation_before(first_span) + { + ArgumentsFormatting::Multiline { fallback_indent, brace_indent } + } else { + ArgumentsFormatting::SingleLine + } + }; + let mut suggestion = "(".to_owned(); let mut needs_comma = false; for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() { if needs_comma { - suggestion += ", "; - } else { - needs_comma = true; + suggestion += ","; + } + match &arguments_formatting { + ArgumentsFormatting::SingleLine if needs_comma => suggestion += " ", + ArgumentsFormatting::SingleLine => {} + ArgumentsFormatting::Multiline { .. } => suggestion += "\n", } - let suggestion_text = if let Some(provided_idx) = provided_idx + needs_comma = true; + let (suggestion_span, suggestion_text) = if let Some(provided_idx) = provided_idx && let (_, provided_span) = provided_arg_tys[*provided_idx] && let Ok(arg_text) = source_map.span_to_snippet(provided_span) { - arg_text + (Some(provided_span), arg_text) } else { // Propose a placeholder of the correct type let (_, expected_ty) = formal_and_expected_inputs[expected_idx]; - ty_to_snippet(expected_ty, expected_idx) + (None, ty_to_snippet(expected_ty, expected_idx)) }; + if let ArgumentsFormatting::Multiline { fallback_indent, .. } = + &arguments_formatting + { + let indent = suggestion_span + .and_then(|span| source_map.indentation_before(span)) + .unwrap_or_else(|| fallback_indent.clone()); + suggestion += &indent; + } suggestion += &suggestion_text; } + if let ArgumentsFormatting::Multiline { brace_indent, .. } = arguments_formatting { + suggestion += ",\n"; + suggestion += &brace_indent; + } suggestion += ")"; err.span_suggestion_verbose( suggestion_span, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 2345cdab208e..aca3840712e7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -936,7 +936,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Node::ImplItem(item) => { // If it doesn't impl a trait, we can add a return type let Node::Item(&hir::Item { - kind: hir::ItemKind::Impl(&hir::Impl { of_trait, .. }), + kind: hir::ItemKind::Impl(hir::Impl { of_trait, .. }), .. }) = self.tcx.parent_hir_node(item.hir_id()) else { @@ -2378,6 +2378,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .filter_map(|variant| { let sole_field = &variant.single_field(); + // When expected_ty and expr_ty are the same ADT, we prefer to compare their internal generic params, + // When the current variant has a sole field whose type is still an unresolved inference variable, + // suggestions would be often wrong. So suppress the suggestion. See #145294. + if let (ty::Adt(exp_adt, _), ty::Adt(act_adt, _)) = (expected.kind(), expr_ty.kind()) + && exp_adt.did() == act_adt.did() + && sole_field.ty(self.tcx, args).is_ty_var() { + return None; + } + let field_is_local = sole_field.did.is_local(); let field_is_accessible = sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx) diff --git a/compiler/rustc_hir_typeck/src/loops.rs b/compiler/rustc_hir_typeck/src/loops.rs index d47a32469640..acfa5c473aac 100644 --- a/compiler/rustc_hir_typeck/src/loops.rs +++ b/compiler/rustc_hir_typeck/src/loops.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::fmt; use Context::*; -use rustc_ast::Label; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; use rustc_hir::def::DefKind; @@ -42,8 +41,8 @@ enum Context { ConstBlock, /// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`. LoopMatch { - /// The label of the labeled block (not of the loop itself). - labeled_block: Label, + /// The destination pointing to the labeled block (not to the loop itself). + labeled_block: Destination, }, } @@ -186,18 +185,18 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { { self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b)); } - hir::ExprKind::Break(break_label, ref opt_expr) => { + hir::ExprKind::Break(break_destination, ref opt_expr) => { if let Some(e) = opt_expr { self.visit_expr(e); } - if self.require_label_in_labeled_block(e.span, &break_label, "break") { + if self.require_label_in_labeled_block(e.span, &break_destination, "break") { // If we emitted an error about an unlabeled break in a labeled // block, we don't need any further checking for this break any more return; } - let loop_id = match break_label.target_id { + let loop_id = match break_destination.target_id { Ok(loop_id) => Some(loop_id), Err(hir::LoopIdError::OutsideLoopScope) => None, Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => { @@ -212,18 +211,25 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { // A `#[const_continue]` must break to a block in a `#[loop_match]`. if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) { - if let Some(break_label) = break_label.label { - let is_target_label = |cx: &Context| match cx { - Context::LoopMatch { labeled_block } => { - break_label.ident.name == labeled_block.ident.name - } - _ => false, - }; + let Some(label) = break_destination.label else { + let span = e.span; + self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span }); + }; - if !self.cx_stack.iter().rev().any(is_target_label) { - let span = break_label.ident.span; - self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span }); + let is_target_label = |cx: &Context| match cx { + Context::LoopMatch { labeled_block } => { + // NOTE: with macro expansion, the label's span might be different here + // even though it does still refer to the same HIR node. A block + // can't have two labels, so the hir_id is a unique identifier. + assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`. + break_destination.target_id == labeled_block.target_id } + _ => false, + }; + + if !self.cx_stack.iter().rev().any(is_target_label) { + let span = label.ident.span; + self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span }); } } @@ -249,7 +255,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { Some(kind) => { let suggestion = format!( "break{}", - break_label + break_destination .label .map_or_else(String::new, |l| format!(" {}", l.ident)) ); @@ -259,7 +265,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { kind: kind.name(), suggestion, loop_label, - break_label: break_label.label, + break_label: break_destination.label, break_expr_kind: &break_expr.kind, break_expr_span: break_expr.span, }); @@ -268,7 +274,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> { } let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32)); - let label_sp = match break_label.label { + let label_sp = match break_destination.label { Some(label) => sp_lo.with_hi(label.ident.span.hi()), None => sp_lo.shrink_to_lo(), }; @@ -416,7 +422,7 @@ impl<'hir> CheckLoopVisitor<'hir> { &self, e: &'hir hir::Expr<'hir>, body: &'hir hir::Block<'hir>, - ) -> Option for &F where - F: ~const Fn, + F: [const] Fn, { extern "rust-call" fn call(&self, args: A) -> F::Output { (**self).call(args) @@ -271,7 +271,7 @@ mod impls { #[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")] impl const FnMut for &F where - F: ~const Fn, + F: [const] Fn, { extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output { (**self).call(args) @@ -282,7 +282,7 @@ mod impls { #[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")] impl const FnOnce for &F where - F: ~const Fn, + F: [const] Fn, { type Output = F::Output; @@ -295,7 +295,7 @@ mod impls { #[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")] impl const FnMut for &mut F where - F: ~const FnMut, + F: [const] FnMut, { extern "rust-call" fn call_mut(&mut self, args: A) -> F::Output { (*self).call_mut(args) @@ -306,7 +306,7 @@ mod impls { #[rustc_const_unstable(feature = "const_trait_impl", issue = "143874")] impl const FnOnce for &mut F where - F: ~const FnMut, + F: [const] FnMut, { type Output = F::Output; extern "rust-call" fn call_once(self, args: A) -> F::Output { diff --git a/library/core/src/ops/index.rs b/library/core/src/ops/index.rs index d8489e9a9491..1aed2fb4742e 100644 --- a/library/core/src/ops/index.rs +++ b/library/core/src/ops/index.rs @@ -169,7 +169,7 @@ see chapter in The Book : ~const Index { +pub trait IndexMut: [const] Index { /// Performs the mutable indexing (`container[index]`) operation. /// /// # Panics diff --git a/library/core/src/ops/range.rs b/library/core/src/ops/range.rs index f33a33e6b752..95d1e2069ac9 100644 --- a/library/core/src/ops/range.rs +++ b/library/core/src/ops/range.rs @@ -853,7 +853,7 @@ pub trait RangeBounds { /// assert!( RangeBounds::is_empty(&(f32::NAN..5.0))); /// ``` /// - /// But never empty is either side is unbounded: + /// But never empty if either side is unbounded: /// /// ``` /// #![feature(range_bounds_is_empty)] diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index a889c824be53..76bf438878f0 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -130,7 +130,7 @@ use crate::ops::ControlFlow; #[lang = "Try"] #[const_trait] #[rustc_const_unstable(feature = "const_try", issue = "74935")] -pub trait Try: ~const FromResidual { +pub trait Try: [const] FromResidual { /// The type of the value produced by `?` when *not* short-circuiting. #[unstable(feature = "try_trait_v2", issue = "84277", old_name = "try_trait")] type Output; diff --git a/library/core/src/option.rs b/library/core/src/option.rs index ed070fbd2274..e83e77344cf3 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -651,7 +651,7 @@ impl Option { #[inline] #[stable(feature = "is_some_and", since = "1.70.0")] #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] - pub const fn is_some_and(self, f: impl ~const FnOnce(T) -> bool + ~const Destruct) -> bool { + pub const fn is_some_and(self, f: impl [const] FnOnce(T) -> bool + [const] Destruct) -> bool { match self { None => false, Some(x) => f(x), @@ -700,7 +700,7 @@ impl Option { #[inline] #[stable(feature = "is_none_or", since = "1.82.0")] #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] - pub const fn is_none_or(self, f: impl ~const FnOnce(T) -> bool + ~const Destruct) -> bool { + pub const fn is_none_or(self, f: impl [const] FnOnce(T) -> bool + [const] Destruct) -> bool { match self { None => true, Some(x) => f(x), @@ -1030,7 +1030,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn unwrap_or(self, default: T) -> T where - T: ~const Destruct, + T: [const] Destruct, { match self { Some(x) => x, @@ -1053,7 +1053,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn unwrap_or_else(self, f: F) -> T where - F: ~const FnOnce() -> T + ~const Destruct, + F: [const] FnOnce() -> T + [const] Destruct, { match self { Some(x) => x, @@ -1085,7 +1085,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn unwrap_or_default(self) -> T where - T: ~const Default, + T: [const] Default, { match self { Some(x) => x, @@ -1152,7 +1152,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn map(self, f: F) -> Option where - F: ~const FnOnce(T) -> U + ~const Destruct, + F: [const] FnOnce(T) -> U + [const] Destruct, { match self { Some(x) => Some(f(x)), @@ -1183,7 +1183,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn inspect(self, f: F) -> Self where - F: ~const FnOnce(&T) + ~const Destruct, + F: [const] FnOnce(&T) + [const] Destruct, { if let Some(ref x) = self { f(x); @@ -1216,8 +1216,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn map_or(self, default: U, f: F) -> U where - F: ~const FnOnce(T) -> U + ~const Destruct, - U: ~const Destruct, + F: [const] FnOnce(T) -> U + [const] Destruct, + U: [const] Destruct, { match self { Some(t) => f(t), @@ -1263,8 +1263,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn map_or_else(self, default: D, f: F) -> U where - D: ~const FnOnce() -> U + ~const Destruct, - F: ~const FnOnce(T) -> U + ~const Destruct, + D: [const] FnOnce() -> U + [const] Destruct, + F: [const] FnOnce(T) -> U + [const] Destruct, { match self { Some(t) => f(t), @@ -1294,8 +1294,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn map_or_default(self, f: F) -> U where - U: ~const Default, - F: ~const FnOnce(T) -> U + ~const Destruct, + U: [const] Default, + F: [const] FnOnce(T) -> U + [const] Destruct, { match self { Some(t) => f(t), @@ -1327,7 +1327,7 @@ impl Option { #[inline] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] - pub const fn ok_or(self, err: E) -> Result { + pub const fn ok_or(self, err: E) -> Result { match self { Some(v) => Ok(v), None => Err(err), @@ -1355,7 +1355,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn ok_or_else(self, err: F) -> Result where - F: ~const FnOnce() -> E + ~const Destruct, + F: [const] FnOnce() -> E + [const] Destruct, { match self { Some(v) => Ok(v), @@ -1487,8 +1487,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn and(self, optb: Option) -> Option where - T: ~const Destruct, - U: ~const Destruct, + T: [const] Destruct, + U: [const] Destruct, { match self { Some(_) => optb, @@ -1531,7 +1531,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn and_then(self, f: F) -> Option where - F: ~const FnOnce(T) -> Option + ~const Destruct, + F: [const] FnOnce(T) -> Option + [const] Destruct, { match self { Some(x) => f(x), @@ -1568,8 +1568,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn filter

(self, predicate: P) -> Self where - P: ~const FnOnce(&T) -> bool + ~const Destruct, - T: ~const Destruct, + P: [const] FnOnce(&T) -> bool + [const] Destruct, + T: [const] Destruct, { if let Some(x) = self { if predicate(&x) { @@ -1611,7 +1611,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn or(self, optb: Option) -> Option where - T: ~const Destruct, + T: [const] Destruct, { match self { x @ Some(_) => x, @@ -1637,10 +1637,10 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn or_else(self, f: F) -> Option where - F: ~const FnOnce() -> Option + ~const Destruct, + F: [const] FnOnce() -> Option + [const] Destruct, //FIXME(const_hack): this `T: ~const Destruct` is unnecessary, but even precise live drops can't tell // no value of type `T` gets dropped here - T: ~const Destruct, + T: [const] Destruct, { match self { x @ Some(_) => x, @@ -1674,7 +1674,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn xor(self, optb: Option) -> Option where - T: ~const Destruct, + T: [const] Destruct, { match (self, optb) { (a @ Some(_), None) => a, @@ -1712,7 +1712,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn insert(&mut self, value: T) -> &mut T where - T: ~const Destruct, + T: [const] Destruct, { *self = Some(value); @@ -1768,7 +1768,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn get_or_insert_default(&mut self) -> &mut T where - T: ~const Default + ~const Destruct, + T: [const] Default + [const] Destruct, { self.get_or_insert_with(T::default) } @@ -1795,8 +1795,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn get_or_insert_with(&mut self, f: F) -> &mut T where - F: ~const FnOnce() -> T + ~const Destruct, - T: ~const Destruct, + F: [const] FnOnce() -> T + [const] Destruct, + T: [const] Destruct, { if let None = self { *self = Some(f()); @@ -1863,7 +1863,7 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn take_if

(&mut self, predicate: P) -> Option where - P: ~const FnOnce(&mut T) -> bool + ~const Destruct, + P: [const] FnOnce(&mut T) -> bool + [const] Destruct, { if self.as_mut().map_or(false, predicate) { self.take() } else { None } } @@ -1911,8 +1911,8 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn zip(self, other: Option) -> Option<(T, U)> where - T: ~const Destruct, - U: ~const Destruct, + T: [const] Destruct, + U: [const] Destruct, { match (self, other) { (Some(a), Some(b)) => Some((a, b)), @@ -1952,9 +1952,9 @@ impl Option { #[rustc_const_unstable(feature = "const_option_ops", issue = "143956")] pub const fn zip_with(self, other: Option, f: F) -> Option where - F: ~const FnOnce(T, U) -> R + ~const Destruct, - T: ~const Destruct, - U: ~const Destruct, + F: [const] FnOnce(T, U) -> R + [const] Destruct, + T: [const] Destruct, + U: [const] Destruct, { match (self, other) { (Some(a), Some(b)) => Some(f(a, b)), @@ -2095,9 +2095,9 @@ impl Option<&mut T> { impl Option> { /// Transposes an `Option` of a [`Result`] into a [`Result`] of an `Option`. /// - /// [`None`] will be mapped to [Ok]\([None]). - /// [Some]\([Ok]\(\_)) and [Some]\([Err]\(\_)) will be mapped to - /// [Ok]\([Some]\(\_)) and [Err]\(\_). + /// [Some]\([Ok]\(\_)) is mapped to [Ok]\([Some]\(\_)), + /// [Some]\([Err]\(\_)) is mapped to [Err]\(\_), + /// and [`None`] will be mapped to [Ok]\([None]). /// /// # Examples /// @@ -2105,9 +2105,9 @@ impl Option> { /// #[derive(Debug, Eq, PartialEq)] /// struct SomeErr; /// - /// let x: Result, SomeErr> = Ok(Some(5)); - /// let y: Option> = Some(Ok(5)); - /// assert_eq!(x, y.transpose()); + /// let x: Option> = Some(Ok(5)); + /// let y: Result, SomeErr> = Ok(Some(5)); + /// assert_eq!(x.transpose(), y); /// ``` #[inline] #[stable(feature = "transpose_result", since = "1.33.0")] @@ -2149,7 +2149,7 @@ impl const Clone for Option where // FIXME(const_hack): the T: ~const Destruct should be inferred from the Self: ~const Destruct in clone_from. // See https://github.com/rust-lang/rust/issues/144207 - T: ~const Clone + ~const Destruct, + T: [const] Clone + [const] Destruct, { #[inline] fn clone(&self) -> Self { @@ -2307,7 +2307,7 @@ impl<'a, T> const From<&'a mut Option> for Option<&'a mut T> { impl crate::marker::StructuralPartialEq for Option {} #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] -impl const PartialEq for Option { +impl const PartialEq for Option { #[inline] fn eq(&self, other: &Self) -> bool { // Spelling out the cases explicitly optimizes better than diff --git a/library/core/src/panic.rs b/library/core/src/panic.rs index 5fa340a6147f..7a42b3d0fc78 100644 --- a/library/core/src/panic.rs +++ b/library/core/src/panic.rs @@ -48,7 +48,6 @@ pub macro panic_2015 { #[allow_internal_unstable(panic_internals, const_format_args)] #[rustc_diagnostic_item = "core_panic_2021_macro"] #[rustc_macro_transparency = "semitransparent"] -#[cfg(feature = "panic_immediate_abort")] pub macro panic_2021 { () => ( $crate::panicking::panic("explicit panic") @@ -64,50 +63,6 @@ pub macro panic_2021 { }), } -#[doc(hidden)] -#[unstable(feature = "edition_panic", issue = "none", reason = "use panic!() instead")] -#[allow_internal_unstable( - panic_internals, - core_intrinsics, - const_dispatch, - const_eval_select, - const_format_args, - rustc_attrs -)] -#[rustc_diagnostic_item = "core_panic_2021_macro"] -#[rustc_macro_transparency = "semitransparent"] -#[cfg(not(feature = "panic_immediate_abort"))] -pub macro panic_2021 { - () => ({ - // Create a function so that the argument for `track_caller` - // can be moved inside if possible. - #[cold] - #[track_caller] - #[inline(never)] - const fn panic_cold_explicit() -> ! { - $crate::panicking::panic_explicit() - } - panic_cold_explicit(); - }), - // Special-case the single-argument case for const_panic. - ("{}", $arg:expr $(,)?) => ({ - #[cold] - #[track_caller] - #[inline(never)] - #[rustc_const_panic_str] // enforce a &&str argument in const-check and hook this by const-eval - #[rustc_do_not_const_check] // hooked by const-eval - const fn panic_cold_display(arg: &T) -> ! { - $crate::panicking::panic_display(arg) - } - panic_cold_display(&$arg); - }), - ($($t:tt)+) => ({ - // Semicolon to prevent temporaries inside the formatting machinery from - // being considered alive in the caller after the panic_fmt call. - $crate::panicking::panic_fmt($crate::const_format_args!($($t)+)); - }), -} - #[doc(hidden)] #[unstable(feature = "edition_panic", issue = "none", reason = "use unreachable!() instead")] #[allow_internal_unstable(panic_internals)] diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index 812bc5e61457..804a12ee477b 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -233,14 +233,6 @@ pub fn panic_nounwind_nobacktrace(expr: &'static str) -> ! { panic_nounwind_fmt(fmt::Arguments::new_const(&[expr]), /* force_no_backtrace */ true); } -#[track_caller] -#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] -#[cfg_attr(feature = "panic_immediate_abort", inline)] -#[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable -pub const fn panic_explicit() -> ! { - panic_display(&"explicit panic"); -} - #[inline] #[track_caller] #[rustc_diagnostic_item = "unreachable_display"] // needed for `non-fmt-panics` lint @@ -260,9 +252,8 @@ pub const fn panic_str_2015(expr: &str) -> ! { #[inline] #[track_caller] +#[lang = "panic_display"] // needed for const-evaluated panics #[rustc_do_not_const_check] // hooked by const-eval -// enforce a &&str argument in const-check and hook this by const-eval -#[rustc_const_panic_str] #[rustc_const_stable_indirect] // must follow stable const rules since it is exposed to stable pub const fn panic_display(x: &T) -> ! { panic_fmt(format_args!("{}", *x)); diff --git a/library/core/src/pin/unsafe_pinned.rs b/library/core/src/pin/unsafe_pinned.rs index b18b5d7c9ec0..ede6e0d6106b 100644 --- a/library/core/src/pin/unsafe_pinned.rs +++ b/library/core/src/pin/unsafe_pinned.rs @@ -120,8 +120,8 @@ impl UnsafePinned { #[inline(always)] #[must_use] #[unstable(feature = "unsafe_pinned", issue = "125735")] - pub const fn raw_get(this: *const Self) -> *const T { - this as *const T + pub const fn raw_get(this: *const Self) -> *mut T { + this as *const T as *mut T } /// Gets a mutable pointer to the wrapped value. diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs index 9a1ba7d1728c..1c824e336bed 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -271,6 +271,7 @@ mod prim_bool {} /// When the compiler sees a value of type `!` in a [coercion site], it implicitly inserts a /// coercion to allow the type checker to infer any type: /// +// FIXME: use `core::convert::absurd` here instead, once it's merged /// ```rust,ignore (illustrative-and-has-placeholders) /// // this /// let x: u8 = panic!(); @@ -281,7 +282,6 @@ mod prim_bool {} /// // where absurd is a function with the following signature /// // (it's sound, because `!` always marks unreachable code): /// fn absurd(_: !) -> T { ... } -// FIXME: use `core::convert::absurd` here instead, once it's merged /// ``` /// /// This can lead to compilation errors if the type cannot be inferred: diff --git a/library/core/src/ptr/alignment.rs b/library/core/src/ptr/alignment.rs index bd5b4e21baa0..402634e49b37 100644 --- a/library/core/src/ptr/alignment.rs +++ b/library/core/src/ptr/alignment.rs @@ -1,3 +1,5 @@ +#![allow(clippy::enum_clike_unportable_variant)] + use crate::num::NonZero; use crate::ub_checks::assert_unsafe_precondition; use crate::{cmp, fmt, hash, mem, num}; @@ -241,7 +243,7 @@ impl const Default for Alignment { #[cfg(target_pointer_width = "16")] #[derive(Copy, Clone, PartialEq, Eq)] -#[repr(u16)] +#[repr(usize)] enum AlignmentEnum { _Align1Shl0 = 1 << 0, _Align1Shl1 = 1 << 1, @@ -263,7 +265,7 @@ enum AlignmentEnum { #[cfg(target_pointer_width = "32")] #[derive(Copy, Clone, PartialEq, Eq)] -#[repr(u32)] +#[repr(usize)] enum AlignmentEnum { _Align1Shl0 = 1 << 0, _Align1Shl1 = 1 << 1, @@ -301,7 +303,7 @@ enum AlignmentEnum { #[cfg(target_pointer_width = "64")] #[derive(Copy, Clone, PartialEq, Eq)] -#[repr(u64)] +#[repr(usize)] enum AlignmentEnum { _Align1Shl0 = 1 << 0, _Align1Shl1 = 1 << 1, diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 2ad520b7ead7..c5f0cb8016e3 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -1430,6 +1430,28 @@ impl *const T { } } +impl *const T { + /// Casts from a type to its maybe-uninitialized version. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_uninit(self) -> *const MaybeUninit { + self as _ + } +} +impl *const MaybeUninit { + /// Casts from a maybe-uninitialized type to its initialized version. + /// + /// This is always safe, since UB can only occur if the pointer is read + /// before being initialized. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_init(self) -> *const T { + self as _ + } +} + impl *const [T] { /// Returns the length of a raw slice. /// @@ -1528,7 +1550,7 @@ impl *const [T] { #[inline] pub const unsafe fn get_unchecked(self, index: I) -> *const I::Output where - I: ~const SliceIndex<[T]>, + I: [const] SliceIndex<[T]>, { // SAFETY: the caller ensures that `self` is dereferenceable and `index` in-bounds. unsafe { index.get_unchecked(self) } @@ -1547,6 +1569,15 @@ impl *const [T] { } } +impl *const T { + /// Casts from a pointer-to-`T` to a pointer-to-`[T; N]`. + #[inline] + #[unstable(feature = "ptr_cast_array", issue = "144514")] + pub const fn cast_array(self) -> *const [T; N] { + self.cast() + } +} + impl *const [T; N] { /// Returns a raw pointer to the array's buffer. /// diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 1a2a5182567b..f5c490ca7cea 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -885,10 +885,10 @@ pub const fn without_provenance(addr: usize) -> *const T { /// This is useful for initializing types which lazily allocate, like /// `Vec::new` does. /// -/// Note that the pointer value may potentially represent a valid pointer to -/// a `T`, which means this must not be used as a "not yet initialized" -/// sentinel value. Types that lazily allocate must track initialization by -/// some other means. +/// Note that the address of the returned pointer may potentially +/// be that of a valid pointer, which means this must not be used +/// as a "not yet initialized" sentinel value. +/// Types that lazily allocate must track initialization by some other means. #[inline(always)] #[must_use] #[stable(feature = "strict_provenance", since = "1.84.0")] @@ -928,10 +928,10 @@ pub const fn without_provenance_mut(addr: usize) -> *mut T { /// This is useful for initializing types which lazily allocate, like /// `Vec::new` does. /// -/// Note that the pointer value may potentially represent a valid pointer to -/// a `T`, which means this must not be used as a "not yet initialized" -/// sentinel value. Types that lazily allocate must track initialization by -/// some other means. +/// Note that the address of the returned pointer may potentially +/// be that of a valid pointer, which means this must not be used +/// as a "not yet initialized" sentinel value. +/// Types that lazily allocate must track initialization by some other means. #[inline(always)] #[must_use] #[stable(feature = "strict_provenance", since = "1.84.0")] @@ -974,7 +974,7 @@ pub const fn dangling_mut() -> *mut T { #[must_use] #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] -#[rustc_const_unstable(feature = "const_exposed_provenance", issue = "144538")] +#[rustc_const_stable(feature = "const_exposed_provenance", since = "CURRENT_RUSTC_VERSION")] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[allow(fuzzy_provenance_casts)] // this *is* the explicit provenance API one should use instead pub const fn with_exposed_provenance(addr: usize) -> *const T { @@ -1015,7 +1015,7 @@ pub const fn with_exposed_provenance(addr: usize) -> *const T { #[must_use] #[inline(always)] #[stable(feature = "exposed_provenance", since = "1.84.0")] -#[rustc_const_unstable(feature = "const_exposed_provenance", issue = "144538")] +#[rustc_const_stable(feature = "const_exposed_provenance", since = "CURRENT_RUSTC_VERSION")] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[allow(fuzzy_provenance_casts)] // this *is* the explicit provenance API one should use instead pub const fn with_exposed_provenance_mut(addr: usize) -> *mut T { @@ -1347,40 +1347,6 @@ pub const unsafe fn swap(x: *mut T, y: *mut T) { /// assert_eq!(x, [7, 8, 3, 4]); /// assert_eq!(y, [1, 2, 9]); /// ``` -/// -/// # Const evaluation limitations -/// -/// If this function is invoked during const-evaluation, the current implementation has a small (and -/// rarely relevant) limitation: if `count` is at least 2 and the data pointed to by `x` or `y` -/// contains a pointer that crosses the boundary of two `T`-sized chunks of memory, the function may -/// fail to evaluate (similar to a panic during const-evaluation). This behavior may change in the -/// future. -/// -/// The limitation is illustrated by the following example: -/// -/// ``` -/// use std::mem::size_of; -/// use std::ptr; -/// -/// const { unsafe { -/// const PTR_SIZE: usize = size_of::<*const i32>(); -/// let mut data1 = [0u8; PTR_SIZE]; -/// let mut data2 = [0u8; PTR_SIZE]; -/// // Store a pointer in `data1`. -/// data1.as_mut_ptr().cast::<*const i32>().write_unaligned(&42); -/// // Swap the contents of `data1` and `data2` by swapping `PTR_SIZE` many `u8`-sized chunks. -/// // This call will fail, because the pointer in `data1` crosses the boundary -/// // between several of the 1-byte chunks that are being swapped here. -/// //ptr::swap_nonoverlapping(data1.as_mut_ptr(), data2.as_mut_ptr(), PTR_SIZE); -/// // Swap the contents of `data1` and `data2` by swapping a single chunk of size -/// // `[u8; PTR_SIZE]`. That works, as there is no pointer crossing the boundary between -/// // two chunks. -/// ptr::swap_nonoverlapping(&mut data1, &mut data2, 1); -/// // Read the pointer from `data2` and dereference it. -/// let ptr = data2.as_ptr().cast::<*const i32>().read_unaligned(); -/// assert!(*ptr == 42); -/// } } -/// ``` #[inline] #[stable(feature = "swap_nonoverlapping", since = "1.27.0")] #[rustc_const_stable(feature = "const_swap_nonoverlapping", since = "1.88.0")] @@ -1409,9 +1375,7 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { const_eval_select!( @capture[T] { x: *mut T, y: *mut T, count: usize }: if const { - // At compile-time we want to always copy this in chunks of `T`, to ensure that if there - // are pointers inside `T` we will copy them in one go rather than trying to copy a part - // of a pointer (which would not work). + // At compile-time we don't need all the special code below. // SAFETY: Same preconditions as this function unsafe { swap_nonoverlapping_const(x, y, count) } } else { diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 579e2461103d..3fe4b08d459e 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -1687,6 +1687,31 @@ impl *mut T { } } +impl *mut T { + /// Casts from a type to its maybe-uninitialized version. + /// + /// This is always safe, since UB can only occur if the pointer is read + /// before being initialized. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_uninit(self) -> *mut MaybeUninit { + self as _ + } +} +impl *mut MaybeUninit { + /// Casts from a maybe-uninitialized type to its initialized version. + /// + /// This is always safe, since UB can only occur if the pointer is read + /// before being initialized. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_init(self) -> *mut T { + self as _ + } +} + impl *mut [T] { /// Returns the length of a raw slice. /// @@ -1885,7 +1910,7 @@ impl *mut [T] { #[inline(always)] pub const unsafe fn get_unchecked_mut(self, index: I) -> *mut I::Output where - I: ~const SliceIndex<[T]>, + I: [const] SliceIndex<[T]>, { // SAFETY: the caller ensures that `self` is dereferenceable and `index` in-bounds. unsafe { index.get_unchecked_mut(self) } @@ -1965,6 +1990,15 @@ impl *mut [T] { } } +impl *mut T { + /// Casts from a pointer-to-`T` to a pointer-to-`[T; N]`. + #[inline] + #[unstable(feature = "ptr_cast_array", issue = "144514")] + pub const fn cast_array(self) -> *mut [T; N] { + self.cast() + } +} + impl *mut [T; N] { /// Returns a raw pointer to the array's buffer. /// diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index 62da6567cca7..117eb18826e4 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -109,10 +109,10 @@ impl NonNull { /// This is useful for initializing types which lazily allocate, like /// `Vec::new` does. /// - /// Note that the pointer value may potentially represent a valid pointer to - /// a `T`, which means this must not be used as a "not yet initialized" - /// sentinel value. Types that lazily allocate must track initialization by - /// some other means. + /// Note that the address of the returned pointer may potentially + /// be that of a valid pointer, which means this must not be used + /// as a "not yet initialized" sentinel value. + /// Types that lazily allocate must track initialization by some other means. /// /// # Examples /// @@ -193,6 +193,13 @@ impl NonNull { // requirements for a reference. unsafe { &mut *self.cast().as_ptr() } } + + /// Casts from a pointer-to-`T` to a pointer-to-`[T; N]`. + #[inline] + #[unstable(feature = "ptr_cast_array", issue = "144514")] + pub const fn cast_array(self) -> NonNull<[T; N]> { + self.cast() + } } impl NonNull { @@ -1357,6 +1364,28 @@ impl NonNull { } } +impl NonNull { + /// Casts from a type to its maybe-uninitialized version. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_uninit(self) -> NonNull> { + self.cast() + } +} +impl NonNull> { + /// Casts from a maybe-uninitialized type to its initialized version. + /// + /// This is always safe, since UB can only occur if the pointer is read + /// before being initialized. + #[must_use] + #[inline(always)] + #[unstable(feature = "cast_maybe_uninit", issue = "145036")] + pub const fn cast_init(self) -> NonNull { + self.cast() + } +} + impl NonNull<[T]> { /// Creates a non-null raw slice from a thin pointer and a length. /// @@ -1601,7 +1630,7 @@ impl NonNull<[T]> { #[inline] pub const unsafe fn get_unchecked_mut(self, index: I) -> NonNull where - I: ~const SliceIndex<[T]>, + I: [const] SliceIndex<[T]>, { // SAFETY: the caller ensures that `self` is dereferenceable and `index` in-bounds. // As a consequence, the resulting pointer cannot be null. diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs index e9e13f9e97f8..4302c1b1e445 100644 --- a/library/core/src/ptr/unique.rs +++ b/library/core/src/ptr/unique.rs @@ -63,10 +63,10 @@ impl Unique { /// This is useful for initializing types which lazily allocate, like /// `Vec::new` does. /// - /// Note that the pointer value may potentially represent a valid pointer to - /// a `T`, which means this must not be used as a "not yet initialized" - /// sentinel value. Types that lazily allocate must track initialization by - /// some other means. + /// Note that the address of the returned pointer may potentially + /// be that of a valid pointer, which means this must not be used + /// as a "not yet initialized" sentinel value. + /// Types that lazily allocate must track initialization by some other means. #[must_use] #[inline] pub const fn dangling() -> Self { diff --git a/library/core/src/result.rs b/library/core/src/result.rs index f65257ff59b9..6148bdb866ad 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -534,6 +534,7 @@ #![stable(feature = "rust1", since = "1.0.0")] use crate::iter::{self, FusedIterator, TrustedLen}; +use crate::marker::Destruct; use crate::ops::{self, ControlFlow, Deref, DerefMut}; use crate::{convert, fmt, hint}; @@ -606,7 +607,13 @@ impl Result { #[must_use] #[inline] #[stable(feature = "is_some_and", since = "1.70.0")] - pub fn is_ok_and(self, f: impl FnOnce(T) -> bool) -> bool { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn is_ok_and(self, f: F) -> bool + where + F: [const] FnOnce(T) -> bool + [const] Destruct, + T: [const] Destruct, + E: [const] Destruct, + { match self { Err(_) => false, Ok(x) => f(x), @@ -655,7 +662,13 @@ impl Result { #[must_use] #[inline] #[stable(feature = "is_some_and", since = "1.70.0")] - pub fn is_err_and(self, f: impl FnOnce(E) -> bool) -> bool { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn is_err_and(self, f: F) -> bool + where + F: [const] FnOnce(E) -> bool + [const] Destruct, + E: [const] Destruct, + T: [const] Destruct, + { match self { Ok(_) => false, Err(e) => f(e), @@ -682,8 +695,13 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] #[rustc_diagnostic_item = "result_ok_method"] - pub fn ok(self) -> Option { + pub const fn ok(self) -> Option + where + T: [const] Destruct, + E: [const] Destruct, + { match self { Ok(x) => Some(x), Err(_) => None, @@ -706,7 +724,12 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn err(self) -> Option { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn err(self) -> Option + where + T: [const] Destruct, + E: [const] Destruct, + { match self { Ok(_) => None, Err(x) => Some(x), @@ -796,7 +819,11 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn map U>(self, op: F) -> Result { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn map(self, op: F) -> Result + where + F: [const] FnOnce(T) -> U + [const] Destruct, + { match self { Ok(t) => Ok(op(t)), Err(e) => Err(e), @@ -823,8 +850,15 @@ impl Result { /// ``` #[inline] #[stable(feature = "result_map_or", since = "1.41.0")] + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] #[must_use = "if you don't need the returned value, use `if let` instead"] - pub fn map_or U>(self, default: U, f: F) -> U { + pub const fn map_or(self, default: U, f: F) -> U + where + F: [const] FnOnce(T) -> U + [const] Destruct, + T: [const] Destruct, + E: [const] Destruct, + U: [const] Destruct, + { match self { Ok(t) => f(t), Err(_) => default, @@ -851,7 +885,12 @@ impl Result { /// ``` #[inline] #[stable(feature = "result_map_or_else", since = "1.41.0")] - pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn map_or_else(self, default: D, f: F) -> U + where + D: [const] FnOnce(E) -> U + [const] Destruct, + F: [const] FnOnce(T) -> U + [const] Destruct, + { match self { Ok(t) => f(t), Err(e) => default(e), @@ -877,10 +916,13 @@ impl Result { /// [default value]: Default::default #[inline] #[unstable(feature = "result_option_map_or_default", issue = "138099")] - pub fn map_or_default(self, f: F) -> U + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn map_or_default(self, f: F) -> U where - U: Default, - F: FnOnce(T) -> U, + F: [const] FnOnce(T) -> U + [const] Destruct, + U: [const] Default, + T: [const] Destruct, + E: [const] Destruct, { match self { Ok(t) => f(t), @@ -908,7 +950,11 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn map_err F>(self, op: O) -> Result { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn map_err(self, op: O) -> Result + where + O: [const] FnOnce(E) -> F + [const] Destruct, + { match self { Ok(t) => Ok(t), Err(e) => Err(op(e)), @@ -930,7 +976,11 @@ impl Result { /// ``` #[inline] #[stable(feature = "result_option_inspect", since = "1.76.0")] - pub fn inspect(self, f: F) -> Self { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn inspect(self, f: F) -> Self + where + F: [const] FnOnce(&T) + [const] Destruct, + { if let Ok(ref t) = self { f(t); } @@ -954,7 +1004,11 @@ impl Result { /// ``` #[inline] #[stable(feature = "result_option_inspect", since = "1.76.0")] - pub fn inspect_err(self, f: F) -> Self { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn inspect_err(self, f: F) -> Self + where + F: [const] FnOnce(&E) + [const] Destruct, + { if let Err(ref e) = self { f(e); } @@ -1033,7 +1087,8 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn iter(&self) -> Iter<'_, T> { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn iter(&self) -> Iter<'_, T> { Iter { inner: self.as_ref().ok() } } @@ -1056,7 +1111,8 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn iter_mut(&mut self) -> IterMut<'_, T> { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn iter_mut(&mut self) -> IterMut<'_, T> { IterMut { inner: self.as_mut().ok() } } @@ -1195,9 +1251,11 @@ impl Result { /// [`FromStr`]: crate::str::FromStr #[inline] #[stable(feature = "result_unwrap_or_default", since = "1.16.0")] - pub fn unwrap_or_default(self) -> T + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn unwrap_or_default(self) -> T where - T: Default, + T: [const] Default + [const] Destruct, + E: [const] Destruct, { match self { Ok(x) => x, @@ -1292,7 +1350,7 @@ impl Result { #[rustc_const_unstable(feature = "const_try", issue = "74935")] pub const fn into_ok(self) -> T where - E: ~const Into, + E: [const] Into, { match self { Ok(x) => x, @@ -1329,7 +1387,7 @@ impl Result { #[rustc_const_unstable(feature = "const_try", issue = "74935")] pub const fn into_err(self) -> E where - T: ~const Into, + T: [const] Into, { match self { Ok(x) => x.into(), @@ -1370,7 +1428,13 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn and(self, res: Result) -> Result { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn and(self, res: Result) -> Result + where + T: [const] Destruct, + E: [const] Destruct, + U: [const] Destruct, + { match self { Ok(_) => res, Err(e) => Err(e), @@ -1409,8 +1473,12 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] #[rustc_confusables("flat_map", "flatmap")] - pub fn and_then Result>(self, op: F) -> Result { + pub const fn and_then(self, op: F) -> Result + where + F: [const] FnOnce(T) -> Result + [const] Destruct, + { match self { Ok(t) => op(t), Err(e) => Err(e), @@ -1446,7 +1514,13 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn or(self, res: Result) -> Result { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn or(self, res: Result) -> Result + where + T: [const] Destruct, + E: [const] Destruct, + F: [const] Destruct, + { match self { Ok(v) => Ok(v), Err(_) => res, @@ -1471,7 +1545,11 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn or_else Result>(self, op: O) -> Result { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn or_else(self, op: O) -> Result + where + O: [const] FnOnce(E) -> Result + [const] Destruct, + { match self { Ok(t) => Ok(t), Err(e) => op(e), @@ -1498,7 +1576,12 @@ impl Result { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub fn unwrap_or(self, default: T) -> T { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn unwrap_or(self, default: T) -> T + where + T: [const] Destruct, + E: [const] Destruct, + { match self { Ok(t) => t, Err(_) => default, @@ -1519,7 +1602,11 @@ impl Result { #[inline] #[track_caller] #[stable(feature = "rust1", since = "1.0.0")] - pub fn unwrap_or_else T>(self, op: F) -> T { + #[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")] + pub const fn unwrap_or_else(self, op: F) -> T + where + F: [const] FnOnce(E) -> T + [const] Destruct, + { match self { Ok(t) => t, Err(e) => op(e), @@ -1544,7 +1631,7 @@ impl Result { /// /// ```no_run /// let x: Result = Err("emergency failure"); - /// unsafe { x.unwrap_unchecked(); } // Undefined behavior! + /// unsafe { x.unwrap_unchecked() }; // Undefined behavior! /// ``` #[inline] #[track_caller] @@ -1762,7 +1849,7 @@ impl Result, E> { #[cold] #[track_caller] fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! { - panic!("{msg}: {error:?}") + panic!("{msg}: {error:?}"); } // This is a separate function to avoid constructing a `dyn Debug` @@ -1773,7 +1860,7 @@ fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! { #[inline] #[cold] #[track_caller] -fn unwrap_failed(_msg: &str, _error: &T) -> ! { +const fn unwrap_failed(_msg: &str, _error: &T) -> ! { panic!() } @@ -2077,7 +2164,7 @@ impl const ops::Try for Result { #[unstable(feature = "try_trait_v2", issue = "84277", old_name = "try_trait")] #[rustc_const_unstable(feature = "const_try", issue = "74935")] -impl> const ops::FromResidual> +impl> const ops::FromResidual> for Result { #[inline] @@ -2091,7 +2178,7 @@ impl> const ops::FromResidual> const ops::FromResidual> for Result { +impl> const ops::FromResidual> for Result { #[inline] fn from_residual(ops::Yeet(e): ops::Yeet) -> Self { Err(From::from(e)) diff --git a/library/core/src/slice/cmp.rs b/library/core/src/slice/cmp.rs index 1eda8bc1bec4..68bd12aa7bf2 100644 --- a/library/core/src/slice/cmp.rs +++ b/library/core/src/slice/cmp.rs @@ -11,7 +11,7 @@ use crate::ops::ControlFlow; #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] impl const PartialEq<[U]> for [T] where - T: ~const PartialEq, + T: [const] PartialEq, { fn eq(&self, other: &[U]) -> bool { SlicePartialEq::equal(self, other) @@ -109,7 +109,7 @@ trait SlicePartialEq { #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] impl const SlicePartialEq for [A] where - A: ~const PartialEq, + A: [const] PartialEq, { default fn equal(&self, other: &[B]) -> bool { if self.len() != other.len() { @@ -138,7 +138,7 @@ where #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] impl const SlicePartialEq for [A] where - A: ~const BytewiseEq, + A: [const] BytewiseEq, { fn equal(&self, other: &[B]) -> bool { if self.len() != other.len() { diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 322b3580eded..98091e9fe83f 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -9,7 +9,7 @@ use crate::{ops, range}; #[rustc_const_unstable(feature = "const_index", issue = "143775")] impl const ops::Index for [T] where - I: ~const SliceIndex<[T]>, + I: [const] SliceIndex<[T]>, { type Output = I::Output; @@ -23,7 +23,7 @@ where #[rustc_const_unstable(feature = "const_index", issue = "143775")] impl const ops::IndexMut for [T] where - I: ~const SliceIndex<[T]>, + I: [const] SliceIndex<[T]>, { #[inline(always)] fn index_mut(&mut self, index: I) -> &mut I::Output { @@ -34,53 +34,44 @@ where #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] #[cfg_attr(feature = "panic_immediate_abort", inline)] #[track_caller] -const fn slice_start_index_len_fail(index: usize, len: usize) -> ! { - const_panic!( - "slice start index is out of range for slice", - "range start index {index} out of range for slice of length {len}", - index: usize, - len: usize, - ) -} +const fn slice_index_fail(start: usize, end: usize, len: usize) -> ! { + if start > len { + const_panic!( + "slice start index is out of range for slice", + "range start index {start} out of range for slice of length {len}", + start: usize, + len: usize, + ) + } -#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] -#[cfg_attr(feature = "panic_immediate_abort", inline)] -#[track_caller] -const fn slice_end_index_len_fail(index: usize, len: usize) -> ! { - const_panic!( - "slice end index is out of range for slice", - "range end index {index} out of range for slice of length {len}", - index: usize, - len: usize, - ) -} + if end > len { + const_panic!( + "slice end index is out of range for slice", + "range end index {end} out of range for slice of length {len}", + end: usize, + len: usize, + ) + } -#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] -#[cfg_attr(feature = "panic_immediate_abort", inline)] -#[track_caller] -const fn slice_index_order_fail(index: usize, end: usize) -> ! { + if start > end { + const_panic!( + "slice index start is larger than end", + "slice index starts at {start} but ends at {end}", + start: usize, + end: usize, + ) + } + + // Only reachable if the range was a `RangeInclusive` or a + // `RangeToInclusive`, with `end == len`. const_panic!( - "slice index start is larger than end", - "slice index starts at {index} but ends at {end}", - index: usize, + "slice end index is out of range for slice", + "range end index {end} out of range for slice of length {len}", end: usize, + len: usize, ) } -#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] -#[cfg_attr(feature = "panic_immediate_abort", inline)] -#[track_caller] -const fn slice_start_index_overflow_fail() -> ! { - panic!("attempted to index slice from after maximum usize"); -} - -#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)] -#[cfg_attr(feature = "panic_immediate_abort", inline)] -#[track_caller] -const fn slice_end_index_overflow_fail() -> ! { - panic!("attempted to index slice up to maximum usize"); -} - // The UbChecks are great for catching bugs in the unsafe methods, but including // them in safe indexing is unnecessary and hurts inlining and debug runtime perf. // Both the safe and unsafe public methods share these helpers, @@ -341,7 +332,7 @@ unsafe impl const SliceIndex<[T]> for ops::IndexRange { // SAFETY: `self` is checked to be valid and in bounds above. unsafe { &*get_offset_len_noubcheck(slice, self.start(), self.len()) } } else { - slice_end_index_len_fail(self.end(), slice.len()) + slice_index_fail(self.start(), self.end(), slice.len()) } } @@ -351,7 +342,7 @@ unsafe impl const SliceIndex<[T]> for ops::IndexRange { // SAFETY: `self` is checked to be valid and in bounds above. unsafe { &mut *get_offset_len_mut_noubcheck(slice, self.start(), self.len()) } } else { - slice_end_index_len_fail(self.end(), slice.len()) + slice_index_fail(self.start(), self.end(), slice.len()) } } } @@ -436,26 +427,27 @@ unsafe impl const SliceIndex<[T]> for ops::Range { #[inline(always)] fn index(self, slice: &[T]) -> &[T] { // Using checked_sub is a safe way to get `SubUnchecked` in MIR - let Some(new_len) = usize::checked_sub(self.end, self.start) else { - slice_index_order_fail(self.start, self.end) - }; - if self.end > slice.len() { - slice_end_index_len_fail(self.end, slice.len()); + if let Some(new_len) = usize::checked_sub(self.end, self.start) + && self.end <= slice.len() + { + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { &*get_offset_len_noubcheck(slice, self.start, new_len) } + } else { + slice_index_fail(self.start, self.end, slice.len()) } - // SAFETY: `self` is checked to be valid and in bounds above. - unsafe { &*get_offset_len_noubcheck(slice, self.start, new_len) } } #[inline] fn index_mut(self, slice: &mut [T]) -> &mut [T] { - let Some(new_len) = usize::checked_sub(self.end, self.start) else { - slice_index_order_fail(self.start, self.end) - }; - if self.end > slice.len() { - slice_end_index_len_fail(self.end, slice.len()); + // Using checked_sub is a safe way to get `SubUnchecked` in MIR + if let Some(new_len) = usize::checked_sub(self.end, self.start) + && self.end <= slice.len() + { + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { &mut *get_offset_len_mut_noubcheck(slice, self.start, new_len) } + } else { + slice_index_fail(self.start, self.end, slice.len()) } - // SAFETY: `self` is checked to be valid and in bounds above. - unsafe { &mut *get_offset_len_mut_noubcheck(slice, self.start, new_len) } } } @@ -567,7 +559,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { #[inline] fn index(self, slice: &[T]) -> &[T] { if self.start > slice.len() { - slice_start_index_len_fail(self.start, slice.len()); + slice_index_fail(self.start, slice.len(), slice.len()) } // SAFETY: `self` is checked to be valid and in bounds above. unsafe { &*self.get_unchecked(slice) } @@ -576,7 +568,7 @@ unsafe impl const SliceIndex<[T]> for ops::RangeFrom { #[inline] fn index_mut(self, slice: &mut [T]) -> &mut [T] { if self.start > slice.len() { - slice_start_index_len_fail(self.start, slice.len()); + slice_index_fail(self.start, slice.len(), slice.len()) } // SAFETY: `self` is checked to be valid and in bounds above. unsafe { &mut *self.get_unchecked_mut(slice) } @@ -690,18 +682,32 @@ unsafe impl const SliceIndex<[T]> for ops::RangeInclusive { #[inline] fn index(self, slice: &[T]) -> &[T] { - if *self.end() == usize::MAX { - slice_end_index_overflow_fail(); + let Self { mut start, mut end, exhausted } = self; + let len = slice.len(); + if end < len { + end = end + 1; + start = if exhausted { end } else { start }; + if let Some(new_len) = usize::checked_sub(end, start) { + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { return &*get_offset_len_noubcheck(slice, start, new_len) } + } } - self.into_slice_range().index(slice) + slice_index_fail(start, end, slice.len()) } #[inline] fn index_mut(self, slice: &mut [T]) -> &mut [T] { - if *self.end() == usize::MAX { - slice_end_index_overflow_fail(); + let Self { mut start, mut end, exhausted } = self; + let len = slice.len(); + if end < len { + end = end + 1; + start = if exhausted { end } else { start }; + if let Some(new_len) = usize::checked_sub(end, start) { + // SAFETY: `self` is checked to be valid and in bounds above. + unsafe { return &mut *get_offset_len_mut_noubcheck(slice, start, new_len) } + } } - self.into_slice_range().index_mut(slice) + slice_index_fail(start, end, slice.len()) } } @@ -852,28 +858,26 @@ where { let len = bounds.end; - let start = match range.start_bound() { - ops::Bound::Included(&start) => start, - ops::Bound::Excluded(start) => { - start.checked_add(1).unwrap_or_else(|| slice_start_index_overflow_fail()) - } - ops::Bound::Unbounded => 0, - }; - let end = match range.end_bound() { - ops::Bound::Included(end) => { - end.checked_add(1).unwrap_or_else(|| slice_end_index_overflow_fail()) - } + ops::Bound::Included(&end) if end >= len => slice_index_fail(0, end, len), + // Cannot overflow because `end < len` implies `end < usize::MAX`. + ops::Bound::Included(&end) => end + 1, + + ops::Bound::Excluded(&end) if end > len => slice_index_fail(0, end, len), ops::Bound::Excluded(&end) => end, ops::Bound::Unbounded => len, }; - if start > end { - slice_index_order_fail(start, end); - } - if end > len { - slice_end_index_len_fail(end, len); - } + let start = match range.start_bound() { + ops::Bound::Excluded(&start) if start >= end => slice_index_fail(start, end, len), + // Cannot overflow because `start < end` implies `start < usize::MAX`. + ops::Bound::Excluded(&start) => start + 1, + + ops::Bound::Included(&start) if start > end => slice_index_fail(start, end, len), + ops::Bound::Included(&start) => start, + + ops::Bound::Unbounded => 0, + }; ops::Range { start, end } } @@ -982,25 +986,27 @@ pub(crate) fn into_slice_range( len: usize, (start, end): (ops::Bound, ops::Bound), ) -> ops::Range { - use ops::Bound; - let start = match start { - Bound::Included(start) => start, - Bound::Excluded(start) => { - start.checked_add(1).unwrap_or_else(|| slice_start_index_overflow_fail()) - } - Bound::Unbounded => 0, - }; - let end = match end { - Bound::Included(end) => { - end.checked_add(1).unwrap_or_else(|| slice_end_index_overflow_fail()) - } - Bound::Excluded(end) => end, - Bound::Unbounded => len, + ops::Bound::Included(end) if end >= len => slice_index_fail(0, end, len), + // Cannot overflow because `end < len` implies `end < usize::MAX`. + ops::Bound::Included(end) => end + 1, + + ops::Bound::Excluded(end) if end > len => slice_index_fail(0, end, len), + ops::Bound::Excluded(end) => end, + + ops::Bound::Unbounded => len, }; - // Don't bother with checking `start < end` and `end <= len` - // since these checks are handled by `Range` impls + let start = match start { + ops::Bound::Excluded(start) if start >= end => slice_index_fail(start, end, len), + // Cannot overflow because `start < end` implies `start < usize::MAX`. + ops::Bound::Excluded(start) => start + 1, + + ops::Bound::Included(start) if start > end => slice_index_fail(start, end, len), + ops::Bound::Included(start) => start, + + ops::Bound::Unbounded => 0, + }; start..end } diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 14042997bc2c..dfbb3628350a 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -328,7 +328,7 @@ impl [T] { } else { // SAFETY: We explicitly check for the correct number of elements, // and do not let the reference outlive the slice. - Some(unsafe { &*(self.as_ptr().cast::<[T; N]>()) }) + Some(unsafe { &*(self.as_ptr().cast_array()) }) } } @@ -359,7 +359,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // do not let the reference outlive the slice, // and require exclusive access to the entire slice to mutate the chunk. - Some(unsafe { &mut *(self.as_mut_ptr().cast::<[T; N]>()) }) + Some(unsafe { &mut *(self.as_mut_ptr().cast_array()) }) } } @@ -387,7 +387,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // and do not let the references outlive the slice. - Some((unsafe { &*(first.as_ptr().cast::<[T; N]>()) }, tail)) + Some((unsafe { &*(first.as_ptr().cast_array()) }, tail)) } /// Returns a mutable array reference to the first `N` items in the slice and the remaining @@ -420,7 +420,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // do not let the reference outlive the slice, // and enforce exclusive mutability of the chunk by the split. - Some((unsafe { &mut *(first.as_mut_ptr().cast::<[T; N]>()) }, tail)) + Some((unsafe { &mut *(first.as_mut_ptr().cast_array()) }, tail)) } /// Returns an array reference to the last `N` items in the slice and the remaining slice. @@ -448,7 +448,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // and do not let the references outlive the slice. - Some((init, unsafe { &*(last.as_ptr().cast::<[T; N]>()) })) + Some((init, unsafe { &*(last.as_ptr().cast_array()) })) } /// Returns a mutable array reference to the last `N` items in the slice and the remaining @@ -482,7 +482,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // do not let the reference outlive the slice, // and enforce exclusive mutability of the chunk by the split. - Some((init, unsafe { &mut *(last.as_mut_ptr().cast::<[T; N]>()) })) + Some((init, unsafe { &mut *(last.as_mut_ptr().cast_array()) })) } /// Returns an array reference to the last `N` items in the slice. @@ -511,7 +511,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // and do not let the references outlive the slice. - Some(unsafe { &*(last.as_ptr().cast::<[T; N]>()) }) + Some(unsafe { &*(last.as_ptr().cast_array()) }) } /// Returns a mutable array reference to the last `N` items in the slice. @@ -542,7 +542,7 @@ impl [T] { // SAFETY: We explicitly check for the correct number of elements, // do not let the reference outlive the slice, // and require exclusive access to the entire slice to mutate the chunk. - Some(unsafe { &mut *(last.as_mut_ptr().cast::<[T; N]>()) }) + Some(unsafe { &mut *(last.as_mut_ptr().cast_array()) }) } /// Returns a reference to an element or subslice depending on the type of @@ -569,7 +569,7 @@ impl [T] { #[rustc_const_unstable(feature = "const_index", issue = "143775")] pub const fn get(&self, index: I) -> Option<&I::Output> where - I: ~const SliceIndex, + I: [const] SliceIndex, { index.get(self) } @@ -596,7 +596,7 @@ impl [T] { #[rustc_const_unstable(feature = "const_index", issue = "143775")] pub const fn get_mut(&mut self, index: I) -> Option<&mut I::Output> where - I: ~const SliceIndex, + I: [const] SliceIndex, { index.get_mut(self) } @@ -636,7 +636,7 @@ impl [T] { #[rustc_const_unstable(feature = "const_index", issue = "143775")] pub const unsafe fn get_unchecked(&self, index: I) -> &I::Output where - I: ~const SliceIndex, + I: [const] SliceIndex, { // SAFETY: the caller must uphold most of the safety requirements for `get_unchecked`; // the slice is dereferenceable because `self` is a safe reference. @@ -681,7 +681,7 @@ impl [T] { #[rustc_const_unstable(feature = "const_index", issue = "143775")] pub const unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output where - I: ~const SliceIndex, + I: [const] SliceIndex, { // SAFETY: the caller must uphold the safety requirements for `get_unchecked_mut`; // the slice is dereferenceable because `self` is a safe reference. @@ -846,7 +846,7 @@ impl [T] { #[must_use] pub const fn as_array(&self) -> Option<&[T; N]> { if self.len() == N { - let ptr = self.as_ptr() as *const [T; N]; + let ptr = self.as_ptr().cast_array(); // SAFETY: The underlying array of a slice can be reinterpreted as an actual array `[T; N]` if `N` is not greater than the slice's length. let me = unsafe { &*ptr }; @@ -864,7 +864,7 @@ impl [T] { #[must_use] pub const fn as_mut_array(&mut self) -> Option<&mut [T; N]> { if self.len() == N { - let ptr = self.as_mut_ptr() as *mut [T; N]; + let ptr = self.as_mut_ptr().cast_array(); // SAFETY: The underlying array of a slice can be reinterpreted as an actual array `[T; N]` if `N` is not greater than the slice's length. let me = unsafe { &mut *ptr }; @@ -969,7 +969,7 @@ impl [T] { /// assert!(v == [3, 2, 1]); /// ``` #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_slice_reverse", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_slice_reverse", since = "1.90.0")] #[inline] pub const fn reverse(&mut self) { let half_len = self.len() / 2; @@ -3974,8 +3974,9 @@ impl [T] { /// /// [`split_at_mut`]: slice::split_at_mut #[stable(feature = "swap_with_slice", since = "1.27.0")] + #[rustc_const_unstable(feature = "const_swap_with_slice", issue = "142204")] #[track_caller] - pub fn swap_with_slice(&mut self, other: &mut [T]) { + pub const fn swap_with_slice(&mut self, other: &mut [T]) { assert!(self.len() == other.len(), "destination and source slices have different lengths"); // SAFETY: `self` is valid for `self.len()` elements by definition, and `src` was // checked to have the same length. The slices cannot overlap because diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index c40af4de7e03..1b6e84175b93 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -603,7 +603,7 @@ impl str { #[stable(feature = "str_checked_slicing", since = "1.20.0")] #[rustc_const_unstable(feature = "const_index", issue = "143775")] #[inline] - pub const fn get>(&self, i: I) -> Option<&I::Output> { + pub const fn get>(&self, i: I) -> Option<&I::Output> { i.get(self) } @@ -636,7 +636,7 @@ impl str { #[stable(feature = "str_checked_slicing", since = "1.20.0")] #[rustc_const_unstable(feature = "const_index", issue = "143775")] #[inline] - pub const fn get_mut>(&mut self, i: I) -> Option<&mut I::Output> { + pub const fn get_mut>(&mut self, i: I) -> Option<&mut I::Output> { i.get_mut(self) } diff --git a/library/core/src/str/traits.rs b/library/core/src/str/traits.rs index 1597d1c1fa86..dc88f35eca7e 100644 --- a/library/core/src/str/traits.rs +++ b/library/core/src/str/traits.rs @@ -53,7 +53,7 @@ impl PartialOrd for str { #[rustc_const_unstable(feature = "const_index", issue = "143775")] impl const ops::Index for str where - I: ~const SliceIndex, + I: [const] SliceIndex, { type Output = I::Output; @@ -67,7 +67,7 @@ where #[rustc_const_unstable(feature = "const_index", issue = "143775")] impl const ops::IndexMut for str where - I: ~const SliceIndex, + I: [const] SliceIndex, { #[inline] fn index_mut(&mut self, index: I) -> &mut I::Output { diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index 70c02ead3584..44a6895f90ac 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -2293,7 +2293,7 @@ impl AtomicPtr { #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn fetch_byte_add(&self, val: usize, order: Ordering) -> *mut T { // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_add(self.p.get(), core::ptr::without_provenance_mut(val), order).cast() } + unsafe { atomic_add(self.p.get(), val, order).cast() } } /// Offsets the pointer's address by subtracting `val` *bytes*, returning the @@ -2318,9 +2318,10 @@ impl AtomicPtr { /// #![feature(strict_provenance_atomic_ptr)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// - /// let atom = AtomicPtr::::new(core::ptr::without_provenance_mut(1)); - /// assert_eq!(atom.fetch_byte_sub(1, Ordering::Relaxed).addr(), 1); - /// assert_eq!(atom.load(Ordering::Relaxed).addr(), 0); + /// let mut arr = [0i64, 1]; + /// let atom = AtomicPtr::::new(&raw mut arr[1]); + /// assert_eq!(atom.fetch_byte_sub(8, Ordering::Relaxed).addr(), (&raw const arr[1]).addr()); + /// assert_eq!(atom.load(Ordering::Relaxed).addr(), (&raw const arr[0]).addr()); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] @@ -2328,7 +2329,7 @@ impl AtomicPtr { #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn fetch_byte_sub(&self, val: usize, order: Ordering) -> *mut T { // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_sub(self.p.get(), core::ptr::without_provenance_mut(val), order).cast() } + unsafe { atomic_sub(self.p.get(), val, order).cast() } } /// Performs a bitwise "or" operation on the address of the current pointer, @@ -2379,7 +2380,7 @@ impl AtomicPtr { #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn fetch_or(&self, val: usize, order: Ordering) -> *mut T { // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_or(self.p.get(), core::ptr::without_provenance_mut(val), order).cast() } + unsafe { atomic_or(self.p.get(), val, order).cast() } } /// Performs a bitwise "and" operation on the address of the current @@ -2429,7 +2430,7 @@ impl AtomicPtr { #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn fetch_and(&self, val: usize, order: Ordering) -> *mut T { // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_and(self.p.get(), core::ptr::without_provenance_mut(val), order).cast() } + unsafe { atomic_and(self.p.get(), val, order).cast() } } /// Performs a bitwise "xor" operation on the address of the current @@ -2477,7 +2478,7 @@ impl AtomicPtr { #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn fetch_xor(&self, val: usize, order: Ordering) -> *mut T { // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_xor(self.p.get(), core::ptr::without_provenance_mut(val), order).cast() } + unsafe { atomic_xor(self.p.get(), val, order).cast() } } /// Returns a mutable pointer to the underlying pointer. @@ -3981,15 +3982,15 @@ unsafe fn atomic_swap(dst: *mut T, val: T, order: Ordering) -> T { #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_add(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_add(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_add`. unsafe { match order { - Relaxed => intrinsics::atomic_xadd::(dst, val), - Acquire => intrinsics::atomic_xadd::(dst, val), - Release => intrinsics::atomic_xadd::(dst, val), - AcqRel => intrinsics::atomic_xadd::(dst, val), - SeqCst => intrinsics::atomic_xadd::(dst, val), + Relaxed => intrinsics::atomic_xadd::(dst, val), + Acquire => intrinsics::atomic_xadd::(dst, val), + Release => intrinsics::atomic_xadd::(dst, val), + AcqRel => intrinsics::atomic_xadd::(dst, val), + SeqCst => intrinsics::atomic_xadd::(dst, val), } } } @@ -3998,15 +3999,15 @@ unsafe fn atomic_add(dst: *mut T, val: T, order: Ordering) -> T { #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_sub(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_sub(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_sub`. unsafe { match order { - Relaxed => intrinsics::atomic_xsub::(dst, val), - Acquire => intrinsics::atomic_xsub::(dst, val), - Release => intrinsics::atomic_xsub::(dst, val), - AcqRel => intrinsics::atomic_xsub::(dst, val), - SeqCst => intrinsics::atomic_xsub::(dst, val), + Relaxed => intrinsics::atomic_xsub::(dst, val), + Acquire => intrinsics::atomic_xsub::(dst, val), + Release => intrinsics::atomic_xsub::(dst, val), + AcqRel => intrinsics::atomic_xsub::(dst, val), + SeqCst => intrinsics::atomic_xsub::(dst, val), } } } @@ -4147,15 +4148,15 @@ unsafe fn atomic_compare_exchange_weak( #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_and(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_and(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_and` unsafe { match order { - Relaxed => intrinsics::atomic_and::(dst, val), - Acquire => intrinsics::atomic_and::(dst, val), - Release => intrinsics::atomic_and::(dst, val), - AcqRel => intrinsics::atomic_and::(dst, val), - SeqCst => intrinsics::atomic_and::(dst, val), + Relaxed => intrinsics::atomic_and::(dst, val), + Acquire => intrinsics::atomic_and::(dst, val), + Release => intrinsics::atomic_and::(dst, val), + AcqRel => intrinsics::atomic_and::(dst, val), + SeqCst => intrinsics::atomic_and::(dst, val), } } } @@ -4163,15 +4164,15 @@ unsafe fn atomic_and(dst: *mut T, val: T, order: Ordering) -> T { #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_nand(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_nand(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_nand` unsafe { match order { - Relaxed => intrinsics::atomic_nand::(dst, val), - Acquire => intrinsics::atomic_nand::(dst, val), - Release => intrinsics::atomic_nand::(dst, val), - AcqRel => intrinsics::atomic_nand::(dst, val), - SeqCst => intrinsics::atomic_nand::(dst, val), + Relaxed => intrinsics::atomic_nand::(dst, val), + Acquire => intrinsics::atomic_nand::(dst, val), + Release => intrinsics::atomic_nand::(dst, val), + AcqRel => intrinsics::atomic_nand::(dst, val), + SeqCst => intrinsics::atomic_nand::(dst, val), } } } @@ -4179,15 +4180,15 @@ unsafe fn atomic_nand(dst: *mut T, val: T, order: Ordering) -> T { #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_or(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_or(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_or` unsafe { match order { - SeqCst => intrinsics::atomic_or::(dst, val), - Acquire => intrinsics::atomic_or::(dst, val), - Release => intrinsics::atomic_or::(dst, val), - AcqRel => intrinsics::atomic_or::(dst, val), - Relaxed => intrinsics::atomic_or::(dst, val), + SeqCst => intrinsics::atomic_or::(dst, val), + Acquire => intrinsics::atomic_or::(dst, val), + Release => intrinsics::atomic_or::(dst, val), + AcqRel => intrinsics::atomic_or::(dst, val), + Relaxed => intrinsics::atomic_or::(dst, val), } } } @@ -4195,15 +4196,15 @@ unsafe fn atomic_or(dst: *mut T, val: T, order: Ordering) -> T { #[inline] #[cfg(target_has_atomic)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces -unsafe fn atomic_xor(dst: *mut T, val: T, order: Ordering) -> T { +unsafe fn atomic_xor(dst: *mut T, val: U, order: Ordering) -> T { // SAFETY: the caller must uphold the safety contract for `atomic_xor` unsafe { match order { - SeqCst => intrinsics::atomic_xor::(dst, val), - Acquire => intrinsics::atomic_xor::(dst, val), - Release => intrinsics::atomic_xor::(dst, val), - AcqRel => intrinsics::atomic_xor::(dst, val), - Relaxed => intrinsics::atomic_xor::(dst, val), + SeqCst => intrinsics::atomic_xor::(dst, val), + Acquire => intrinsics::atomic_xor::(dst, val), + Release => intrinsics::atomic_xor::(dst, val), + AcqRel => intrinsics::atomic_xor::(dst, val), + Relaxed => intrinsics::atomic_xor::(dst, val), } } } diff --git a/library/core/src/task/poll.rs b/library/core/src/task/poll.rs index ca668361ef63..59ffe7ad49c0 100644 --- a/library/core/src/task/poll.rs +++ b/library/core/src/task/poll.rs @@ -125,7 +125,7 @@ impl Poll> { } } - /// Maps a `Poll::Ready>` to `Poll::Ready>` by + /// Maps a `Poll::Ready>` to `Poll::Ready>` by /// applying a function to a contained `Poll::Ready(Err)` value, leaving all other /// variants untouched. /// diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 0fb5c0bac756..0cc570f4b733 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -373,7 +373,6 @@ impl Duration { /// # Examples /// /// ``` - /// #![feature(duration_constructors_lite)] /// use std::time::Duration; /// /// let duration = Duration::from_hours(6); @@ -381,7 +380,8 @@ impl Duration { /// assert_eq!(6 * 60 * 60, duration.as_secs()); /// assert_eq!(0, duration.subsec_nanos()); /// ``` - #[unstable(feature = "duration_constructors_lite", issue = "140881")] + #[stable(feature = "duration_constructors_lite", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "duration_constructors_lite", since = "CURRENT_RUSTC_VERSION")] #[must_use] #[inline] pub const fn from_hours(hours: u64) -> Duration { @@ -401,7 +401,6 @@ impl Duration { /// # Examples /// /// ``` - /// #![feature(duration_constructors_lite)] /// use std::time::Duration; /// /// let duration = Duration::from_mins(10); @@ -409,7 +408,8 @@ impl Duration { /// assert_eq!(10 * 60, duration.as_secs()); /// assert_eq!(0, duration.subsec_nanos()); /// ``` - #[unstable(feature = "duration_constructors_lite", issue = "140881")] + #[stable(feature = "duration_constructors_lite", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "duration_constructors_lite", since = "CURRENT_RUSTC_VERSION")] #[must_use] #[inline] pub const fn from_mins(mins: u64) -> Duration { @@ -1100,7 +1100,8 @@ impl Duration { } #[stable(feature = "duration", since = "1.3.0")] -impl Add for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const Add for Duration { type Output = Duration; #[inline] @@ -1110,7 +1111,8 @@ impl Add for Duration { } #[stable(feature = "time_augmented_assignment", since = "1.9.0")] -impl AddAssign for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const AddAssign for Duration { #[inline] fn add_assign(&mut self, rhs: Duration) { *self = *self + rhs; @@ -1118,7 +1120,8 @@ impl AddAssign for Duration { } #[stable(feature = "duration", since = "1.3.0")] -impl Sub for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const Sub for Duration { type Output = Duration; #[inline] @@ -1128,7 +1131,8 @@ impl Sub for Duration { } #[stable(feature = "time_augmented_assignment", since = "1.9.0")] -impl SubAssign for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const SubAssign for Duration { #[inline] fn sub_assign(&mut self, rhs: Duration) { *self = *self - rhs; @@ -1136,7 +1140,8 @@ impl SubAssign for Duration { } #[stable(feature = "duration", since = "1.3.0")] -impl Mul for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const Mul for Duration { type Output = Duration; #[inline] @@ -1146,7 +1151,8 @@ impl Mul for Duration { } #[stable(feature = "symmetric_u32_duration_mul", since = "1.31.0")] -impl Mul for u32 { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const Mul for u32 { type Output = Duration; #[inline] @@ -1156,7 +1162,8 @@ impl Mul for u32 { } #[stable(feature = "time_augmented_assignment", since = "1.9.0")] -impl MulAssign for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const MulAssign for Duration { #[inline] fn mul_assign(&mut self, rhs: u32) { *self = *self * rhs; @@ -1164,7 +1171,8 @@ impl MulAssign for Duration { } #[stable(feature = "duration", since = "1.3.0")] -impl Div for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const Div for Duration { type Output = Duration; #[inline] @@ -1175,7 +1183,8 @@ impl Div for Duration { } #[stable(feature = "time_augmented_assignment", since = "1.9.0")] -impl DivAssign for Duration { +#[rustc_const_unstable(feature = "const_ops", issue = "143802")] +impl const DivAssign for Duration { #[inline] #[track_caller] fn div_assign(&mut self, rhs: u32) { diff --git a/library/core/src/unicode/mod.rs b/library/core/src/unicode/mod.rs index 49dbdeb1a6d1..191fe7711f97 100644 --- a/library/core/src/unicode/mod.rs +++ b/library/core/src/unicode/mod.rs @@ -1,5 +1,6 @@ +//! Unicode internals used in liballoc and libstd. Not public API. #![unstable(feature = "unicode_internals", issue = "none")] -#![allow(missing_docs)] +#![doc(hidden)] // for use in alloc, not re-exported in std. #[rustfmt::skip] @@ -31,5 +32,4 @@ mod unicode_data; /// /// The version numbering scheme is explained in /// [Unicode 11.0 or later, Section 3.1 Versions of the Unicode Standard](https://www.unicode.org/versions/Unicode11.0.0/ch03.pdf#page=4). -#[stable(feature = "unicode_version", since = "1.45.0")] pub const UNICODE_VERSION: (u8, u8, u8) = unicode_data::UNICODE_VERSION; diff --git a/library/coretests/benches/fmt.rs b/library/coretests/benches/fmt.rs index ee8e981b46b9..f45b921b9393 100644 --- a/library/coretests/benches/fmt.rs +++ b/library/coretests/benches/fmt.rs @@ -162,3 +162,183 @@ fn write_u8_min(bh: &mut Bencher) { black_box(format!("{}", black_box(u8::MIN))); }); } + +#[bench] +fn write_i8_bin(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:b}", black_box(0_i8)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(100_i8)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(-100_i8)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(1_i8 << 4)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i16_bin(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:b}", black_box(0_i16)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(100_i16)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(-100_i16)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(1_i16 << 8)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i32_bin(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:b}", black_box(0_i32)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(100_i32)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(-100_i32)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(1_i32 << 16)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i64_bin(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:b}", black_box(0_i64)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(100_i64)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(-100_i64)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(1_i64 << 32)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i128_bin(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:b}", black_box(0_i128)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(100_i128)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(-100_i128)).unwrap(); + write!(black_box(&mut buf), "{:b}", black_box(1_i128 << 64)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i8_oct(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:o}", black_box(0_i8)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(100_i8)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(-100_i8)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(1_i8 << 4)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i16_oct(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:o}", black_box(0_i16)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(100_i16)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(-100_i16)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(1_i16 << 8)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i32_oct(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:o}", black_box(0_i32)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(100_i32)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(-100_i32)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(1_i32 << 16)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i64_oct(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:o}", black_box(0_i64)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(100_i64)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(-100_i64)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(1_i64 << 32)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i128_oct(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:o}", black_box(0_i128)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(100_i128)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(-100_i128)).unwrap(); + write!(black_box(&mut buf), "{:o}", black_box(1_i128 << 64)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i8_hex(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:x}", black_box(0_i8)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(100_i8)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(-100_i8)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(1_i8 << 4)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i16_hex(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:x}", black_box(0_i16)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(100_i16)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(-100_i16)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(1_i16 << 8)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i32_hex(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:x}", black_box(0_i32)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(100_i32)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(-100_i32)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(1_i32 << 16)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i64_hex(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:x}", black_box(0_i64)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(100_i64)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(-100_i64)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(1_i64 << 32)).unwrap(); + black_box(&mut buf).clear(); + }); +} + +#[bench] +fn write_i128_hex(bh: &mut Bencher) { + let mut buf = String::with_capacity(256); + bh.iter(|| { + write!(black_box(&mut buf), "{:x}", black_box(0_i128)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(100_i128)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(-100_i128)).unwrap(); + write!(black_box(&mut buf), "{:x}", black_box(1_i128 << 64)).unwrap(); + black_box(&mut buf).clear(); + }); +} diff --git a/library/coretests/tests/char.rs b/library/coretests/tests/char.rs index 153fb36925e6..852f073bae18 100644 --- a/library/coretests/tests/char.rs +++ b/library/coretests/tests/char.rs @@ -21,7 +21,6 @@ fn test_convert() { assert!(char::try_from(0xFFFF_FFFF_u32).is_err()); } -/* FIXME(#110395) #[test] const fn test_convert_const() { assert!(u32::from('a') == 0x61); @@ -31,7 +30,6 @@ const fn test_convert_const() { assert!(char::from(b'a') == 'a'); assert!(char::from(b'\xFF') == '\u{FF}'); } -*/ #[test] fn test_from_str() { diff --git a/library/coretests/tests/convert.rs b/library/coretests/tests/convert.rs index f76dd277884e..f1048f4cf09c 100644 --- a/library/coretests/tests/convert.rs +++ b/library/coretests/tests/convert.rs @@ -1,4 +1,3 @@ -/* FIXME(#110395) #[test] fn convert() { const fn from(x: i32) -> i32 { @@ -15,4 +14,3 @@ fn convert() { const BAR: Vec = into(Vec::new()); assert_eq!(BAR, Vec::::new()); } -*/ diff --git a/library/coretests/tests/floats/f128.rs b/library/coretests/tests/floats/f128.rs index 36d6a20a9442..ac4a20665305 100644 --- a/library/coretests/tests/floats/f128.rs +++ b/library/coretests/tests/floats/f128.rs @@ -15,21 +15,6 @@ const TOL: f128 = 1e-12; /// signs. const TOL_PRECISE: f128 = 1e-28; -/// Smallest number -const TINY_BITS: u128 = 0x1; - -/// Next smallest number -const TINY_UP_BITS: u128 = 0x2; - -/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 -const MAX_DOWN_BITS: u128 = 0x7ffefffffffffffffffffffffffffffe; - -/// Zeroed exponent, full significant -const LARGEST_SUBNORMAL_BITS: u128 = 0x0000ffffffffffffffffffffffffffff; - -/// Exponent = 0b1, zeroed significand -const SMALLEST_NORMAL_BITS: u128 = 0x00010000000000000000000000000000; - /// First pattern over the mantissa const NAN_MASK1: u128 = 0x0000aaaaaaaaaaaaaaaaaaaaaaaaaaaa; @@ -39,106 +24,6 @@ const NAN_MASK2: u128 = 0x00005555555555555555555555555555; // FIXME(f16_f128,miri): many of these have to be disabled since miri does not yet support // the intrinsics. -#[test] -#[cfg(any(miri, target_has_reliable_f128_math))] -fn test_abs() { - assert_biteq!(f128::INFINITY.abs(), f128::INFINITY); - assert_biteq!(1f128.abs(), 1f128); - assert_biteq!(0f128.abs(), 0f128); - assert_biteq!((-0f128).abs(), 0f128); - assert_biteq!((-1f128).abs(), 1f128); - assert_biteq!(f128::NEG_INFINITY.abs(), f128::INFINITY); - assert_biteq!((1f128 / f128::NEG_INFINITY).abs(), 0f128); - assert!(f128::NAN.abs().is_nan()); -} - -#[test] -fn test_is_sign_positive() { - assert!(f128::INFINITY.is_sign_positive()); - assert!(1f128.is_sign_positive()); - assert!(0f128.is_sign_positive()); - assert!(!(-0f128).is_sign_positive()); - assert!(!(-1f128).is_sign_positive()); - assert!(!f128::NEG_INFINITY.is_sign_positive()); - assert!(!(1f128 / f128::NEG_INFINITY).is_sign_positive()); - assert!(f128::NAN.is_sign_positive()); - assert!(!(-f128::NAN).is_sign_positive()); -} - -#[test] -fn test_is_sign_negative() { - assert!(!f128::INFINITY.is_sign_negative()); - assert!(!1f128.is_sign_negative()); - assert!(!0f128.is_sign_negative()); - assert!((-0f128).is_sign_negative()); - assert!((-1f128).is_sign_negative()); - assert!(f128::NEG_INFINITY.is_sign_negative()); - assert!((1f128 / f128::NEG_INFINITY).is_sign_negative()); - assert!(!f128::NAN.is_sign_negative()); - assert!((-f128::NAN).is_sign_negative()); -} - -#[test] -fn test_next_up() { - let tiny = f128::from_bits(TINY_BITS); - let tiny_up = f128::from_bits(TINY_UP_BITS); - let max_down = f128::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f128::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f128::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f128::NEG_INFINITY.next_up(), f128::MIN); - assert_biteq!(f128::MIN.next_up(), -max_down); - assert_biteq!((-1.0 - f128::EPSILON).next_up(), -1.0f128); - assert_biteq!((-smallest_normal).next_up(), -largest_subnormal); - assert_biteq!((-tiny_up).next_up(), -tiny); - assert_biteq!((-tiny).next_up(), -0.0f128); - assert_biteq!((-0.0f128).next_up(), tiny); - assert_biteq!(0.0f128.next_up(), tiny); - assert_biteq!(tiny.next_up(), tiny_up); - assert_biteq!(largest_subnormal.next_up(), smallest_normal); - assert_biteq!(1.0f128.next_up(), 1.0 + f128::EPSILON); - assert_biteq!(f128::MAX.next_up(), f128::INFINITY); - assert_biteq!(f128::INFINITY.next_up(), f128::INFINITY); - - // Check that NaNs roundtrip. - let nan0 = f128::NAN; - let nan1 = f128::from_bits(f128::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f128::from_bits(f128::NAN.to_bits() ^ 0x0055_5555); - assert_biteq!(nan0.next_up(), nan0); - assert_biteq!(nan1.next_up(), nan1); - assert_biteq!(nan2.next_up(), nan2); -} - -#[test] -fn test_next_down() { - let tiny = f128::from_bits(TINY_BITS); - let tiny_up = f128::from_bits(TINY_UP_BITS); - let max_down = f128::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f128::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f128::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f128::NEG_INFINITY.next_down(), f128::NEG_INFINITY); - assert_biteq!(f128::MIN.next_down(), f128::NEG_INFINITY); - assert_biteq!((-max_down).next_down(), f128::MIN); - assert_biteq!((-1.0f128).next_down(), -1.0 - f128::EPSILON); - assert_biteq!((-largest_subnormal).next_down(), -smallest_normal); - assert_biteq!((-tiny).next_down(), -tiny_up); - assert_biteq!((-0.0f128).next_down(), -tiny); - assert_biteq!((0.0f128).next_down(), -tiny); - assert_biteq!(tiny.next_down(), 0.0f128); - assert_biteq!(tiny_up.next_down(), tiny); - assert_biteq!(smallest_normal.next_down(), largest_subnormal); - assert_biteq!((1.0 + f128::EPSILON).next_down(), 1.0f128); - assert_biteq!(f128::MAX.next_down(), max_down); - assert_biteq!(f128::INFINITY.next_down(), f128::MAX); - - // Check that NaNs roundtrip. - let nan0 = f128::NAN; - let nan1 = f128::from_bits(f128::NAN.to_bits() ^ 0x002a_aaaa); - let nan2 = f128::from_bits(f128::NAN.to_bits() ^ 0x0055_5555); - assert_biteq!(nan0.next_down(), nan0); - assert_biteq!(nan1.next_down(), nan1); - assert_biteq!(nan2.next_down(), nan2); -} - #[test] #[cfg(not(miri))] #[cfg(target_has_reliable_f128_math)] @@ -193,19 +78,6 @@ fn test_powi() { assert_biteq!(neg_inf.powi(2), inf); } -#[test] -#[cfg(not(miri))] -#[cfg(target_has_reliable_f128_math)] -fn test_sqrt_domain() { - assert!(f128::NAN.sqrt().is_nan()); - assert!(f128::NEG_INFINITY.sqrt().is_nan()); - assert!((-1.0f128).sqrt().is_nan()); - assert_biteq!((-0.0f128).sqrt(), -0.0); - assert_biteq!(0.0f128.sqrt(), 0.0); - assert_biteq!(1.0f128.sqrt(), 1.0); - assert_biteq!(f128::INFINITY.sqrt(), f128::INFINITY); -} - #[test] fn test_to_degrees() { let pi: f128 = consts::PI; @@ -260,168 +132,6 @@ fn test_float_bits_conv() { assert_eq!(f128::from_bits(masked_nan2).to_bits(), masked_nan2); } -#[test] -#[should_panic] -fn test_clamp_min_greater_than_max() { - let _ = 1.0f128.clamp(3.0, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_min_is_nan() { - let _ = 1.0f128.clamp(f128::NAN, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_max_is_nan() { - let _ = 1.0f128.clamp(3.0, f128::NAN); -} - -#[test] -fn test_total_cmp() { - use core::cmp::Ordering; - - fn quiet_bit_mask() -> u128 { - 1 << (f128::MANTISSA_DIGITS - 2) - } - - // FIXME(f16_f128): test subnormals when powf is available - // fn min_subnorm() -> f128 { - // f128::MIN_POSITIVE / f128::powf(2.0, f128::MANTISSA_DIGITS as f128 - 1.0) - // } - - // fn max_subnorm() -> f128 { - // f128::MIN_POSITIVE - min_subnorm() - // } - - fn q_nan() -> f128 { - f128::from_bits(f128::NAN.to_bits() | quiet_bit_mask()) - } - - fn s_nan() -> f128 { - f128::from_bits((f128::NAN.to_bits() & !quiet_bit_mask()) + 42) - } - - assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Equal, (-f128::INFINITY).total_cmp(&-f128::INFINITY)); - assert_eq!(Ordering::Equal, (-f128::MAX).total_cmp(&-f128::MAX)); - assert_eq!(Ordering::Equal, (-2.5_f128).total_cmp(&-2.5)); - assert_eq!(Ordering::Equal, (-1.0_f128).total_cmp(&-1.0)); - assert_eq!(Ordering::Equal, (-1.5_f128).total_cmp(&-1.5)); - assert_eq!(Ordering::Equal, (-0.5_f128).total_cmp(&-0.5)); - assert_eq!(Ordering::Equal, (-f128::MIN_POSITIVE).total_cmp(&-f128::MIN_POSITIVE)); - // assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Equal, (-0.0_f128).total_cmp(&-0.0)); - assert_eq!(Ordering::Equal, 0.0_f128.total_cmp(&0.0)); - // assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Equal, f128::MIN_POSITIVE.total_cmp(&f128::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, 0.5_f128.total_cmp(&0.5)); - assert_eq!(Ordering::Equal, 1.0_f128.total_cmp(&1.0)); - assert_eq!(Ordering::Equal, 1.5_f128.total_cmp(&1.5)); - assert_eq!(Ordering::Equal, 2.5_f128.total_cmp(&2.5)); - assert_eq!(Ordering::Equal, f128::MAX.total_cmp(&f128::MAX)); - assert_eq!(Ordering::Equal, f128::INFINITY.total_cmp(&f128::INFINITY)); - assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); - assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::INFINITY)); - assert_eq!(Ordering::Less, (-f128::INFINITY).total_cmp(&-f128::MAX)); - assert_eq!(Ordering::Less, (-f128::MAX).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-2.5_f128).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-1.5_f128).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-1.0_f128).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-0.5_f128).total_cmp(&-f128::MIN_POSITIVE)); - // assert_eq!(Ordering::Less, (-f128::MIN_POSITIVE).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); - // assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-0.0_f128).total_cmp(&0.0)); - // assert_eq!(Ordering::Less, 0.0_f128.total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); - // assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f128::MIN_POSITIVE)); - assert_eq!(Ordering::Less, f128::MIN_POSITIVE.total_cmp(&0.5)); - assert_eq!(Ordering::Less, 0.5_f128.total_cmp(&1.0)); - assert_eq!(Ordering::Less, 1.0_f128.total_cmp(&1.5)); - assert_eq!(Ordering::Less, 1.5_f128.total_cmp(&2.5)); - assert_eq!(Ordering::Less, 2.5_f128.total_cmp(&f128::MAX)); - assert_eq!(Ordering::Less, f128::MAX.total_cmp(&f128::INFINITY)); - assert_eq!(Ordering::Less, f128::INFINITY.total_cmp(&s_nan())); - assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Greater, (-f128::INFINITY).total_cmp(&-s_nan())); - assert_eq!(Ordering::Greater, (-f128::MAX).total_cmp(&-f128::INFINITY)); - assert_eq!(Ordering::Greater, (-2.5_f128).total_cmp(&-f128::MAX)); - assert_eq!(Ordering::Greater, (-1.5_f128).total_cmp(&-2.5)); - assert_eq!(Ordering::Greater, (-1.0_f128).total_cmp(&-1.5)); - assert_eq!(Ordering::Greater, (-0.5_f128).total_cmp(&-1.0)); - assert_eq!(Ordering::Greater, (-f128::MIN_POSITIVE).total_cmp(&-0.5)); - // assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f128::MIN_POSITIVE)); - // assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Greater, (-0.0_f128).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Greater, 0.0_f128.total_cmp(&-0.0)); - // assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); - // assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Greater, f128::MIN_POSITIVE.total_cmp(&max_subnorm())); - assert_eq!(Ordering::Greater, 0.5_f128.total_cmp(&f128::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, 1.0_f128.total_cmp(&0.5)); - assert_eq!(Ordering::Greater, 1.5_f128.total_cmp(&1.0)); - assert_eq!(Ordering::Greater, 2.5_f128.total_cmp(&1.5)); - assert_eq!(Ordering::Greater, f128::MAX.total_cmp(&2.5)); - assert_eq!(Ordering::Greater, f128::INFINITY.total_cmp(&f128::MAX)); - assert_eq!(Ordering::Greater, s_nan().total_cmp(&f128::INFINITY)); - assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f128::MIN_POSITIVE)); - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f128::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f128::MIN_POSITIVE)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f128::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); -} - #[test] fn test_algebraic() { let a: f128 = 123.0; diff --git a/library/coretests/tests/floats/f16.rs b/library/coretests/tests/floats/f16.rs index 351c008a37ba..bb9c8a002fe8 100644 --- a/library/coretests/tests/floats/f16.rs +++ b/library/coretests/tests/floats/f16.rs @@ -21,21 +21,6 @@ const TOL_P2: f16 = 0.5; #[allow(unused)] const TOL_P4: f16 = 10.0; -/// Smallest number -const TINY_BITS: u16 = 0x1; - -/// Next smallest number -const TINY_UP_BITS: u16 = 0x2; - -/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 -const MAX_DOWN_BITS: u16 = 0x7bfe; - -/// Zeroed exponent, full significant -const LARGEST_SUBNORMAL_BITS: u16 = 0x03ff; - -/// Exponent = 0b1, zeroed significand -const SMALLEST_NORMAL_BITS: u16 = 0x0400; - /// First pattern over the mantissa const NAN_MASK1: u16 = 0x02aa; @@ -45,106 +30,6 @@ const NAN_MASK2: u16 = 0x0155; // FIXME(f16_f128,miri): many of these have to be disabled since miri does not yet support // the intrinsics. -#[test] -#[cfg(any(miri, target_has_reliable_f16_math))] -fn test_abs() { - assert_biteq!(f16::INFINITY.abs(), f16::INFINITY); - assert_biteq!(1f16.abs(), 1f16); - assert_biteq!(0f16.abs(), 0f16); - assert_biteq!((-0f16).abs(), 0f16); - assert_biteq!((-1f16).abs(), 1f16); - assert_biteq!(f16::NEG_INFINITY.abs(), f16::INFINITY); - assert_biteq!((1f16 / f16::NEG_INFINITY).abs(), 0f16); - assert!(f16::NAN.abs().is_nan()); -} - -#[test] -fn test_is_sign_positive() { - assert!(f16::INFINITY.is_sign_positive()); - assert!(1f16.is_sign_positive()); - assert!(0f16.is_sign_positive()); - assert!(!(-0f16).is_sign_positive()); - assert!(!(-1f16).is_sign_positive()); - assert!(!f16::NEG_INFINITY.is_sign_positive()); - assert!(!(1f16 / f16::NEG_INFINITY).is_sign_positive()); - assert!(f16::NAN.is_sign_positive()); - assert!(!(-f16::NAN).is_sign_positive()); -} - -#[test] -fn test_is_sign_negative() { - assert!(!f16::INFINITY.is_sign_negative()); - assert!(!1f16.is_sign_negative()); - assert!(!0f16.is_sign_negative()); - assert!((-0f16).is_sign_negative()); - assert!((-1f16).is_sign_negative()); - assert!(f16::NEG_INFINITY.is_sign_negative()); - assert!((1f16 / f16::NEG_INFINITY).is_sign_negative()); - assert!(!f16::NAN.is_sign_negative()); - assert!((-f16::NAN).is_sign_negative()); -} - -#[test] -fn test_next_up() { - let tiny = f16::from_bits(TINY_BITS); - let tiny_up = f16::from_bits(TINY_UP_BITS); - let max_down = f16::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f16::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f16::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f16::NEG_INFINITY.next_up(), f16::MIN); - assert_biteq!(f16::MIN.next_up(), -max_down); - assert_biteq!((-1.0 - f16::EPSILON).next_up(), -1.0f16); - assert_biteq!((-smallest_normal).next_up(), -largest_subnormal); - assert_biteq!((-tiny_up).next_up(), -tiny); - assert_biteq!((-tiny).next_up(), -0.0f16); - assert_biteq!((-0.0f16).next_up(), tiny); - assert_biteq!(0.0f16.next_up(), tiny); - assert_biteq!(tiny.next_up(), tiny_up); - assert_biteq!(largest_subnormal.next_up(), smallest_normal); - assert_biteq!(1.0f16.next_up(), 1.0 + f16::EPSILON); - assert_biteq!(f16::MAX.next_up(), f16::INFINITY); - assert_biteq!(f16::INFINITY.next_up(), f16::INFINITY); - - // Check that NaNs roundtrip. - let nan0 = f16::NAN; - let nan1 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_up(), nan0); - assert_biteq!(nan1.next_up(), nan1); - assert_biteq!(nan2.next_up(), nan2); -} - -#[test] -fn test_next_down() { - let tiny = f16::from_bits(TINY_BITS); - let tiny_up = f16::from_bits(TINY_UP_BITS); - let max_down = f16::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f16::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f16::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f16::NEG_INFINITY.next_down(), f16::NEG_INFINITY); - assert_biteq!(f16::MIN.next_down(), f16::NEG_INFINITY); - assert_biteq!((-max_down).next_down(), f16::MIN); - assert_biteq!((-1.0f16).next_down(), -1.0 - f16::EPSILON); - assert_biteq!((-largest_subnormal).next_down(), -smallest_normal); - assert_biteq!((-tiny).next_down(), -tiny_up); - assert_biteq!((-0.0f16).next_down(), -tiny); - assert_biteq!((0.0f16).next_down(), -tiny); - assert_biteq!(tiny.next_down(), 0.0f16); - assert_biteq!(tiny_up.next_down(), tiny); - assert_biteq!(smallest_normal.next_down(), largest_subnormal); - assert_biteq!((1.0 + f16::EPSILON).next_down(), 1.0f16); - assert_biteq!(f16::MAX.next_down(), max_down); - assert_biteq!(f16::INFINITY.next_down(), f16::MAX); - - // Check that NaNs roundtrip. - let nan0 = f16::NAN; - let nan1 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f16::from_bits(f16::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_down(), nan0); - assert_biteq!(nan1.next_down(), nan1); - assert_biteq!(nan2.next_down(), nan2); -} - #[test] #[cfg(not(miri))] #[cfg(target_has_reliable_f16_math)] @@ -195,19 +80,6 @@ fn test_powi() { assert_biteq!(neg_inf.powi(2), inf); } -#[test] -#[cfg(not(miri))] -#[cfg(target_has_reliable_f16_math)] -fn test_sqrt_domain() { - assert!(f16::NAN.sqrt().is_nan()); - assert!(f16::NEG_INFINITY.sqrt().is_nan()); - assert!((-1.0f16).sqrt().is_nan()); - assert_biteq!((-0.0f16).sqrt(), -0.0); - assert_biteq!(0.0f16.sqrt(), 0.0); - assert_biteq!(1.0f16.sqrt(), 1.0); - assert_biteq!(f16::INFINITY.sqrt(), f16::INFINITY); -} - #[test] fn test_to_degrees() { let pi: f16 = consts::PI; @@ -259,172 +131,6 @@ fn test_float_bits_conv() { assert_eq!(f16::from_bits(masked_nan2).to_bits(), masked_nan2); } -#[test] -#[should_panic] -fn test_clamp_min_greater_than_max() { - let _ = 1.0f16.clamp(3.0, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_min_is_nan() { - let _ = 1.0f16.clamp(f16::NAN, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_max_is_nan() { - let _ = 1.0f16.clamp(3.0, f16::NAN); -} - -#[test] -#[cfg(not(miri))] -#[cfg(target_has_reliable_f16_math)] -fn test_total_cmp() { - use core::cmp::Ordering; - - fn quiet_bit_mask() -> u16 { - 1 << (f16::MANTISSA_DIGITS - 2) - } - - fn min_subnorm() -> f16 { - f16::MIN_POSITIVE / f16::powf(2.0, f16::MANTISSA_DIGITS as f16 - 1.0) - } - - fn max_subnorm() -> f16 { - f16::MIN_POSITIVE - min_subnorm() - } - - fn q_nan() -> f16 { - f16::from_bits(f16::NAN.to_bits() | quiet_bit_mask()) - } - - // FIXME(f16_f128): Tests involving sNaN are disabled because without optimizations, - // `total_cmp` is getting incorrectly lowered to code that includes a `extend`/`trunc` round - // trip, which quiets sNaNs. See: https://github.com/llvm/llvm-project/issues/104915 - // fn s_nan() -> f16 { - // f16::from_bits((f16::NAN.to_bits() & !quiet_bit_mask()) + 42) - // } - - assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); - // assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Equal, (-f16::INFINITY).total_cmp(&-f16::INFINITY)); - assert_eq!(Ordering::Equal, (-f16::MAX).total_cmp(&-f16::MAX)); - assert_eq!(Ordering::Equal, (-2.5_f16).total_cmp(&-2.5)); - assert_eq!(Ordering::Equal, (-1.0_f16).total_cmp(&-1.0)); - assert_eq!(Ordering::Equal, (-1.5_f16).total_cmp(&-1.5)); - assert_eq!(Ordering::Equal, (-0.5_f16).total_cmp(&-0.5)); - assert_eq!(Ordering::Equal, (-f16::MIN_POSITIVE).total_cmp(&-f16::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Equal, (-0.0_f16).total_cmp(&-0.0)); - assert_eq!(Ordering::Equal, 0.0_f16.total_cmp(&0.0)); - assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Equal, f16::MIN_POSITIVE.total_cmp(&f16::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, 0.5_f16.total_cmp(&0.5)); - assert_eq!(Ordering::Equal, 1.0_f16.total_cmp(&1.0)); - assert_eq!(Ordering::Equal, 1.5_f16.total_cmp(&1.5)); - assert_eq!(Ordering::Equal, 2.5_f16.total_cmp(&2.5)); - assert_eq!(Ordering::Equal, f16::MAX.total_cmp(&f16::MAX)); - assert_eq!(Ordering::Equal, f16::INFINITY.total_cmp(&f16::INFINITY)); - // assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); - assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); - - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::INFINITY)); - assert_eq!(Ordering::Less, (-f16::INFINITY).total_cmp(&-f16::MAX)); - assert_eq!(Ordering::Less, (-f16::MAX).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-2.5_f16).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-1.5_f16).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-1.0_f16).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-0.5_f16).total_cmp(&-f16::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-f16::MIN_POSITIVE).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-0.0_f16).total_cmp(&0.0)); - assert_eq!(Ordering::Less, 0.0_f16.total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f16::MIN_POSITIVE)); - assert_eq!(Ordering::Less, f16::MIN_POSITIVE.total_cmp(&0.5)); - assert_eq!(Ordering::Less, 0.5_f16.total_cmp(&1.0)); - assert_eq!(Ordering::Less, 1.0_f16.total_cmp(&1.5)); - assert_eq!(Ordering::Less, 1.5_f16.total_cmp(&2.5)); - assert_eq!(Ordering::Less, 2.5_f16.total_cmp(&f16::MAX)); - assert_eq!(Ordering::Less, f16::MAX.total_cmp(&f16::INFINITY)); - // assert_eq!(Ordering::Less, f16::INFINITY.total_cmp(&s_nan())); - // assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); - - // assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); - // assert_eq!(Ordering::Greater, (-f16::INFINITY).total_cmp(&-s_nan())); - assert_eq!(Ordering::Greater, (-f16::MAX).total_cmp(&-f16::INFINITY)); - assert_eq!(Ordering::Greater, (-2.5_f16).total_cmp(&-f16::MAX)); - assert_eq!(Ordering::Greater, (-1.5_f16).total_cmp(&-2.5)); - assert_eq!(Ordering::Greater, (-1.0_f16).total_cmp(&-1.5)); - assert_eq!(Ordering::Greater, (-0.5_f16).total_cmp(&-1.0)); - assert_eq!(Ordering::Greater, (-f16::MIN_POSITIVE).total_cmp(&-0.5)); - assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f16::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Greater, (-0.0_f16).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Greater, 0.0_f16.total_cmp(&-0.0)); - assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); - assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Greater, f16::MIN_POSITIVE.total_cmp(&max_subnorm())); - assert_eq!(Ordering::Greater, 0.5_f16.total_cmp(&f16::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, 1.0_f16.total_cmp(&0.5)); - assert_eq!(Ordering::Greater, 1.5_f16.total_cmp(&1.0)); - assert_eq!(Ordering::Greater, 2.5_f16.total_cmp(&1.5)); - assert_eq!(Ordering::Greater, f16::MAX.total_cmp(&2.5)); - assert_eq!(Ordering::Greater, f16::INFINITY.total_cmp(&f16::MAX)); - // assert_eq!(Ordering::Greater, s_nan().total_cmp(&f16::INFINITY)); - // assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); - - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f16::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f16::INFINITY)); - // assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); - - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::INFINITY)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::MAX)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f16::MIN_POSITIVE)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::MIN_POSITIVE)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::MAX)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f16::INFINITY)); - // assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); -} - #[test] fn test_algebraic() { let a: f16 = 123.0; diff --git a/library/coretests/tests/floats/f32.rs b/library/coretests/tests/floats/f32.rs index 267b0e4e2943..e77e44655dc8 100644 --- a/library/coretests/tests/floats/f32.rs +++ b/library/coretests/tests/floats/f32.rs @@ -3,21 +3,6 @@ use core::f32::consts; use super::{assert_approx_eq, assert_biteq}; -/// Smallest number -const TINY_BITS: u32 = 0x1; - -/// Next smallest number -const TINY_UP_BITS: u32 = 0x2; - -/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 -const MAX_DOWN_BITS: u32 = 0x7f7f_fffe; - -/// Zeroed exponent, full significant -const LARGEST_SUBNORMAL_BITS: u32 = 0x007f_ffff; - -/// Exponent = 0b1, zeroed significand -const SMALLEST_NORMAL_BITS: u32 = 0x0080_0000; - /// First pattern over the mantissa const NAN_MASK1: u32 = 0x002a_aaaa; @@ -29,117 +14,6 @@ const NAN_MASK2: u32 = 0x0055_5555; /// They serve as a way to get an idea of the real precision of floating point operations on different platforms. const APPROX_DELTA: f32 = if cfg!(miri) { 1e-4 } else { 1e-6 }; -#[test] -fn test_abs() { - assert_biteq!(f32::INFINITY.abs(), f32::INFINITY); - assert_biteq!(1f32.abs(), 1f32); - assert_biteq!(0f32.abs(), 0f32); - assert_biteq!((-0f32).abs(), 0f32); - assert_biteq!((-1f32).abs(), 1f32); - assert_biteq!(f32::NEG_INFINITY.abs(), f32::INFINITY); - assert_biteq!((1f32 / f32::NEG_INFINITY).abs(), 0f32); - assert!(f32::NAN.abs().is_nan()); -} - -#[test] -fn test_signum() { - assert_biteq!(f32::INFINITY.signum(), 1f32); - assert_biteq!(1f32.signum(), 1f32); - assert_biteq!(0f32.signum(), 1f32); - assert_biteq!((-0f32).signum(), -1f32); - assert_biteq!((-1f32).signum(), -1f32); - assert_biteq!(f32::NEG_INFINITY.signum(), -1f32); - assert_biteq!((1f32 / f32::NEG_INFINITY).signum(), -1f32); - assert!(f32::NAN.signum().is_nan()); -} - -#[test] -fn test_is_sign_positive() { - assert!(f32::INFINITY.is_sign_positive()); - assert!(1f32.is_sign_positive()); - assert!(0f32.is_sign_positive()); - assert!(!(-0f32).is_sign_positive()); - assert!(!(-1f32).is_sign_positive()); - assert!(!f32::NEG_INFINITY.is_sign_positive()); - assert!(!(1f32 / f32::NEG_INFINITY).is_sign_positive()); - assert!(f32::NAN.is_sign_positive()); - assert!(!(-f32::NAN).is_sign_positive()); -} - -#[test] -fn test_is_sign_negative() { - assert!(!f32::INFINITY.is_sign_negative()); - assert!(!1f32.is_sign_negative()); - assert!(!0f32.is_sign_negative()); - assert!((-0f32).is_sign_negative()); - assert!((-1f32).is_sign_negative()); - assert!(f32::NEG_INFINITY.is_sign_negative()); - assert!((1f32 / f32::NEG_INFINITY).is_sign_negative()); - assert!(!f32::NAN.is_sign_negative()); - assert!((-f32::NAN).is_sign_negative()); -} - -#[test] -fn test_next_up() { - let tiny = f32::from_bits(TINY_BITS); - let tiny_up = f32::from_bits(TINY_UP_BITS); - let max_down = f32::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f32::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f32::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f32::NEG_INFINITY.next_up(), f32::MIN); - assert_biteq!(f32::MIN.next_up(), -max_down); - assert_biteq!((-1.0f32 - f32::EPSILON).next_up(), -1.0f32); - assert_biteq!((-smallest_normal).next_up(), -largest_subnormal); - assert_biteq!((-tiny_up).next_up(), -tiny); - assert_biteq!((-tiny).next_up(), -0.0f32); - assert_biteq!((-0.0f32).next_up(), tiny); - assert_biteq!(0.0f32.next_up(), tiny); - assert_biteq!(tiny.next_up(), tiny_up); - assert_biteq!(largest_subnormal.next_up(), smallest_normal); - assert_biteq!(1.0f32.next_up(), 1.0 + f32::EPSILON); - assert_biteq!(f32::MAX.next_up(), f32::INFINITY); - assert_biteq!(f32::INFINITY.next_up(), f32::INFINITY); - - // Check that NaNs roundtrip. - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_up(), nan0); - assert_biteq!(nan1.next_up(), nan1); - assert_biteq!(nan2.next_up(), nan2); -} - -#[test] -fn test_next_down() { - let tiny = f32::from_bits(TINY_BITS); - let tiny_up = f32::from_bits(TINY_UP_BITS); - let max_down = f32::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f32::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f32::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f32::NEG_INFINITY.next_down(), f32::NEG_INFINITY); - assert_biteq!(f32::MIN.next_down(), f32::NEG_INFINITY); - assert_biteq!((-max_down).next_down(), f32::MIN); - assert_biteq!((-1.0f32).next_down(), -1.0 - f32::EPSILON); - assert_biteq!((-largest_subnormal).next_down(), -smallest_normal); - assert_biteq!((-tiny).next_down(), -tiny_up); - assert_biteq!((-0.0f32).next_down(), -tiny); - assert_biteq!((0.0f32).next_down(), -tiny); - assert_biteq!(tiny.next_down(), 0.0f32); - assert_biteq!(tiny_up.next_down(), tiny); - assert_biteq!(smallest_normal.next_down(), largest_subnormal); - assert_biteq!((1.0 + f32::EPSILON).next_down(), 1.0f32); - assert_biteq!(f32::MAX.next_down(), max_down); - assert_biteq!(f32::INFINITY.next_down(), f32::MAX); - - // Check that NaNs roundtrip. - let nan0 = f32::NAN; - let nan1 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f32::from_bits(f32::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_down(), nan0); - assert_biteq!(nan1.next_down(), nan1); - assert_biteq!(nan2.next_down(), nan2); -} - // FIXME(#140515): mingw has an incorrect fma https://sourceforge.net/p/mingw-w64/bugs/848/ #[cfg_attr(all(target_os = "windows", target_env = "gnu", not(target_abi = "llvm")), ignore)] #[test] @@ -186,17 +60,6 @@ fn test_powi() { assert_biteq!(neg_inf.powi(2), inf); } -#[test] -fn test_sqrt_domain() { - assert!(f32::NAN.sqrt().is_nan()); - assert!(f32::NEG_INFINITY.sqrt().is_nan()); - assert!((-1.0f32).sqrt().is_nan()); - assert_biteq!((-0.0f32).sqrt(), -0.0); - assert_biteq!(0.0f32.sqrt(), 0.0); - assert_biteq!(1.0f32.sqrt(), 1.0); - assert_biteq!(f32::INFINITY.sqrt(), f32::INFINITY); -} - #[test] fn test_to_degrees() { let pi: f32 = consts::PI; @@ -249,167 +112,6 @@ fn test_float_bits_conv() { assert_eq!(f32::from_bits(masked_nan2).to_bits(), masked_nan2); } -#[test] -#[should_panic] -fn test_clamp_min_greater_than_max() { - let _ = 1.0f32.clamp(3.0, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_min_is_nan() { - let _ = 1.0f32.clamp(f32::NAN, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_max_is_nan() { - let _ = 1.0f32.clamp(3.0, f32::NAN); -} - -#[test] -fn test_total_cmp() { - use core::cmp::Ordering; - - fn quiet_bit_mask() -> u32 { - 1 << (f32::MANTISSA_DIGITS - 2) - } - - fn min_subnorm() -> f32 { - f32::MIN_POSITIVE / f32::powf(2.0, f32::MANTISSA_DIGITS as f32 - 1.0) - } - - fn max_subnorm() -> f32 { - f32::MIN_POSITIVE - min_subnorm() - } - - fn q_nan() -> f32 { - f32::from_bits(f32::NAN.to_bits() | quiet_bit_mask()) - } - - fn s_nan() -> f32 { - f32::from_bits((f32::NAN.to_bits() & !quiet_bit_mask()) + 42) - } - - assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Equal, (-f32::INFINITY).total_cmp(&-f32::INFINITY)); - assert_eq!(Ordering::Equal, (-f32::MAX).total_cmp(&-f32::MAX)); - assert_eq!(Ordering::Equal, (-2.5_f32).total_cmp(&-2.5)); - assert_eq!(Ordering::Equal, (-1.0_f32).total_cmp(&-1.0)); - assert_eq!(Ordering::Equal, (-1.5_f32).total_cmp(&-1.5)); - assert_eq!(Ordering::Equal, (-0.5_f32).total_cmp(&-0.5)); - assert_eq!(Ordering::Equal, (-f32::MIN_POSITIVE).total_cmp(&-f32::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Equal, (-0.0_f32).total_cmp(&-0.0)); - assert_eq!(Ordering::Equal, 0.0_f32.total_cmp(&0.0)); - assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Equal, f32::MIN_POSITIVE.total_cmp(&f32::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, 0.5_f32.total_cmp(&0.5)); - assert_eq!(Ordering::Equal, 1.0_f32.total_cmp(&1.0)); - assert_eq!(Ordering::Equal, 1.5_f32.total_cmp(&1.5)); - assert_eq!(Ordering::Equal, 2.5_f32.total_cmp(&2.5)); - assert_eq!(Ordering::Equal, f32::MAX.total_cmp(&f32::MAX)); - assert_eq!(Ordering::Equal, f32::INFINITY.total_cmp(&f32::INFINITY)); - assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); - assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::INFINITY)); - assert_eq!(Ordering::Less, (-f32::INFINITY).total_cmp(&-f32::MAX)); - assert_eq!(Ordering::Less, (-f32::MAX).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-2.5_f32).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-1.5_f32).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-1.0_f32).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-0.5_f32).total_cmp(&-f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-f32::MIN_POSITIVE).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-0.0_f32).total_cmp(&0.0)); - assert_eq!(Ordering::Less, 0.0_f32.total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, f32::MIN_POSITIVE.total_cmp(&0.5)); - assert_eq!(Ordering::Less, 0.5_f32.total_cmp(&1.0)); - assert_eq!(Ordering::Less, 1.0_f32.total_cmp(&1.5)); - assert_eq!(Ordering::Less, 1.5_f32.total_cmp(&2.5)); - assert_eq!(Ordering::Less, 2.5_f32.total_cmp(&f32::MAX)); - assert_eq!(Ordering::Less, f32::MAX.total_cmp(&f32::INFINITY)); - assert_eq!(Ordering::Less, f32::INFINITY.total_cmp(&s_nan())); - assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Greater, (-f32::INFINITY).total_cmp(&-s_nan())); - assert_eq!(Ordering::Greater, (-f32::MAX).total_cmp(&-f32::INFINITY)); - assert_eq!(Ordering::Greater, (-2.5_f32).total_cmp(&-f32::MAX)); - assert_eq!(Ordering::Greater, (-1.5_f32).total_cmp(&-2.5)); - assert_eq!(Ordering::Greater, (-1.0_f32).total_cmp(&-1.5)); - assert_eq!(Ordering::Greater, (-0.5_f32).total_cmp(&-1.0)); - assert_eq!(Ordering::Greater, (-f32::MIN_POSITIVE).total_cmp(&-0.5)); - assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f32::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Greater, (-0.0_f32).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Greater, 0.0_f32.total_cmp(&-0.0)); - assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); - assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Greater, f32::MIN_POSITIVE.total_cmp(&max_subnorm())); - assert_eq!(Ordering::Greater, 0.5_f32.total_cmp(&f32::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, 1.0_f32.total_cmp(&0.5)); - assert_eq!(Ordering::Greater, 1.5_f32.total_cmp(&1.0)); - assert_eq!(Ordering::Greater, 2.5_f32.total_cmp(&1.5)); - assert_eq!(Ordering::Greater, f32::MAX.total_cmp(&2.5)); - assert_eq!(Ordering::Greater, f32::INFINITY.total_cmp(&f32::MAX)); - assert_eq!(Ordering::Greater, s_nan().total_cmp(&f32::INFINITY)); - assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f32::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); -} - #[test] fn test_algebraic() { let a: f32 = 123.0; diff --git a/library/coretests/tests/floats/f64.rs b/library/coretests/tests/floats/f64.rs index 735b7a765151..fea9cc19b39f 100644 --- a/library/coretests/tests/floats/f64.rs +++ b/library/coretests/tests/floats/f64.rs @@ -3,136 +3,12 @@ use core::f64::consts; use super::{assert_approx_eq, assert_biteq}; -/// Smallest number -const TINY_BITS: u64 = 0x1; - -/// Next smallest number -const TINY_UP_BITS: u64 = 0x2; - -/// Exponent = 0b11...10, Sifnificand 0b1111..10. Min val > 0 -const MAX_DOWN_BITS: u64 = 0x7fef_ffff_ffff_fffe; - -/// Zeroed exponent, full significant -const LARGEST_SUBNORMAL_BITS: u64 = 0x000f_ffff_ffff_ffff; - -/// Exponent = 0b1, zeroed significand -const SMALLEST_NORMAL_BITS: u64 = 0x0010_0000_0000_0000; - /// First pattern over the mantissa const NAN_MASK1: u64 = 0x000a_aaaa_aaaa_aaaa; /// Second pattern over the mantissa const NAN_MASK2: u64 = 0x0005_5555_5555_5555; -#[test] -fn test_abs() { - assert_biteq!(f64::INFINITY.abs(), f64::INFINITY); - assert_biteq!(1f64.abs(), 1f64); - assert_biteq!(0f64.abs(), 0f64); - assert_biteq!((-0f64).abs(), 0f64); - assert_biteq!((-1f64).abs(), 1f64); - assert_biteq!(f64::NEG_INFINITY.abs(), f64::INFINITY); - assert_biteq!((1f64 / f64::NEG_INFINITY).abs(), 0f64); - assert!(f64::NAN.abs().is_nan()); -} - -#[test] -fn test_signum() { - assert_biteq!(f64::INFINITY.signum(), 1f64); - assert_biteq!(1f64.signum(), 1f64); - assert_biteq!(0f64.signum(), 1f64); - assert_biteq!((-0f64).signum(), -1f64); - assert_biteq!((-1f64).signum(), -1f64); - assert_biteq!(f64::NEG_INFINITY.signum(), -1f64); - assert_biteq!((1f64 / f64::NEG_INFINITY).signum(), -1f64); - assert!(f64::NAN.signum().is_nan()); -} - -#[test] -fn test_is_sign_positive() { - assert!(f64::INFINITY.is_sign_positive()); - assert!(1f64.is_sign_positive()); - assert!(0f64.is_sign_positive()); - assert!(!(-0f64).is_sign_positive()); - assert!(!(-1f64).is_sign_positive()); - assert!(!f64::NEG_INFINITY.is_sign_positive()); - assert!(!(1f64 / f64::NEG_INFINITY).is_sign_positive()); - assert!(f64::NAN.is_sign_positive()); - assert!(!(-f64::NAN).is_sign_positive()); -} - -#[test] -fn test_is_sign_negative() { - assert!(!f64::INFINITY.is_sign_negative()); - assert!(!1f64.is_sign_negative()); - assert!(!0f64.is_sign_negative()); - assert!((-0f64).is_sign_negative()); - assert!((-1f64).is_sign_negative()); - assert!(f64::NEG_INFINITY.is_sign_negative()); - assert!((1f64 / f64::NEG_INFINITY).is_sign_negative()); - assert!(!f64::NAN.is_sign_negative()); - assert!((-f64::NAN).is_sign_negative()); -} - -#[test] -fn test_next_up() { - let tiny = f64::from_bits(TINY_BITS); - let tiny_up = f64::from_bits(TINY_UP_BITS); - let max_down = f64::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f64::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f64::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f64::NEG_INFINITY.next_up(), f64::MIN); - assert_biteq!(f64::MIN.next_up(), -max_down); - assert_biteq!((-1.0 - f64::EPSILON).next_up(), -1.0f64); - assert_biteq!((-smallest_normal).next_up(), -largest_subnormal); - assert_biteq!((-tiny_up).next_up(), -tiny); - assert_biteq!((-tiny).next_up(), -0.0f64); - assert_biteq!((-0.0f64).next_up(), tiny); - assert_biteq!(0.0f64.next_up(), tiny); - assert_biteq!(tiny.next_up(), tiny_up); - assert_biteq!(largest_subnormal.next_up(), smallest_normal); - assert_biteq!(1.0f64.next_up(), 1.0 + f64::EPSILON); - assert_biteq!(f64::MAX.next_up(), f64::INFINITY); - assert_biteq!(f64::INFINITY.next_up(), f64::INFINITY); - - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_up(), nan0); - assert_biteq!(nan1.next_up(), nan1); - assert_biteq!(nan2.next_up(), nan2); -} - -#[test] -fn test_next_down() { - let tiny = f64::from_bits(TINY_BITS); - let tiny_up = f64::from_bits(TINY_UP_BITS); - let max_down = f64::from_bits(MAX_DOWN_BITS); - let largest_subnormal = f64::from_bits(LARGEST_SUBNORMAL_BITS); - let smallest_normal = f64::from_bits(SMALLEST_NORMAL_BITS); - assert_biteq!(f64::NEG_INFINITY.next_down(), f64::NEG_INFINITY); - assert_biteq!(f64::MIN.next_down(), f64::NEG_INFINITY); - assert_biteq!((-max_down).next_down(), f64::MIN); - assert_biteq!((-1.0f64).next_down(), -1.0 - f64::EPSILON); - assert_biteq!((-largest_subnormal).next_down(), -smallest_normal); - assert_biteq!((-tiny).next_down(), -tiny_up); - assert_biteq!((-0.0f64).next_down(), -tiny); - assert_biteq!((0.0f64).next_down(), -tiny); - assert_biteq!(tiny.next_down(), 0.0f64); - assert_biteq!(tiny_up.next_down(), tiny); - assert_biteq!(smallest_normal.next_down(), largest_subnormal); - assert_biteq!((1.0 + f64::EPSILON).next_down(), 1.0f64); - assert_biteq!(f64::MAX.next_down(), max_down); - assert_biteq!(f64::INFINITY.next_down(), f64::MAX); - - let nan0 = f64::NAN; - let nan1 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK1); - let nan2 = f64::from_bits(f64::NAN.to_bits() ^ NAN_MASK2); - assert_biteq!(nan0.next_down(), nan0); - assert_biteq!(nan1.next_down(), nan1); - assert_biteq!(nan2.next_down(), nan2); -} - // FIXME(#140515): mingw has an incorrect fma https://sourceforge.net/p/mingw-w64/bugs/848/ #[cfg_attr(all(target_os = "windows", target_env = "gnu", not(target_abi = "llvm")), ignore)] #[test] @@ -179,17 +55,6 @@ fn test_powi() { assert_biteq!(neg_inf.powi(2), inf); } -#[test] -fn test_sqrt_domain() { - assert!(f64::NAN.sqrt().is_nan()); - assert!(f64::NEG_INFINITY.sqrt().is_nan()); - assert!((-1.0f64).sqrt().is_nan()); - assert_biteq!((-0.0f64).sqrt(), -0.0); - assert_biteq!(0.0f64.sqrt(), 0.0); - assert_biteq!(1.0f64.sqrt(), 1.0); - assert_biteq!(f64::INFINITY.sqrt(), f64::INFINITY); -} - #[test] fn test_to_degrees() { let pi: f64 = consts::PI; @@ -240,167 +105,6 @@ fn test_float_bits_conv() { assert_eq!(f64::from_bits(masked_nan2).to_bits(), masked_nan2); } -#[test] -#[should_panic] -fn test_clamp_min_greater_than_max() { - let _ = 1.0f64.clamp(3.0, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_min_is_nan() { - let _ = 1.0f64.clamp(f64::NAN, 1.0); -} - -#[test] -#[should_panic] -fn test_clamp_max_is_nan() { - let _ = 1.0f64.clamp(3.0, f64::NAN); -} - -#[test] -fn test_total_cmp() { - use core::cmp::Ordering; - - fn quiet_bit_mask() -> u64 { - 1 << (f64::MANTISSA_DIGITS - 2) - } - - fn min_subnorm() -> f64 { - f64::MIN_POSITIVE / f64::powf(2.0, f64::MANTISSA_DIGITS as f64 - 1.0) - } - - fn max_subnorm() -> f64 { - f64::MIN_POSITIVE - min_subnorm() - } - - fn q_nan() -> f64 { - f64::from_bits(f64::NAN.to_bits() | quiet_bit_mask()) - } - - fn s_nan() -> f64 { - f64::from_bits((f64::NAN.to_bits() & !quiet_bit_mask()) + 42) - } - - assert_eq!(Ordering::Equal, (-q_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Equal, (-s_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Equal, (-f64::INFINITY).total_cmp(&-f64::INFINITY)); - assert_eq!(Ordering::Equal, (-f64::MAX).total_cmp(&-f64::MAX)); - assert_eq!(Ordering::Equal, (-2.5_f64).total_cmp(&-2.5)); - assert_eq!(Ordering::Equal, (-1.0_f64).total_cmp(&-1.0)); - assert_eq!(Ordering::Equal, (-1.5_f64).total_cmp(&-1.5)); - assert_eq!(Ordering::Equal, (-0.5_f64).total_cmp(&-0.5)); - assert_eq!(Ordering::Equal, (-f64::MIN_POSITIVE).total_cmp(&-f64::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, (-max_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Equal, (-min_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Equal, (-0.0_f64).total_cmp(&-0.0)); - assert_eq!(Ordering::Equal, 0.0_f64.total_cmp(&0.0)); - assert_eq!(Ordering::Equal, min_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Equal, max_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Equal, f64::MIN_POSITIVE.total_cmp(&f64::MIN_POSITIVE)); - assert_eq!(Ordering::Equal, 0.5_f64.total_cmp(&0.5)); - assert_eq!(Ordering::Equal, 1.0_f64.total_cmp(&1.0)); - assert_eq!(Ordering::Equal, 1.5_f64.total_cmp(&1.5)); - assert_eq!(Ordering::Equal, 2.5_f64.total_cmp(&2.5)); - assert_eq!(Ordering::Equal, f64::MAX.total_cmp(&f64::MAX)); - assert_eq!(Ordering::Equal, f64::INFINITY.total_cmp(&f64::INFINITY)); - assert_eq!(Ordering::Equal, s_nan().total_cmp(&s_nan())); - assert_eq!(Ordering::Equal, q_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f64::INFINITY)); - assert_eq!(Ordering::Less, (-f64::INFINITY).total_cmp(&-f64::MAX)); - assert_eq!(Ordering::Less, (-f64::MAX).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-2.5_f64).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-1.5_f64).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-1.0_f64).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-0.5_f64).total_cmp(&-f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-f64::MIN_POSITIVE).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-max_subnorm()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-min_subnorm()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-0.0_f64).total_cmp(&0.0)); - assert_eq!(Ordering::Less, 0.0_f64.total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, min_subnorm().total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, max_subnorm().total_cmp(&f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, f64::MIN_POSITIVE.total_cmp(&0.5)); - assert_eq!(Ordering::Less, 0.5_f64.total_cmp(&1.0)); - assert_eq!(Ordering::Less, 1.0_f64.total_cmp(&1.5)); - assert_eq!(Ordering::Less, 1.5_f64.total_cmp(&2.5)); - assert_eq!(Ordering::Less, 2.5_f64.total_cmp(&f64::MAX)); - assert_eq!(Ordering::Less, f64::MAX.total_cmp(&f64::INFINITY)); - assert_eq!(Ordering::Less, f64::INFINITY.total_cmp(&s_nan())); - assert_eq!(Ordering::Less, s_nan().total_cmp(&q_nan())); - - assert_eq!(Ordering::Greater, (-s_nan()).total_cmp(&-q_nan())); - assert_eq!(Ordering::Greater, (-f64::INFINITY).total_cmp(&-s_nan())); - assert_eq!(Ordering::Greater, (-f64::MAX).total_cmp(&-f64::INFINITY)); - assert_eq!(Ordering::Greater, (-2.5_f64).total_cmp(&-f64::MAX)); - assert_eq!(Ordering::Greater, (-1.5_f64).total_cmp(&-2.5)); - assert_eq!(Ordering::Greater, (-1.0_f64).total_cmp(&-1.5)); - assert_eq!(Ordering::Greater, (-0.5_f64).total_cmp(&-1.0)); - assert_eq!(Ordering::Greater, (-f64::MIN_POSITIVE).total_cmp(&-0.5)); - assert_eq!(Ordering::Greater, (-max_subnorm()).total_cmp(&-f64::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, (-min_subnorm()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Greater, (-0.0_f64).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Greater, 0.0_f64.total_cmp(&-0.0)); - assert_eq!(Ordering::Greater, min_subnorm().total_cmp(&0.0)); - assert_eq!(Ordering::Greater, max_subnorm().total_cmp(&min_subnorm())); - assert_eq!(Ordering::Greater, f64::MIN_POSITIVE.total_cmp(&max_subnorm())); - assert_eq!(Ordering::Greater, 0.5_f64.total_cmp(&f64::MIN_POSITIVE)); - assert_eq!(Ordering::Greater, 1.0_f64.total_cmp(&0.5)); - assert_eq!(Ordering::Greater, 1.5_f64.total_cmp(&1.0)); - assert_eq!(Ordering::Greater, 2.5_f64.total_cmp(&1.5)); - assert_eq!(Ordering::Greater, f64::MAX.total_cmp(&2.5)); - assert_eq!(Ordering::Greater, f64::INFINITY.total_cmp(&f64::MAX)); - assert_eq!(Ordering::Greater, s_nan().total_cmp(&f64::INFINITY)); - assert_eq!(Ordering::Greater, q_nan().total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-s_nan())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f64::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f64::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f64::MAX)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&f64::INFINITY)); - assert_eq!(Ordering::Less, (-q_nan()).total_cmp(&s_nan())); - - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f64::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f64::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-max_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-min_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&-0.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&min_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&max_subnorm())); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::MIN_POSITIVE)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&0.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.0)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&1.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&2.5)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::MAX)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::INFINITY)); - assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); -} - #[test] fn test_algebraic() { let a: f64 = 123.0; diff --git a/library/coretests/tests/floats/mod.rs b/library/coretests/tests/floats/mod.rs index 43431bba6954..2c2a07920d06 100644 --- a/library/coretests/tests/floats/mod.rs +++ b/library/coretests/tests/floats/mod.rs @@ -2,34 +2,80 @@ use std::num::FpCategory as Fp; use std::ops::{Add, Div, Mul, Rem, Sub}; trait TestableFloat { + /// Unsigned int with the same size, for converting to/from bits. + type Int; /// Set the default tolerance for float comparison based on the type. const APPROX: Self; + const ZERO: Self; + const ONE: Self; const MIN_POSITIVE_NORMAL: Self; const MAX_SUBNORMAL: Self; + /// Smallest number + const TINY: Self; + /// Next smallest number + const TINY_UP: Self; + /// Exponent = 0b11...10, Significand 0b1111..10. Min val > 0 + const MAX_DOWN: Self; + /// First pattern over the mantissa + const NAN_MASK1: Self::Int; + /// Second pattern over the mantissa + const NAN_MASK2: Self::Int; } impl TestableFloat for f16 { + type Int = u16; const APPROX: Self = 1e-3; + const ZERO: Self = 0.0; + const ONE: Self = 1.0; const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); + const TINY: Self = Self::from_bits(0x1); + const TINY_UP: Self = Self::from_bits(0x2); + const MAX_DOWN: Self = Self::from_bits(0x7bfe); + const NAN_MASK1: Self::Int = 0x02aa; + const NAN_MASK2: Self::Int = 0x0155; } impl TestableFloat for f32 { + type Int = u32; const APPROX: Self = 1e-6; + const ZERO: Self = 0.0; + const ONE: Self = 1.0; const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); + const TINY: Self = Self::from_bits(0x1); + const TINY_UP: Self = Self::from_bits(0x2); + const MAX_DOWN: Self = Self::from_bits(0x7f7f_fffe); + const NAN_MASK1: Self::Int = 0x002a_aaaa; + const NAN_MASK2: Self::Int = 0x0055_5555; } impl TestableFloat for f64 { + type Int = u64; const APPROX: Self = 1e-6; + const ZERO: Self = 0.0; + const ONE: Self = 1.0; const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); + const TINY: Self = Self::from_bits(0x1); + const TINY_UP: Self = Self::from_bits(0x2); + const MAX_DOWN: Self = Self::from_bits(0x7fef_ffff_ffff_fffe); + const NAN_MASK1: Self::Int = 0x000a_aaaa_aaaa_aaaa; + const NAN_MASK2: Self::Int = 0x0005_5555_5555_5555; } impl TestableFloat for f128 { + type Int = u128; const APPROX: Self = 1e-9; + const ZERO: Self = 0.0; + const ONE: Self = 1.0; const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE; const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down(); + const TINY: Self = Self::from_bits(0x1); + const TINY_UP: Self = Self::from_bits(0x2); + const MAX_DOWN: Self = Self::from_bits(0x7ffefffffffffffffffffffffffffffe); + const NAN_MASK1: Self::Int = 0x0000aaaaaaaaaaaaaaaaaaaaaaaaaaaa; + const NAN_MASK2: Self::Int = 0x00005555555555555555555555555555; } /// Determine the tolerance for values of the argument type. @@ -342,15 +388,14 @@ float_test! { f128: #[cfg(any(miri, target_has_reliable_f128))], }, test { - let zero: Float = 0.0; - assert_biteq!(0.0, zero); - assert!(!zero.is_infinite()); - assert!(zero.is_finite()); - assert!(zero.is_sign_positive()); - assert!(!zero.is_sign_negative()); - assert!(!zero.is_nan()); - assert!(!zero.is_normal()); - assert!(matches!(zero.classify(), Fp::Zero)); + assert_biteq!(0.0, Float::ZERO); + assert!(!Float::ZERO.is_infinite()); + assert!(Float::ZERO.is_finite()); + assert!(Float::ZERO.is_sign_positive()); + assert!(!Float::ZERO.is_sign_negative()); + assert!(!Float::ZERO.is_nan()); + assert!(!Float::ZERO.is_normal()); + assert!(matches!(Float::ZERO.classify(), Fp::Zero)); } } @@ -381,15 +426,14 @@ float_test! { f128: #[cfg(any(miri, target_has_reliable_f128))], }, test { - let one: Float = 1.0; - assert_biteq!(1.0, one); - assert!(!one.is_infinite()); - assert!(one.is_finite()); - assert!(one.is_sign_positive()); - assert!(!one.is_sign_negative()); - assert!(!one.is_nan()); - assert!(one.is_normal()); - assert!(matches!(one.classify(), Fp::Normal)); + assert_biteq!(1.0, Float::ONE); + assert!(!Float::ONE.is_infinite()); + assert!(Float::ONE.is_finite()); + assert!(Float::ONE.is_sign_positive()); + assert!(!Float::ONE.is_sign_negative()); + assert!(!Float::ONE.is_nan()); + assert!(Float::ONE.is_normal()); + assert!(matches!(Float::ONE.classify(), Fp::Normal)); } } @@ -403,11 +447,10 @@ float_test! { let nan: Float = Float::NAN; let inf: Float = Float::INFINITY; let neg_inf: Float = Float::NEG_INFINITY; - let zero: Float = 0.0; let pos: Float = 5.3; let neg: Float = -10.732; assert!(nan.is_nan()); - assert!(!zero.is_nan()); + assert!(!Float::ZERO.is_nan()); assert!(!pos.is_nan()); assert!(!neg.is_nan()); assert!(!inf.is_nan()); @@ -425,13 +468,12 @@ float_test! { let nan: Float = Float::NAN; let inf: Float = Float::INFINITY; let neg_inf: Float = Float::NEG_INFINITY; - let zero: Float = 0.0; let pos: Float = 42.8; let neg: Float = -109.2; assert!(!nan.is_infinite()); assert!(inf.is_infinite()); assert!(neg_inf.is_infinite()); - assert!(!zero.is_infinite()); + assert!(!Float::ZERO.is_infinite()); assert!(!pos.is_infinite()); assert!(!neg.is_infinite()); } @@ -447,13 +489,12 @@ float_test! { let nan: Float = Float::NAN; let inf: Float = Float::INFINITY; let neg_inf: Float = Float::NEG_INFINITY; - let zero: Float = 0.0; let pos: Float = 42.8; let neg: Float = -109.2; assert!(!nan.is_finite()); assert!(!inf.is_finite()); assert!(!neg_inf.is_finite()); - assert!(zero.is_finite()); + assert!(Float::ZERO.is_finite()); assert!(pos.is_finite()); assert!(neg.is_finite()); } @@ -469,15 +510,13 @@ float_test! { let nan: Float = Float::NAN; let inf: Float = Float::INFINITY; let neg_inf: Float = Float::NEG_INFINITY; - let zero: Float = 0.0; let neg_zero: Float = -0.0; - let one : Float = 1.0; assert!(!nan.is_normal()); assert!(!inf.is_normal()); assert!(!neg_inf.is_normal()); - assert!(!zero.is_normal()); + assert!(!Float::ZERO.is_normal()); assert!(!neg_zero.is_normal()); - assert!(one.is_normal()); + assert!(Float::ONE.is_normal()); assert!(Float::MIN_POSITIVE_NORMAL.is_normal()); assert!(!Float::MAX_SUBNORMAL.is_normal()); } @@ -492,15 +531,13 @@ float_test! { let nan: Float = Float::NAN; let inf: Float = Float::INFINITY; let neg_inf: Float = Float::NEG_INFINITY; - let zero: Float = 0.0; let neg_zero: Float = -0.0; - let one: Float = 1.0; assert!(matches!(nan.classify(), Fp::Nan)); assert!(matches!(inf.classify(), Fp::Infinite)); assert!(matches!(neg_inf.classify(), Fp::Infinite)); - assert!(matches!(zero.classify(), Fp::Zero)); + assert!(matches!(Float::ZERO.classify(), Fp::Zero)); assert!(matches!(neg_zero.classify(), Fp::Zero)); - assert!(matches!(one.classify(), Fp::Normal)); + assert!(matches!(Float::ONE.classify(), Fp::Normal)); assert!(matches!(Float::MIN_POSITIVE_NORMAL.classify(), Fp::Normal)); assert!(matches!(Float::MAX_SUBNORMAL.classify(), Fp::Subnormal)); } @@ -720,10 +757,14 @@ float_test! { f128: #[cfg(any(miri, target_has_reliable_f128_math))], }, test { - assert_biteq!((-1.0 as Float).abs(), 1.0); - assert_biteq!((1.0 as Float).abs(), 1.0); - assert_biteq!(Float::NEG_INFINITY.abs(), Float::INFINITY); assert_biteq!(Float::INFINITY.abs(), Float::INFINITY); + assert_biteq!(Float::ONE.abs(), Float::ONE); + assert_biteq!(Float::ZERO.abs(), Float::ZERO); + assert_biteq!((-Float::ZERO).abs(), Float::ZERO); + assert_biteq!((-Float::ONE).abs(), Float::ONE); + assert_biteq!(Float::NEG_INFINITY.abs(), Float::INFINITY); + assert_biteq!((Float::ONE / Float::NEG_INFINITY).abs(), Float::ZERO); + assert!(Float::NAN.abs().is_nan()); } } @@ -951,3 +992,351 @@ float_test! { assert!(Float::NEG_INFINITY.fract().is_nan()); } } + +float_test! { + name: signum, + attrs: { + f16: #[cfg(any(miri, target_has_reliable_f16_math))], + f128: #[cfg(any(miri, target_has_reliable_f128_math))], + }, + test { + assert_biteq!(Float::INFINITY.signum(), Float::ONE); + assert_biteq!(Float::ONE.signum(), Float::ONE); + assert_biteq!(Float::ZERO.signum(), Float::ONE); + assert_biteq!((-Float::ZERO).signum(), -Float::ONE); + assert_biteq!((-Float::ONE).signum(), -Float::ONE); + assert_biteq!(Float::NEG_INFINITY.signum(), -Float::ONE); + assert_biteq!((Float::ONE / Float::NEG_INFINITY).signum(), -Float::ONE); + assert!(Float::NAN.signum().is_nan()); + } +} + +float_test! { + name: is_sign_positive, + attrs: { + f16: #[cfg(any(miri, target_has_reliable_f16))], + f128: #[cfg(any(miri, target_has_reliable_f128))], + }, + test { + assert!(Float::INFINITY.is_sign_positive()); + assert!(Float::ONE.is_sign_positive()); + assert!(Float::ZERO.is_sign_positive()); + assert!(!(-Float::ZERO).is_sign_positive()); + assert!(!(-Float::ONE).is_sign_positive()); + assert!(!Float::NEG_INFINITY.is_sign_positive()); + assert!(!(Float::ONE / Float::NEG_INFINITY).is_sign_positive()); + assert!(Float::NAN.is_sign_positive()); + assert!(!(-Float::NAN).is_sign_positive()); + } +} + +float_test! { + name: is_sign_negative, + attrs: { + f16: #[cfg(any(miri, target_has_reliable_f16))], + f128: #[cfg(any(miri, target_has_reliable_f128))], + }, + test { + assert!(!Float::INFINITY.is_sign_negative()); + assert!(!Float::ONE.is_sign_negative()); + assert!(!Float::ZERO.is_sign_negative()); + assert!((-Float::ZERO).is_sign_negative()); + assert!((-Float::ONE).is_sign_negative()); + assert!(Float::NEG_INFINITY.is_sign_negative()); + assert!((Float::ONE / Float::NEG_INFINITY).is_sign_negative()); + assert!(!Float::NAN.is_sign_negative()); + assert!((-Float::NAN).is_sign_negative()); + } +} + +float_test! { + name: next_up, + attrs: { + f16: #[cfg(any(miri, target_has_reliable_f16))], + f128: #[cfg(any(miri, target_has_reliable_f128))], + }, + test { + assert_biteq!(Float::NEG_INFINITY.next_up(), Float::MIN); + assert_biteq!(Float::MIN.next_up(), -Float::MAX_DOWN); + assert_biteq!((-Float::ONE - Float::EPSILON).next_up(), -Float::ONE); + assert_biteq!((-Float::MIN_POSITIVE_NORMAL).next_up(), -Float::MAX_SUBNORMAL); + assert_biteq!((-Float::TINY_UP).next_up(), -Float::TINY); + assert_biteq!((-Float::TINY).next_up(), -Float::ZERO); + assert_biteq!((-Float::ZERO).next_up(), Float::TINY); + assert_biteq!(Float::ZERO.next_up(), Float::TINY); + assert_biteq!(Float::TINY.next_up(), Float::TINY_UP); + assert_biteq!(Float::MAX_SUBNORMAL.next_up(), Float::MIN_POSITIVE_NORMAL); + assert_biteq!(Float::ONE.next_up(), 1.0 + Float::EPSILON); + assert_biteq!(Float::MAX.next_up(), Float::INFINITY); + assert_biteq!(Float::INFINITY.next_up(), Float::INFINITY); + + // Check that NaNs roundtrip. + let nan0 = Float::NAN; + let nan1 = Float::from_bits(Float::NAN.to_bits() ^ Float::NAN_MASK1); + let nan2 = Float::from_bits(Float::NAN.to_bits() ^ Float::NAN_MASK2); + assert_biteq!(nan0.next_up(), nan0); + assert_biteq!(nan1.next_up(), nan1); + assert_biteq!(nan2.next_up(), nan2); + } +} + +float_test! { + name: next_down, + attrs: { + f16: #[cfg(any(miri, target_has_reliable_f16))], + f128: #[cfg(any(miri, target_has_reliable_f128))], + }, + test { + assert_biteq!(Float::NEG_INFINITY.next_down(), Float::NEG_INFINITY); + assert_biteq!(Float::MIN.next_down(), Float::NEG_INFINITY); + assert_biteq!((-Float::MAX_DOWN).next_down(), Float::MIN); + assert_biteq!((-Float::ONE).next_down(), -1.0 - Float::EPSILON); + assert_biteq!((-Float::MAX_SUBNORMAL).next_down(), -Float::MIN_POSITIVE_NORMAL); + assert_biteq!((-Float::TINY).next_down(), -Float::TINY_UP); + assert_biteq!((-Float::ZERO).next_down(), -Float::TINY); + assert_biteq!((Float::ZERO).next_down(), -Float::TINY); + assert_biteq!(Float::TINY.next_down(), Float::ZERO); + assert_biteq!(Float::TINY_UP.next_down(), Float::TINY); + assert_biteq!(Float::MIN_POSITIVE_NORMAL.next_down(), Float::MAX_SUBNORMAL); + assert_biteq!((1.0 + Float::EPSILON).next_down(), Float::ONE); + assert_biteq!(Float::MAX.next_down(), Float::MAX_DOWN); + assert_biteq!(Float::INFINITY.next_down(), Float::MAX); + + // Check that NaNs roundtrip. + let nan0 = Float::NAN; + let nan1 = Float::from_bits(Float::NAN.to_bits() ^ Float::NAN_MASK1); + let nan2 = Float::from_bits(Float::NAN.to_bits() ^ Float::NAN_MASK2); + assert_biteq!(nan0.next_down(), nan0); + assert_biteq!(nan1.next_down(), nan1); + assert_biteq!(nan2.next_down(), nan2); + } +} + +// FIXME(f16_f128,miri): many of these have to be disabled since miri does not yet support +// the intrinsics. + +float_test! { + name: sqrt_domain, + attrs: { + const: #[cfg(false)], + f16: #[cfg(all(not(miri), target_has_reliable_f16_math))], + f128: #[cfg(all(not(miri), target_has_reliable_f128_math))], + }, + test { + assert!(Float::NAN.sqrt().is_nan()); + assert!(Float::NEG_INFINITY.sqrt().is_nan()); + assert!((-Float::ONE).sqrt().is_nan()); + assert_biteq!((-Float::ZERO).sqrt(), -Float::ZERO); + assert_biteq!(Float::ZERO.sqrt(), Float::ZERO); + assert_biteq!(Float::ONE.sqrt(), Float::ONE); + assert_biteq!(Float::INFINITY.sqrt(), Float::INFINITY); + } +} + +float_test! { + name: clamp_min_greater_than_max, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(any(miri, target_has_reliable_f16))], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(any(miri, target_has_reliable_f128))], + }, + test { + let _ = Float::ONE.clamp(3.0, 1.0); + } +} + +float_test! { + name: clamp_min_is_nan, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(any(miri, target_has_reliable_f16))], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(any(miri, target_has_reliable_f128))], + }, + test { + let _ = Float::ONE.clamp(Float::NAN, 1.0); + } +} + +float_test! { + name: clamp_max_is_nan, + attrs: { + const: #[cfg(false)], + f16: #[should_panic, cfg(any(miri, target_has_reliable_f16))], + f32: #[should_panic], + f64: #[should_panic], + f128: #[should_panic, cfg(any(miri, target_has_reliable_f128))], + }, + test { + let _ = Float::ONE.clamp(3.0, Float::NAN); + } +} + +float_test! { + name: total_cmp, + attrs: { + const: #[cfg(false)], + f16: #[cfg(all(not(miri), target_has_reliable_f16_math))], + f128: #[cfg(all(not(miri), target_has_reliable_f128_math))], + }, + test { + use core::cmp::Ordering; + + fn quiet_bit_mask() -> ::Int { + 1 << (Float::MANTISSA_DIGITS - 2) + } + + fn q_nan() -> Float { + Float::from_bits(Float::NAN.to_bits() | quiet_bit_mask()) + } + + assert_eq!(Ordering::Equal, Float::total_cmp(&-q_nan(), &-q_nan())); + assert_eq!(Ordering::Equal, Float::total_cmp(&-Float::INFINITY, &-Float::INFINITY)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-Float::MAX, &-Float::MAX)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-2.5, &-2.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-1.0, &-1.0)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-1.5, &-1.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-0.5, &-0.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-Float::MIN_POSITIVE, &-Float::MIN_POSITIVE)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-Float::MAX_SUBNORMAL, &-Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-Float::TINY, &-Float::TINY)); + assert_eq!(Ordering::Equal, Float::total_cmp(&-0.0, &-0.0)); + assert_eq!(Ordering::Equal, Float::total_cmp(&0.0, &0.0)); + assert_eq!(Ordering::Equal, Float::total_cmp(&Float::TINY, &Float::TINY)); + assert_eq!(Ordering::Equal, Float::total_cmp(&Float::MAX_SUBNORMAL, &Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Equal, Float::total_cmp(&Float::MIN_POSITIVE, &Float::MIN_POSITIVE)); + assert_eq!(Ordering::Equal, Float::total_cmp(&0.5, &0.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&1.0, &1.0)); + assert_eq!(Ordering::Equal, Float::total_cmp(&1.5, &1.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&2.5, &2.5)); + assert_eq!(Ordering::Equal, Float::total_cmp(&Float::MAX, &Float::MAX)); + assert_eq!(Ordering::Equal, Float::total_cmp(&Float::INFINITY, &Float::INFINITY)); + assert_eq!(Ordering::Equal, Float::total_cmp(&q_nan(), &q_nan())); + + assert_eq!(Ordering::Less, Float::total_cmp(&-Float::INFINITY, &-Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&-Float::MAX, &-2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-2.5, &-1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-1.5, &-1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-1.0, &-0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-0.5, &-Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&-Float::MIN_POSITIVE, &-Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&-Float::MAX_SUBNORMAL, &-Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-Float::TINY, &-0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-0.0, &0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&0.0, &Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&Float::TINY, &Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&Float::MAX_SUBNORMAL, &Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&Float::MIN_POSITIVE, &0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&0.5, &1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&1.0, &1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&1.5, &2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&2.5, &Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&Float::MAX, &Float::INFINITY)); + + assert_eq!(Ordering::Greater, Float::total_cmp(&-Float::MAX, &-Float::INFINITY)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-2.5, &-Float::MAX)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-1.5, &-2.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-1.0, &-1.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-0.5, &-1.0)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-Float::MIN_POSITIVE, &-0.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-Float::MAX_SUBNORMAL, &-Float::MIN_POSITIVE)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-Float::TINY, &-Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Greater, Float::total_cmp(&-0.0, &-Float::TINY)); + assert_eq!(Ordering::Greater, Float::total_cmp(&0.0, &-0.0)); + assert_eq!(Ordering::Greater, Float::total_cmp(&Float::TINY, &0.0)); + assert_eq!(Ordering::Greater, Float::total_cmp(&Float::MAX_SUBNORMAL, &Float::TINY)); + assert_eq!(Ordering::Greater, Float::total_cmp(&Float::MIN_POSITIVE, &Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Greater, Float::total_cmp(&0.5, &Float::MIN_POSITIVE)); + assert_eq!(Ordering::Greater, Float::total_cmp(&1.0, &0.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&1.5, &1.0)); + assert_eq!(Ordering::Greater, Float::total_cmp(&2.5, &1.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&Float::MAX, &2.5)); + assert_eq!(Ordering::Greater, Float::total_cmp(&Float::INFINITY, &Float::MAX)); + + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-Float::INFINITY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &Float::INFINITY)); + + } +} + +// FIXME(f16): Tests involving sNaN are disabled because without optimizations, `total_cmp` is +// getting incorrectly lowered to code that includes a `extend`/`trunc` round trip, which quiets +// sNaNs. See: https://github.com/llvm/llvm-project/issues/104915 + +float_test! { + name: total_cmp_s_nan, + attrs: { + const: #[cfg(false)], + f16: #[cfg(false)], + f128: #[cfg(all(not(miri), target_has_reliable_f128_math))], + }, + test { + use core::cmp::Ordering; + + fn quiet_bit_mask() -> ::Int { + 1 << (Float::MANTISSA_DIGITS - 2) + } + + fn q_nan() -> Float { + Float::from_bits(Float::NAN.to_bits() | quiet_bit_mask()) + } + + fn s_nan() -> Float { + Float::from_bits((Float::NAN.to_bits() & !quiet_bit_mask()) + 42) + } + assert_eq!(Ordering::Equal, Float::total_cmp(&-s_nan(), &-s_nan())); + assert_eq!(Ordering::Equal, Float::total_cmp(&s_nan(), &s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::INFINITY)); + assert_eq!(Ordering::Less, Float::total_cmp(&Float::INFINITY, &s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&s_nan(), &q_nan())); + assert_eq!(Ordering::Greater, Float::total_cmp(&-s_nan(), &-q_nan())); + assert_eq!(Ordering::Greater, Float::total_cmp(&-Float::INFINITY, &-s_nan())); + assert_eq!(Ordering::Greater, Float::total_cmp(&s_nan(), &Float::INFINITY)); + assert_eq!(Ordering::Greater, Float::total_cmp(&q_nan(), &s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &-s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&-q_nan(), &s_nan())); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::INFINITY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &-0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &0.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &Float::TINY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &Float::MAX_SUBNORMAL)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &Float::MIN_POSITIVE)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &0.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &1.0)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &1.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &2.5)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &Float::MAX)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &Float::INFINITY)); + assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &s_nan())); + } +} diff --git a/library/coretests/tests/io/borrowed_buf.rs b/library/coretests/tests/io/borrowed_buf.rs index 4074148436cf..aaa98d26ff8b 100644 --- a/library/coretests/tests/io/borrowed_buf.rs +++ b/library/coretests/tests/io/borrowed_buf.rs @@ -66,7 +66,7 @@ fn clear() { #[test] fn set_init() { - let buf: &mut [_] = &mut [MaybeUninit::uninit(); 16]; + let buf: &mut [_] = &mut [MaybeUninit::zeroed(); 16]; let mut rbuf: BorrowedBuf<'_> = buf.into(); unsafe { @@ -134,7 +134,7 @@ fn reborrow_written() { #[test] fn cursor_set_init() { - let buf: &mut [_] = &mut [MaybeUninit::uninit(); 16]; + let buf: &mut [_] = &mut [MaybeUninit::zeroed(); 16]; let mut rbuf: BorrowedBuf<'_> = buf.into(); unsafe { diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 029a7b00ad36..d2281b1df2ff 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -18,9 +18,13 @@ #![feature(const_deref)] #![feature(const_destruct)] #![feature(const_eval_select)] +#![feature(const_from)] #![feature(const_ops)] +#![feature(const_option_ops)] #![feature(const_ref_cell)] +#![feature(const_result_trait_fn)] #![feature(const_trait_impl)] +#![feature(control_flow_ok)] #![feature(core_float_math)] #![feature(core_intrinsics)] #![feature(core_intrinsics_fallbacks)] @@ -32,7 +36,6 @@ #![feature(drop_guard)] #![feature(duration_constants)] #![feature(duration_constructors)] -#![feature(duration_constructors_lite)] #![feature(error_generic_member_access)] #![feature(exact_div)] #![feature(exact_size_is_empty)] @@ -51,14 +54,13 @@ #![feature(generic_assert_internals)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] +#![feature(int_lowest_highest_one)] #![feature(int_roundings)] #![feature(ip)] -#![feature(ip_from)] #![feature(is_ascii_octdigit)] #![feature(isolate_most_least_significant_one)] #![feature(iter_advance_by)] #![feature(iter_array_chunks)] -#![feature(iter_chain)] #![feature(iter_collect_into)] #![feature(iter_intersperse)] #![feature(iter_is_partitioned)] @@ -82,6 +84,7 @@ #![feature(pointer_is_aligned_to)] #![feature(portable_simd)] #![feature(ptr_metadata)] +#![feature(result_option_map_or_default)] #![feature(slice_from_ptr_range)] #![feature(slice_internals)] #![feature(slice_partition_dedup)] diff --git a/library/coretests/tests/nonzero.rs b/library/coretests/tests/nonzero.rs index 00232c9b7061..69e4ed9c36b3 100644 --- a/library/coretests/tests/nonzero.rs +++ b/library/coretests/tests/nonzero.rs @@ -214,13 +214,11 @@ fn nonzero_const() { const ONE: Option> = NonZero::new(1); assert!(ONE.is_some()); - /* FIXME(#110395) const FROM_NONZERO_U8: u8 = u8::from(NONZERO_U8); assert_eq!(FROM_NONZERO_U8, 5); const NONZERO_CONVERT: NonZero = NonZero::::from(NONZERO_U8); assert_eq!(NONZERO_CONVERT.get(), 5); - */ } #[test] @@ -321,7 +319,7 @@ fn nonzero_trailing_zeros() { } #[test] -fn test_nonzero_isolate_most_significant_one() { +fn test_nonzero_isolate_highest_one() { // Signed most significant one macro_rules! nonzero_int_impl { ($($T:ty),+) => { @@ -335,8 +333,8 @@ fn test_nonzero_isolate_most_significant_one() { let mut i = 0; while i < <$T>::BITS { assert_eq!( - NonZero::<$T>::new(BITS >> i).unwrap().isolate_most_significant_one(), - NonZero::<$T>::new(MOST_SIG_ONE >> i).unwrap().isolate_most_significant_one() + NonZero::<$T>::new(BITS >> i).unwrap().isolate_highest_one(), + NonZero::<$T>::new(MOST_SIG_ONE >> i).unwrap().isolate_highest_one() ); i += 1; } @@ -356,8 +354,8 @@ fn test_nonzero_isolate_most_significant_one() { let mut i = 0; while i < <$T>::BITS { assert_eq!( - NonZero::<$T>::new(BITS >> i).unwrap().isolate_most_significant_one(), - NonZero::<$T>::new(MOST_SIG_ONE >> i).unwrap().isolate_most_significant_one(), + NonZero::<$T>::new(BITS >> i).unwrap().isolate_highest_one(), + NonZero::<$T>::new(MOST_SIG_ONE >> i).unwrap().isolate_highest_one(), ); i += 1; } @@ -371,7 +369,7 @@ fn test_nonzero_isolate_most_significant_one() { } #[test] -fn test_nonzero_isolate_least_significant_one() { +fn test_nonzero_isolate_lowest_one() { // Signed least significant one macro_rules! nonzero_int_impl { ($($T:ty),+) => { @@ -385,8 +383,8 @@ fn test_nonzero_isolate_least_significant_one() { let mut i = 0; while i < <$T>::BITS { assert_eq!( - NonZero::<$T>::new(BITS << i).unwrap().isolate_least_significant_one(), - NonZero::<$T>::new(LEAST_SIG_ONE << i).unwrap().isolate_least_significant_one() + NonZero::<$T>::new(BITS << i).unwrap().isolate_lowest_one(), + NonZero::<$T>::new(LEAST_SIG_ONE << i).unwrap().isolate_lowest_one() ); i += 1; } @@ -406,8 +404,8 @@ fn test_nonzero_isolate_least_significant_one() { let mut i = 0; while i < <$T>::BITS { assert_eq!( - NonZero::<$T>::new(BITS << i).unwrap().isolate_least_significant_one(), - NonZero::<$T>::new(LEAST_SIG_ONE << i).unwrap().isolate_least_significant_one(), + NonZero::<$T>::new(BITS << i).unwrap().isolate_lowest_one(), + NonZero::<$T>::new(LEAST_SIG_ONE << i).unwrap().isolate_lowest_one(), ); i += 1; } @@ -464,3 +462,111 @@ fn test_nonzero_fmt() { assert_eq!(i, nz); } + +#[test] +fn test_nonzero_highest_one() { + macro_rules! nonzero_int_impl { + ($($T:ty),+) => { + $( + { + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(1 << i).unwrap().highest_one(), i); + if i > <$T>::BITS { + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().highest_one(), + <$T>::BITS - i - 2, + ); + } + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(-1 << i).unwrap().highest_one(), + <$T>::BITS - 1, + ); + } + } + )+ + }; + } + + macro_rules! nonzero_uint_impl { + ($($T:ty),+) => { + $( + { + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(1 << i).unwrap().highest_one(), i); + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().highest_one(), + <$T>::BITS - i - 1, + ); + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX << i).unwrap().highest_one(), + <$T>::BITS - 1, + ); + } + } + )+ + }; + } + + nonzero_int_impl!(i8, i16, i32, i64, i128, isize); + nonzero_uint_impl!(u8, u16, u32, u64, u128, usize); +} + +#[test] +fn test_nonzero_lowest_one() { + macro_rules! nonzero_int_impl { + ($($T:ty),+) => { + $( + { + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(1 << i).unwrap().lowest_one(), i); + if i > <$T>::BITS { + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().lowest_one(), + 0, + ); + } + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(-1 << i).unwrap().lowest_one(), + i, + ); + } + } + )+ + }; + } + + macro_rules! nonzero_uint_impl { + ($($T:ty),+) => { + $( + { + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!(NonZero::<$T>::new(1 << i).unwrap().lowest_one(), i); + // Set lowest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX >> i).unwrap().lowest_one(), + 0, + ); + // Set highest bits. + assert_eq!( + NonZero::<$T>::new(<$T>::MAX << i).unwrap().lowest_one(), + i, + ); + } + } + )+ + }; + } + + nonzero_int_impl!(i8, i16, i32, i64, i128, isize); + nonzero_uint_impl!(u8, u16, u32, u64, u128, usize); +} diff --git a/library/coretests/tests/num/const_from.rs b/library/coretests/tests/num/const_from.rs index fa58e7718791..aca18ef39de1 100644 --- a/library/coretests/tests/num/const_from.rs +++ b/library/coretests/tests/num/const_from.rs @@ -1,4 +1,3 @@ -/* FIXME(#110395) #[test] fn from() { use core::convert::TryFrom; @@ -24,4 +23,3 @@ fn from() { const I16_FROM_U16: Result = i16::try_from(1u16); assert_eq!(I16_FROM_U16, Ok(1i16)); } -*/ diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index 41d399c1ad9f..1611a6466f5a 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -194,7 +194,7 @@ macro_rules! int_module { } #[test] - fn test_isolate_most_significant_one() { + fn test_isolate_highest_one() { const BITS: $T = -1; const MOST_SIG_ONE: $T = 1 << (<$T>::BITS - 1); @@ -203,15 +203,15 @@ macro_rules! int_module { let mut i = 0; while i < <$T>::BITS { assert_eq!( - (BITS >> i).isolate_most_significant_one(), - (MOST_SIG_ONE >> i).isolate_most_significant_one() + (BITS >> i).isolate_highest_one(), + (MOST_SIG_ONE >> i).isolate_highest_one() ); i += 1; } } #[test] - fn test_isolate_least_significant_one() { + fn test_isolate_lowest_one() { const BITS: $T = -1; const LEAST_SIG_ONE: $T = 1; @@ -220,13 +220,53 @@ macro_rules! int_module { let mut i = 0; while i < <$T>::BITS { assert_eq!( - (BITS << i).isolate_least_significant_one(), - (LEAST_SIG_ONE << i).isolate_least_significant_one() + (BITS << i).isolate_lowest_one(), + (LEAST_SIG_ONE << i).isolate_lowest_one() ); i += 1; } } + #[test] + fn test_highest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + assert_eq!(ZERO.highest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).highest_one(), Some(i)); + if i != <$T>::BITS - 1 { + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).highest_one(), Some(<$T>::BITS - i - 2)); + } + // Set highest bits. + assert_eq!((MINUS_ONE << i).highest_one(), Some(<$T>::BITS - 1)); + } + } + + #[test] + fn test_lowest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + const MINUS_ONE: $T = -1; + + assert_eq!(ZERO.lowest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).lowest_one(), Some(i)); + if i != <$T>::BITS - 1 { + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).lowest_one(), Some(0)); + } + // Set highest bits. + assert_eq!((MINUS_ONE << i).lowest_one(), Some(i)); + } + } + #[test] fn test_from_str() { fn from_str(t: &str) -> Option { diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 7e02027bdd6a..c7d10ea4d880 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -151,7 +151,7 @@ macro_rules! uint_module { } #[test] - fn test_isolate_most_significant_one() { + fn test_isolate_highest_one() { const BITS: $T = <$T>::MAX; const MOST_SIG_ONE: $T = 1 << (<$T>::BITS - 1); @@ -160,15 +160,15 @@ macro_rules! uint_module { let mut i = 0; while i < <$T>::BITS { assert_eq!( - (BITS >> i).isolate_most_significant_one(), - (MOST_SIG_ONE >> i).isolate_most_significant_one(), + (BITS >> i).isolate_highest_one(), + (MOST_SIG_ONE >> i).isolate_highest_one(), ); i += 1; } } #[test] - fn test_isolate_least_significant_one() { + fn test_isolate_lowest_one() { const BITS: $T = <$T>::MAX; const LEAST_SIG_ONE: $T = 1; @@ -177,13 +177,47 @@ macro_rules! uint_module { let mut i = 0; while i < <$T>::BITS { assert_eq!( - (BITS << i).isolate_least_significant_one(), - (LEAST_SIG_ONE << i).isolate_least_significant_one(), + (BITS << i).isolate_lowest_one(), + (LEAST_SIG_ONE << i).isolate_lowest_one(), ); i += 1; } } + #[test] + fn test_highest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + + assert_eq!(ZERO.highest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).highest_one(), Some(i)); + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).highest_one(), Some(<$T>::BITS - i - 1)); + // Set highest bits. + assert_eq!((<$T>::MAX << i).highest_one(), Some(<$T>::BITS - 1)); + } + } + + #[test] + fn test_lowest_one() { + const ZERO: $T = 0; + const ONE: $T = 1; + + assert_eq!(ZERO.lowest_one(), None); + + for i in 0..<$T>::BITS { + // Set single bit. + assert_eq!((ONE << i).lowest_one(), Some(i)); + // Set lowest bits. + assert_eq!((<$T>::MAX >> i).lowest_one(), Some(0)); + // Set highest bits. + assert_eq!((<$T>::MAX << i).lowest_one(), Some(i)); + } + } + fn from_str(t: &str) -> Option { core::str::FromStr::from_str(t).ok() } diff --git a/library/coretests/tests/ops/control_flow.rs b/library/coretests/tests/ops/control_flow.rs index eacfd63a6c48..1df6599ac4a5 100644 --- a/library/coretests/tests/ops/control_flow.rs +++ b/library/coretests/tests/ops/control_flow.rs @@ -16,3 +16,15 @@ fn control_flow_discriminants_match_result() { discriminant_value(&Result::::Ok(3)), ); } + +#[test] +fn control_flow_break_ok() { + assert_eq!(ControlFlow::::Break('b').break_ok(), Ok('b')); + assert_eq!(ControlFlow::::Continue(3).break_ok(), Err(3)); +} + +#[test] +fn control_flow_continue_ok() { + assert_eq!(ControlFlow::::Break('b').continue_ok(), Err('b')); + assert_eq!(ControlFlow::::Continue(3).continue_ok(), Ok(3)); +} diff --git a/library/coretests/tests/option.rs b/library/coretests/tests/option.rs index 336a79a02cee..fc0f82ad6bb3 100644 --- a/library/coretests/tests/option.rs +++ b/library/coretests/tests/option.rs @@ -87,7 +87,6 @@ fn test_and() { assert_eq!(x.and(Some(2)), None); assert_eq!(x.and(None::), None); - /* FIXME(#110395) const FOO: Option = Some(1); const A: Option = FOO.and(Some(2)); const B: Option = FOO.and(None); @@ -99,7 +98,6 @@ fn test_and() { const D: Option = BAR.and(None); assert_eq!(C, None); assert_eq!(D, None); - */ } #[test] diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs index 197a14423b59..c13fb96a67f9 100644 --- a/library/coretests/tests/ptr.rs +++ b/library/coretests/tests/ptr.rs @@ -936,22 +936,18 @@ fn test_const_swap_ptr() { assert!(*s1.0.ptr == 666); assert!(*s2.0.ptr == 1); - // Swap them back, again as an array. + // Swap them back, byte-for-byte unsafe { ptr::swap_nonoverlapping( - ptr::from_mut(&mut s1).cast::(), - ptr::from_mut(&mut s2).cast::(), - 1, + ptr::from_mut(&mut s1).cast::(), + ptr::from_mut(&mut s2).cast::(), + size_of::(), ); } // Make sure they still work. assert!(*s1.0.ptr == 1); assert!(*s2.0.ptr == 666); - - // This is where we'd swap again using a `u8` type and a `count` of `size_of::()` if it - // were not for the limitation of `swap_nonoverlapping` around pointers crossing multiple - // elements. }; } diff --git a/library/coretests/tests/result.rs b/library/coretests/tests/result.rs index 90ec844bc577..39898d5dbb76 100644 --- a/library/coretests/tests/result.rs +++ b/library/coretests/tests/result.rs @@ -421,3 +421,86 @@ fn result_try_trait_v2_branch() { assert_eq!(Ok::, ()>(one).branch(), Continue(one)); assert_eq!(Err::, ()>(()).branch(), Break(Err(()))); } + +// helper functions for const contexts +const fn eq10(x: u8) -> bool { + x == 10 +} +const fn eq20(e: u8) -> bool { + e == 20 +} +const fn double_u16(x: u8) -> u16 { + x as u16 * 2 +} +const fn to_u16(x: u8) -> u16 { + x as u16 +} +const fn err_to_u16_plus1(e: u8) -> u16 { + e as u16 + 1 +} +const fn inc_u8(x: u8) -> u8 { + x + 1 +} +const fn noop_u8_ref(_x: &u8) {} +const fn add1_result(x: u8) -> Result { + Ok(x + 1) +} +const fn add5_result(e: u8) -> Result { + Ok(e + 5) +} +const fn plus7_u8(e: u8) -> u8 { + e + 7 +} + +#[test] +fn test_const_result() { + const { + let r_ok: Result = Ok(10); + let r_err: Result = Err(20); + assert!(r_ok.is_ok()); + assert!(r_err.is_err()); + + let ok_and = r_ok.is_ok_and(eq10); + let err_and = r_err.is_err_and(eq20); + assert!(ok_and); + assert!(err_and); + + let opt_ok: Option = r_ok.ok(); + let opt_err: Option = r_err.err(); + assert!(opt_ok.unwrap() == 10); + assert!(opt_err.unwrap() == 20); + + let mapped: Result = r_ok.map(double_u16); + let map_or: u16 = r_ok.map_or(0, to_u16); + let map_or_else: u16 = r_err.map_or_else(err_to_u16_plus1, to_u16); + let map_or_default: u8 = r_err.map_or_default(inc_u8); + assert!(mapped.unwrap_or_default() == 20); + assert!(map_or == 10); + assert!(map_or_else == 21); + assert!(map_or_default == 0); + + let _map_err: Result = r_err.map_err(to_u16); + //FIXME: currently can't unwrap const error + // assert!(map_err.unwrap_err() == 20); + + let inspected_ok: Result = r_ok.inspect(noop_u8_ref); + let inspected_err: Result = r_err.inspect_err(noop_u8_ref); + assert!(inspected_ok.is_ok()); + assert!(inspected_err.is_err()); + + let unwrapped_default: u8 = r_err.unwrap_or_default(); + assert!(unwrapped_default == 0); + + let and_then: Result = r_ok.and_then(add1_result); + let or: Result = r_err.or(Ok(5)); + let or_else: Result = r_err.or_else(add5_result); + assert!(and_then.unwrap_or_default() == 11); + assert!(or.unwrap_or_default() == 5); + assert!(or_else.unwrap_or_default() == 25); + + let u_or: u8 = r_err.unwrap_or(7); + let u_or_else: u8 = r_err.unwrap_or_else(plus7_u8); + assert!(u_or == 7); + assert!(u_or_else == 27); + }; +} diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index 992f24cb18f2..110c4e5f3b40 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1492,28 +1492,28 @@ mod slice_index { // note: using 0 specifically ensures that the result of overflowing is 0..0, // so that `get` doesn't simply return None for the wrong reason. bad: data[0 ..= usize::MAX]; - message: "maximum usize"; + message: "out of range"; } in mod rangetoinclusive_overflow { data: [0, 1]; bad: data[..= usize::MAX]; - message: "maximum usize"; + message: "out of range"; } in mod boundpair_overflow_end { data: [0; 1]; bad: data[(Bound::Unbounded, Bound::Included(usize::MAX))]; - message: "maximum usize"; + message: "out of range"; } in mod boundpair_overflow_start { data: [0; 1]; bad: data[(Bound::Excluded(usize::MAX), Bound::Unbounded)]; - message: "maximum usize"; + message: "out of range"; } } // panic_cases! } @@ -2008,7 +2008,7 @@ fn test_copy_within_panics_src_inverted() { bytes.copy_within(2..1, 0); } #[test] -#[should_panic(expected = "attempted to index slice up to maximum usize")] +#[should_panic(expected = "out of range")] fn test_copy_within_panics_src_out_of_bounds() { let mut bytes = *b"Hello, World!"; // an inclusive range ending at usize::MAX would make src_end overflow diff --git a/library/coretests/tests/tuple.rs b/library/coretests/tests/tuple.rs index ea1e281425c8..5d680d104723 100644 --- a/library/coretests/tests/tuple.rs +++ b/library/coretests/tests/tuple.rs @@ -37,7 +37,7 @@ fn test_partial_ord() { assert!(!((1.0f64, 2.0f64) <= (f64::NAN, 3.0))); assert!(!((1.0f64, 2.0f64) > (f64::NAN, 3.0))); assert!(!((1.0f64, 2.0f64) >= (f64::NAN, 3.0))); - assert!(((1.0f64, 2.0f64) < (2.0, f64::NAN))); + assert!((1.0f64, 2.0f64) < (2.0, f64::NAN)); assert!(!((2.0f64, 2.0f64) < (2.0, f64::NAN))); } diff --git a/library/panic_unwind/Cargo.toml b/library/panic_unwind/Cargo.toml index 13d1a7160da8..67fc919c42c2 100644 --- a/library/panic_unwind/Cargo.toml +++ b/library/panic_unwind/Cargo.toml @@ -13,7 +13,6 @@ doc = false [dependencies] alloc = { path = "../alloc" } -cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } core = { path = "../rustc-std-workspace-core", package = "rustc-std-workspace-core" } unwind = { path = "../unwind" } diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index 50bd933aca20..83311f323801 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -15,6 +15,7 @@ #![unstable(feature = "panic_unwind", issue = "32837")] #![doc(issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")] #![feature(cfg_emscripten_wasm_eh)] +#![feature(cfg_select)] #![feature(core_intrinsics)] #![feature(lang_items)] #![feature(panic_unwind)] @@ -33,18 +34,21 @@ use alloc::boxed::Box; use core::any::Any; use core::panic::PanicPayload; -cfg_if::cfg_if! { - if #[cfg(all(target_os = "emscripten", not(emscripten_wasm_eh)))] { +cfg_select! { + all(target_os = "emscripten", not(emscripten_wasm_eh)) => { #[path = "emcc.rs"] mod imp; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { #[path = "hermit.rs"] mod imp; - } else if #[cfg(target_os = "l4re")] { + } + target_os = "l4re" => { // L4Re is unix family but does not yet support unwinding. #[path = "dummy.rs"] mod imp; - } else if #[cfg(any( + } + any( all(target_family = "windows", target_env = "gnu"), target_os = "psp", target_os = "xous", @@ -52,19 +56,22 @@ cfg_if::cfg_if! { all(target_family = "unix", not(any(target_os = "espidf", target_os = "nuttx"))), all(target_vendor = "fortanix", target_env = "sgx"), target_family = "wasm", - ))] { + ) => { #[path = "gcc.rs"] mod imp; - } else if #[cfg(miri)] { + } + miri => { // Use the Miri runtime on Windows as miri doesn't support funclet based unwinding, // only landingpad based unwinding. Also use the Miri runtime on unsupported platforms. #[path = "miri.rs"] mod imp; - } else if #[cfg(all(target_env = "msvc", not(target_arch = "arm")))] { + } + all(target_env = "msvc", not(target_arch = "arm")) => { // LLVM does not support unwinding on 32 bit ARM msvc (thumbv7a-pc-windows-msvc) #[path = "seh.rs"] mod imp; - } else { + } + _ => { // Targets that don't support unwinding. // - os=none ("bare metal" targets) // - os=uefi diff --git a/library/panic_unwind/src/seh.rs b/library/panic_unwind/src/seh.rs index 668e988abff3..a5d67dbb6a9f 100644 --- a/library/panic_unwind/src/seh.rs +++ b/library/panic_unwind/src/seh.rs @@ -289,10 +289,11 @@ macro_rules! define_cleanup { } } } -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86")] { +cfg_select! { + target_arch = "x86" => { define_cleanup!("thiscall" "thiscall-unwind"); - } else { + } + _ => { define_cleanup!("C" "C-unwind"); } } diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 7bc52976500d..eff2562f0319 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["dylib", "rlib"] [dependencies] alloc = { path = "../alloc", public = true } +# std no longer uses cfg-if directly, but the included copy of backtrace does. cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } panic_unwind = { path = "../panic_unwind", optional = true } panic_abort = { path = "../panic_abort" } diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index edbdd0411457..15a7a770d1a8 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -135,6 +135,8 @@ use crate::ops::Index; /// ]); /// ``` /// +/// ## `Entry` API +/// /// `HashMap` implements an [`Entry` API](#method.entry), which allows /// for complex methods of getting, setting, updating and removing keys and /// their values: @@ -167,6 +169,8 @@ use crate::ops::Index; /// player_stats.entry("mana").and_modify(|mana| *mana += 200).or_insert(100); /// ``` /// +/// ## Usage with custom key types +/// /// The easiest way to use `HashMap` with a custom key type is to derive [`Eq`] and [`Hash`]. /// We must also derive [`PartialEq`]. /// diff --git a/library/std/src/collections/mod.rs b/library/std/src/collections/mod.rs index 889ed3c53803..6104a02c739b 100644 --- a/library/std/src/collections/mod.rs +++ b/library/std/src/collections/mod.rs @@ -26,7 +26,7 @@ //! should be considered. Detailed discussions of strengths and weaknesses of //! individual collections can be found on their own documentation pages. //! -//! ### Use a `Vec` when: +//! ### Use a [`Vec`] when: //! * You want to collect items up to be processed or sent elsewhere later, and //! don't care about any properties of the actual values being stored. //! * You want a sequence of elements in a particular order, and will only be @@ -35,25 +35,25 @@ //! * You want a resizable array. //! * You want a heap-allocated array. //! -//! ### Use a `VecDeque` when: +//! ### Use a [`VecDeque`] when: //! * You want a [`Vec`] that supports efficient insertion at both ends of the //! sequence. //! * You want a queue. //! * You want a double-ended queue (deque). //! -//! ### Use a `LinkedList` when: +//! ### Use a [`LinkedList`] when: //! * You want a [`Vec`] or [`VecDeque`] of unknown size, and can't tolerate //! amortization. //! * You want to efficiently split and append lists. //! * You are *absolutely* certain you *really*, *truly*, want a doubly linked //! list. //! -//! ### Use a `HashMap` when: +//! ### Use a [`HashMap`] when: //! * You want to associate arbitrary keys with an arbitrary value. //! * You want a cache. //! * You want a map, with no extra functionality. //! -//! ### Use a `BTreeMap` when: +//! ### Use a [`BTreeMap`] when: //! * You want a map sorted by its keys. //! * You want to be able to get a range of entries on-demand. //! * You're interested in what the smallest or largest key-value pair is. @@ -65,7 +65,7 @@ //! * There is no meaningful value to associate with your keys. //! * You just want a set. //! -//! ### Use a `BinaryHeap` when: +//! ### Use a [`BinaryHeap`] when: //! //! * You want to store a bunch of elements, but only ever want to process the //! "biggest" or "most important" one at any given time. diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 8d7edc732aff..1214490caadf 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -137,7 +137,7 @@ impl OsString { #[stable(feature = "rust1", since = "1.0.0")] #[must_use] #[inline] - #[rustc_const_unstable(feature = "const_pathbuf_osstring_new", issue = "141520")] + #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "CURRENT_RUSTC_VERSION")] pub const fn new() -> OsString { OsString { inner: Buf::from_string(String::new()) } } diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 72ad7c244eeb..1ed4f2f9f0c6 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -814,7 +814,7 @@ impl File { /// /// If this file handle/descriptor, or a clone of it, already holds a lock, the exact behavior /// is unspecified and platform dependent, including the possibility that it will deadlock. - /// However, if this method returns `Ok(true)`, then it has acquired an exclusive lock. + /// However, if this method returns `Ok(())`, then it has acquired an exclusive lock. /// /// If the file is not open for writing, it is unspecified whether this function returns an error. /// @@ -879,7 +879,7 @@ impl File { /// /// If this file handle, or a clone of it, already holds a lock, the exact behavior is /// unspecified and platform dependent, including the possibility that it will deadlock. - /// However, if this method returns `Ok(true)`, then it has acquired a shared lock. + /// However, if this method returns `Ok(())`, then it has acquired a shared lock. /// /// The lock will be released when this file (along with any other file descriptors/handles /// duplicated or inherited from it) is closed, or if the [`unlock`] method is called. @@ -1111,6 +1111,11 @@ impl File { /// `futimes` on macOS before 10.13) and the `SetFileTime` function on Windows. Note that this /// [may change in the future][changes]. /// + /// On most platforms, including UNIX and Windows platforms, this function can also change the + /// timestamps of a directory. To get a `File` representing a directory in order to call + /// `set_times`, open the directory with `File::open` without attempting to obtain write + /// permission. + /// /// [changes]: io#platform-specific-behavior /// /// # Errors @@ -1128,7 +1133,7 @@ impl File { /// use std::fs::{self, File, FileTimes}; /// /// let src = fs::metadata("src")?; - /// let dest = File::options().write(true).open("dest")?; + /// let dest = File::open("dest")?; /// let times = FileTimes::new() /// .set_accessed(src.accessed()?) /// .set_modified(src.modified()?); @@ -3114,7 +3119,7 @@ pub fn read_dir>(path: P) -> io::Result { /// On UNIX-like systems, this function will update the permission bits /// of the file pointed to by the symlink. /// -/// Note that this behavior can lead to privalage escalation vulnerabilities, +/// Note that this behavior can lead to privilege escalation vulnerabilities, /// where the ability to create a symlink in one directory allows you to /// cause the permissions of another file or directory to be modified. /// @@ -3151,6 +3156,25 @@ pub fn set_permissions>(path: P, perm: Permissions) -> io::Result fs_imp::set_permissions(path.as_ref(), perm.0) } +/// Set the permissions of a file, unless it is a symlink. +/// +/// Note that the non-final path elements are allowed to be symlinks. +/// +/// # Platform-specific behavior +/// +/// Currently unimplemented on Windows. +/// +/// On Unix platforms, this results in a [`FilesystemLoop`] error if the last element is a symlink. +/// +/// This behavior may change in the future. +/// +/// [`FilesystemLoop`]: crate::io::ErrorKind::FilesystemLoop +#[doc(alias = "chmod", alias = "SetFileAttributes")] +#[unstable(feature = "set_permissions_nofollow", issue = "141607")] +pub fn set_permissions_nofollow>(path: P, perm: Permissions) -> io::Result<()> { + fs_imp::set_permissions_nofollow(path.as_ref(), perm) +} + impl DirBuilder { /// Creates a new set of options with default mode/security settings for all /// platforms and also non-recursive. diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/std/src/io/buffered/bufreader/buffer.rs index 574288e579e0..9b600cd55758 100644 --- a/library/std/src/io/buffered/bufreader/buffer.rs +++ b/library/std/src/io/buffered/bufreader/buffer.rs @@ -122,7 +122,7 @@ impl Buffer { /// Remove bytes that have already been read from the buffer. pub fn backshift(&mut self) { - self.buf.copy_within(self.pos.., 0); + self.buf.copy_within(self.pos..self.filled, 0); self.filled -= self.pos; self.pos = 0; } diff --git a/library/std/src/io/copy.rs b/library/std/src/io/copy.rs index 15e962924ac7..d060ad528973 100644 --- a/library/std/src/io/copy.rs +++ b/library/std/src/io/copy.rs @@ -63,10 +63,11 @@ where R: Read, W: Write, { - cfg_if::cfg_if! { - if #[cfg(any(target_os = "linux", target_os = "android"))] { + cfg_select! { + any(target_os = "linux", target_os = "android") => { crate::sys::kernel_copy::copy_spec(reader, writer) - } else { + } + _ => { generic_copy(reader, writer) } } diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 562fdbf4ff76..dcfa189823f8 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -18,7 +18,7 @@ use crate::{error, fmt, result, sys}; /// This type is broadly used across [`std::io`] for any operation which may /// produce an error. /// -/// This typedef is generally used to avoid writing out [`io::Error`] directly and +/// This type alias is generally used to avoid writing out [`io::Error`] directly and /// is otherwise a direct mapping to [`Result`]. /// /// While usual Rust style is to import types directly, aliases of [`Result`] diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index d351ee5e739d..ff0e29e04c25 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -2461,7 +2461,7 @@ pub trait BufRead: Read { /// delimiter or EOF is found. /// /// If successful, this function will return the total number of bytes read, - /// including the delimiter byte. + /// including the delimiter byte if found. /// /// This is useful for efficiently skipping data such as NUL-terminated strings /// in binary file formats without buffering. @@ -2489,7 +2489,7 @@ pub trait BufRead: Read { /// ``` /// use std::io::{self, BufRead}; /// - /// let mut cursor = io::Cursor::new(b"Ferris\0Likes long walks on the beach\0Crustacean\0"); + /// let mut cursor = io::Cursor::new(b"Ferris\0Likes long walks on the beach\0Crustacean\0!"); /// /// // read name /// let mut name = Vec::new(); @@ -2509,6 +2509,11 @@ pub trait BufRead: Read { /// .expect("reading from cursor won't fail"); /// assert_eq!(num_bytes, 11); /// assert_eq!(animal, b"Crustacean\0"); + /// + /// // reach EOF + /// let num_bytes = cursor.skip_until(b'\0') + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 1); /// ``` #[stable(feature = "bufread_skip_until", since = "1.83.0")] fn skip_until(&mut self, byte: u8) -> Result { diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 77301d7228ea..ab417b6c72f9 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -281,9 +281,11 @@ #![feature(cfg_target_thread_local)] #![feature(cfi_encoding)] #![feature(char_max_len)] +#![feature(const_trait_impl)] #![feature(core_float_math)] #![feature(decl_macro)] #![feature(deprecated_suggestion)] +#![feature(derive_const)] #![feature(doc_cfg)] #![feature(doc_cfg_hide)] #![feature(doc_masked)] @@ -294,6 +296,8 @@ #![feature(f128)] #![feature(ffi_const)] #![feature(formatting_options)] +#![feature(hash_map_internals)] +#![feature(hash_map_macro)] #![feature(if_let_guard)] #![feature(intra_doc_pointers)] #![feature(iter_advance_by)] @@ -319,15 +323,20 @@ #![feature(try_blocks)] #![feature(try_trait_v2)] #![feature(type_alias_impl_trait)] -#![feature(unsigned_signed_diff)] // tidy-alphabetical-end // // Library features (core): // tidy-alphabetical-start #![feature(bstr)] #![feature(bstr_internals)] +#![feature(cast_maybe_uninit)] +#![feature(cfg_select)] #![feature(char_internals)] #![feature(clone_to_uninit)] +#![feature(const_cmp)] +#![feature(const_ops)] +#![feature(const_option_ops)] +#![feature(const_try)] #![feature(core_intrinsics)] #![feature(core_io_borrowed_buf)] #![feature(drop_guard)] @@ -420,11 +429,15 @@ // #![default_lib_allocator] +// The Rust prelude +// The compiler expects the prelude definition to be defined before it's use statement. +pub mod prelude; + // Explicitly import the prelude. The compiler uses this same unstable attribute // to import the prelude implicitly when building crates that depend on std. #[prelude_import] #[allow(unused)] -use prelude::rust_2021::*; +use prelude::rust_2024::*; // Access to Bencher, etc. #[cfg(test)] @@ -475,9 +488,6 @@ mod macros; #[macro_use] pub mod rt; -// The Rust prelude -pub mod prelude; - #[stable(feature = "rust1", since = "1.0.0")] pub use core::any; #[stable(feature = "core_array", since = "1.35.0")] @@ -728,6 +738,14 @@ pub use core::{ unreachable, write, writeln, }; +// Re-export unstable derive macro defined through core. +#[unstable(feature = "derive_from", issue = "144889")] +/// Unstable module containing the unstable `From` derive macro. +pub mod from { + #[unstable(feature = "derive_from", issue = "144889")] + pub use core::from::From; +} + // Include a number of private modules that exist solely to provide // the rustdoc documentation for primitive types. Using `include!` // because rustdoc only looks for these modules at the crate level. diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 25e2b7ea1370..254570ae9c83 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -379,3 +379,77 @@ macro_rules! dbg { ($($crate::dbg!($val)),+,) }; } + +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable(hash_map_internals)] +#[unstable(feature = "hash_map_internals", issue = "none")] +macro_rules! repetition_utils { + (@count $($tokens:tt),*) => {{ + [$($crate::repetition_utils!(@replace $tokens => ())),*].len() + }}; + + (@replace $x:tt => $y:tt) => { $y } +} + +/// Creates a [`HashMap`] containing the arguments. +/// +/// `hash_map!` allows specifying the entries that make +/// up the [`HashMap`] where the key and value are separated by a `=>`. +/// +/// The entries are separated by commas with a trailing comma being allowed. +/// +/// It is semantically equivalent to using repeated [`HashMap::insert`] +/// on a newly created hashmap. +/// +/// `hash_map!` will attempt to avoid repeated reallocations by +/// using [`HashMap::with_capacity`]. +/// +/// # Examples +/// +/// ```rust +/// #![feature(hash_map_macro)] +/// +/// let map = hash_map! { +/// "key" => "value", +/// "key1" => "value1" +/// }; +/// +/// assert_eq!(map.get("key"), Some(&"value")); +/// assert_eq!(map.get("key1"), Some(&"value1")); +/// assert!(map.get("brrrrrrooooommm").is_none()); +/// ``` +/// +/// And with a trailing comma +/// +///```rust +/// #![feature(hash_map_macro)] +/// +/// let map = hash_map! { +/// "key" => "value", // notice the , +/// }; +/// +/// assert_eq!(map.get("key"), Some(&"value")); +/// ``` +/// +/// The key and value are moved into the HashMap. +/// +/// [`HashMap`]: crate::collections::HashMap +/// [`HashMap::insert`]: crate::collections::HashMap::insert +/// [`HashMap::with_capacity`]: crate::collections::HashMap::with_capacity +#[macro_export] +#[allow_internal_unstable(hash_map_internals)] +#[unstable(feature = "hash_map_macro", issue = "144032")] +macro_rules! hash_map { + () => {{ + $crate::collections::HashMap::new() + }}; + + ( $( $key:expr => $value:expr ),* $(,)? ) => {{ + let mut map = $crate::collections::HashMap::with_capacity( + const { $crate::repetition_utils!(@count $($key),*) } + ); + $( map.insert($key, $value); )* + map + }} +} diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index 2bff73add33d..5dee68ad909e 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -44,7 +44,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn floor(self) -> f32 { core::f32::math::floor(self) @@ -67,7 +67,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn ceil(self) -> f32 { core::f32::math::ceil(self) @@ -96,7 +96,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn round(self) -> f32 { core::f32::math::round(self) @@ -123,7 +123,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "round_ties_even", since = "1.77.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn round_ties_even(self) -> f32 { core::f32::math::round_ties_even(self) @@ -149,7 +149,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn trunc(self) -> f32 { core::f32::math::trunc(self) @@ -173,7 +173,7 @@ impl f32 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn fract(self) -> f32 { core::f32::math::fract(self) diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index b71e319f4074..3ec80f68bdb2 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -44,7 +44,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn floor(self) -> f64 { core::f64::math::floor(self) @@ -67,7 +67,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn ceil(self) -> f64 { core::f64::math::ceil(self) @@ -96,7 +96,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn round(self) -> f64 { core::f64::math::round(self) @@ -123,7 +123,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "round_ties_even", since = "1.77.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn round_ties_even(self) -> f64 { core::f64::math::round_ties_even(self) @@ -149,7 +149,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn trunc(self) -> f64 { core::f64::math::trunc(self) @@ -173,7 +173,7 @@ impl f64 { #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_float_round_methods", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_float_round_methods", since = "1.90.0")] #[inline] pub const fn fract(self) -> f64 { core::f64::math::fract(self) diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index 035768a6fab7..768fa77a5f8c 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -1,14 +1,16 @@ -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( target_os = "linux", target_os = "android", target_os = "hurd", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "solaris", target_os = "illumos", target_os = "haiku", target_os = "nto", - target_os = "cygwin"))] { + target_os = "cygwin", + ) => { use libc::MSG_NOSIGNAL; - } else { + } + _ => { const MSG_NOSIGNAL: core::ffi::c_int = 0x0; } } diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 76e63a69e45d..09429af06e38 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -4,8 +4,6 @@ #![stable(feature = "rust1", since = "1.0.0")] -use cfg_if::cfg_if; - use crate::ffi::OsStr; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use crate::path::Path; @@ -13,17 +11,19 @@ use crate::sealed::Sealed; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::{io, process, sys}; -cfg_if! { - if #[cfg(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon", target_os = "vita"))] { +cfg_select! { + any(target_os = "vxworks", target_os = "espidf", target_os = "horizon", target_os = "vita") => { type UserId = u16; type GroupId = u16; - } else if #[cfg(target_os = "nto")] { + } + target_os = "nto" => { // Both IDs are signed, see `sys/target_nto.h` of the QNX Neutrino SDP. // Only positive values should be used, see e.g. // https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/s/setuid.html type UserId = i32; type GroupId = i32; - } else { + } + _ => { type UserId = u32; type GroupId = u32; } diff --git a/library/std/src/panic.rs b/library/std/src/panic.rs index 234fb284a590..5e8d2f8e78ec 100644 --- a/library/std/src/panic.rs +++ b/library/std/src/panic.rs @@ -60,6 +60,7 @@ impl<'a> PanicHookInfo<'a> { /// Returns the payload associated with the panic. /// /// This will commonly, but not always, be a `&'static str` or [`String`]. + /// If you only care about such payloads, use [`payload_as_str`] instead. /// /// A invocation of the `panic!()` macro in Rust 2021 or later will always result in a /// panic payload of type `&'static str` or `String`. @@ -69,6 +70,7 @@ impl<'a> PanicHookInfo<'a> { /// can result in a panic payload other than a `&'static str` or `String`. /// /// [`String`]: ../../std/string/struct.String.html + /// [`payload_as_str`]: PanicHookInfo::payload_as_str /// /// # Examples /// @@ -108,8 +110,6 @@ impl<'a> PanicHookInfo<'a> { /// # Example /// /// ```should_panic - /// #![feature(panic_payload_as_str)] - /// /// std::panic::set_hook(Box::new(|panic_info| { /// if let Some(s) = panic_info.payload_as_str() { /// println!("panic occurred: {s:?}"); @@ -122,7 +122,7 @@ impl<'a> PanicHookInfo<'a> { /// ``` #[must_use] #[inline] - #[unstable(feature = "panic_payload_as_str", issue = "125175")] + #[stable(feature = "panic_payload_as_str", since = "CURRENT_RUSTC_VERSION")] pub fn payload_as_str(&self) -> Option<&str> { if let Some(s) = self.payload.downcast_ref::<&str>() { Some(s) @@ -388,7 +388,7 @@ pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { /// ``` #[stable(feature = "resume_unwind", since = "1.9.0")] pub fn resume_unwind(payload: Box) -> ! { - panicking::rust_panic_without_hook(payload) + panicking::resume_unwind(payload) } /// Makes all future panics abort directly without running the panic hook or unwinding. diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 7873049d20bf..87a3fc80dfab 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -269,6 +269,7 @@ fn default_hook(info: &PanicHookInfo<'_>) { thread::with_current_name(|name| { let name = name.unwrap_or(""); + let tid = thread::current_os_id(); // Try to write the panic message to a buffer first to prevent other concurrent outputs // interleaving with it. @@ -277,7 +278,7 @@ fn default_hook(info: &PanicHookInfo<'_>) { let write_msg = |dst: &mut dyn crate::io::Write| { // We add a newline to ensure the panic message appears at the start of a line. - writeln!(dst, "\nthread '{name}' panicked at {location}:\n{msg}") + writeln!(dst, "\nthread '{name}' ({tid}) panicked at {location}:\n{msg}") }; if write_msg(&mut cursor).is_ok() { @@ -627,7 +628,7 @@ pub fn panicking() -> bool { /// Entry point of panics from the core crate (`panic_impl` lang item). #[cfg(not(any(test, doctest)))] #[panic_handler] -pub fn begin_panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { +pub fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { struct FormatStringPayload<'a> { inner: &'a core::panic::PanicMessage<'a>, string: Option, @@ -696,14 +697,14 @@ pub fn begin_panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { let msg = info.message(); crate::sys::backtrace::__rust_end_short_backtrace(move || { if let Some(s) = msg.as_str() { - rust_panic_with_hook( + panic_with_hook( &mut StaticStrPayload(s), loc, info.can_unwind(), info.force_no_backtrace(), ); } else { - rust_panic_with_hook( + panic_with_hook( &mut FormatStringPayload { inner: &msg, string: None }, loc, info.can_unwind(), @@ -767,7 +768,7 @@ pub const fn begin_panic(msg: M) -> ! { let loc = Location::caller(); crate::sys::backtrace::__rust_end_short_backtrace(move || { - rust_panic_with_hook( + panic_with_hook( &mut Payload { inner: Some(msg) }, loc, /* can_unwind */ true, @@ -792,7 +793,7 @@ fn payload_as_str(payload: &dyn Any) -> &str { /// panics, panic hooks, and finally dispatching to the panic runtime to either /// abort or unwind. #[optimize(size)] -fn rust_panic_with_hook( +fn panic_with_hook( payload: &mut dyn PanicPayload, location: &Location<'_>, can_unwind: bool, @@ -861,7 +862,7 @@ fn rust_panic_with_hook( /// This is the entry point for `resume_unwind`. /// It just forwards the payload to the panic runtime. #[cfg_attr(feature = "panic_immediate_abort", inline)] -pub fn rust_panic_without_hook(payload: Box) -> ! { +pub fn resume_unwind(payload: Box) -> ! { panic_count::increase(false); struct RewrapBox(Box); @@ -885,8 +886,8 @@ pub fn rust_panic_without_hook(payload: Box) -> ! { rust_panic(&mut RewrapBox(payload)) } -/// An unmangled function (through `rustc_std_internal_symbol`) on which to slap -/// yer breakpoints. +/// A function with a fixed suffix (through `rustc_std_internal_symbol`) +/// on which to slap yer breakpoints. #[inline(never)] #[cfg_attr(not(test), rustc_std_internal_symbol)] #[cfg(not(feature = "panic_immediate_abort"))] diff --git a/library/std/src/path.rs b/library/std/src/path.rs index d9c34d4fa045..3899fbf86db8 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -1191,7 +1191,7 @@ impl PathBuf { #[stable(feature = "rust1", since = "1.0.0")] #[must_use] #[inline] - #[rustc_const_unstable(feature = "const_pathbuf_osstring_new", issue = "141520")] + #[rustc_const_stable(feature = "const_pathbuf_osstring_new", since = "CURRENT_RUSTC_VERSION")] pub const fn new() -> PathBuf { PathBuf { inner: OsString::new() } } @@ -2105,6 +2105,38 @@ impl PartialEq for PathBuf { } } +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for PathBuf { + #[inline] + fn eq(&self, other: &str) -> bool { + &*self == other + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for str { + #[inline] + fn eq(&self, other: &PathBuf) -> bool { + other == self + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for PathBuf { + #[inline] + fn eq(&self, other: &String) -> bool { + **self == **other + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for String { + #[inline] + fn eq(&self, other: &PathBuf) -> bool { + other == self + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Hash for PathBuf { fn hash(&self, h: &mut H) { @@ -2678,11 +2710,12 @@ impl Path { /// # Examples /// /// ``` - /// # #![feature(path_file_prefix)] /// use std::path::Path; /// /// assert_eq!("foo", Path::new("foo.rs").file_prefix().unwrap()); /// assert_eq!("foo", Path::new("foo.tar.gz").file_prefix().unwrap()); + /// assert_eq!(".config", Path::new(".config").file_prefix().unwrap()); + /// assert_eq!(".config", Path::new(".config.toml").file_prefix().unwrap()); /// ``` /// /// # See Also @@ -2691,7 +2724,7 @@ impl Path { /// /// [`Path::file_stem`]: Path::file_stem /// - #[unstable(feature = "path_file_prefix", issue = "86319")] + #[stable(feature = "path_file_prefix", since = "CURRENT_RUSTC_VERSION")] #[must_use] pub fn file_prefix(&self) -> Option<&OsStr> { self.file_name().map(split_file_at_dot).and_then(|(before, _after)| Some(before)) @@ -3259,8 +3292,8 @@ impl Path { /// /// # Examples /// - #[cfg_attr(unix, doc = "```no_run")] - #[cfg_attr(not(unix), doc = "```ignore")] + /// ```rust,no_run + /// # #[cfg(unix)] { /// use std::path::Path; /// use std::os::unix::fs::symlink; /// @@ -3268,6 +3301,7 @@ impl Path { /// symlink("/origin_does_not_exist/", link_path).unwrap(); /// assert_eq!(link_path.is_symlink(), true); /// assert_eq!(link_path.exists(), false); + /// # } /// ``` /// /// # See Also @@ -3364,6 +3398,39 @@ impl PartialEq for Path { } } +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for Path { + #[inline] + fn eq(&self, other: &str) -> bool { + let other: &OsStr = other.as_ref(); + self == other + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for str { + #[inline] + fn eq(&self, other: &Path) -> bool { + other == self + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for Path { + #[inline] + fn eq(&self, other: &String) -> bool { + self == &*other + } +} + +#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")] +impl cmp::PartialEq for String { + #[inline] + fn eq(&self, other: &Path) -> bool { + other == self + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Hash for Path { fn hash(&self, h: &mut H) { diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs index eba849d16dac..a40e29a772a9 100644 --- a/library/std/src/sync/lazy_lock.rs +++ b/library/std/src/sync/lazy_lock.rs @@ -25,6 +25,22 @@ union Data { /// /// [`LazyCell`]: crate::cell::LazyCell /// +/// # Poisoning +/// +/// If the initialization closure passed to [`LazyLock::new`] panics, the lock will be poisoned. +/// Once the lock is poisoned, any threads that attempt to access this lock (via a dereference +/// or via an explicit call to [`force()`]) will panic. +/// +/// This concept is similar to that of poisoning in the [`std::sync::poison`] module. A key +/// difference, however, is that poisoning in `LazyLock` is _unrecoverable_. All future accesses of +/// the lock from other threads will panic, whereas a type in [`std::sync::poison`] like +/// [`std::sync::poison::Mutex`] allows recovery via [`PoisonError::into_inner()`]. +/// +/// [`force()`]: LazyLock::force +/// [`std::sync::poison`]: crate::sync::poison +/// [`std::sync::poison::Mutex`]: crate::sync::poison::Mutex +/// [`PoisonError::into_inner()`]: crate::sync::poison::PoisonError::into_inner +/// /// # Examples /// /// Initialize static variables with `LazyLock`. @@ -102,6 +118,10 @@ impl T> LazyLock { /// /// Returns `Ok(value)` if `Lazy` is initialized and `Err(f)` otherwise. /// + /// # Panics + /// + /// Panics if the lock is poisoned. + /// /// # Examples /// /// ``` @@ -136,6 +156,15 @@ impl T> LazyLock { /// Forces the evaluation of this lazy value and returns a mutable reference to /// the result. /// + /// # Panics + /// + /// If the initialization closure panics (the one that is passed to the [`new()`] method), the + /// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future + /// accesses of the lock (via [`force()`] or a dereference) to panic. + /// + /// [`new()`]: LazyLock::new + /// [`force()`]: LazyLock::force + /// /// # Examples /// /// ``` @@ -193,6 +222,15 @@ impl T> LazyLock { /// This method will block the calling thread if another initialization /// routine is currently running. /// + /// # Panics + /// + /// If the initialization closure panics (the one that is passed to the [`new()`] method), the + /// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future + /// accesses of the lock (via [`force()`] or a dereference) to panic. + /// + /// [`new()`]: LazyLock::new + /// [`force()`]: LazyLock::force + /// /// # Examples /// /// ``` @@ -227,7 +265,8 @@ impl T> LazyLock { } impl LazyLock { - /// Returns a mutable reference to the value if initialized, or `None` if not. + /// Returns a mutable reference to the value if initialized. Otherwise (if uninitialized or + /// poisoned), returns `None`. /// /// # Examples /// @@ -256,7 +295,8 @@ impl LazyLock { } } - /// Returns a reference to the value if initialized, or `None` if not. + /// Returns a reference to the value if initialized. Otherwise (if uninitialized or poisoned), + /// returns `None`. /// /// # Examples /// @@ -307,6 +347,14 @@ impl T> Deref for LazyLock { /// This method will block the calling thread if another initialization /// routine is currently running. /// + /// # Panics + /// + /// If the initialization closure panics (the one that is passed to the [`new()`] method), the + /// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future + /// accesses of the lock (via [`force()`] or a dereference) to panic. + /// + /// [`new()`]: LazyLock::new + /// [`force()`]: LazyLock::force #[inline] fn deref(&self) -> &T { LazyLock::force(self) @@ -315,6 +363,14 @@ impl T> Deref for LazyLock { #[stable(feature = "lazy_deref_mut", since = "1.89.0")] impl T> DerefMut for LazyLock { + /// # Panics + /// + /// If the initialization closure panics (the one that is passed to the [`new()`] method), the + /// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future + /// accesses of the lock (via [`force()`] or a dereference) to panic. + /// + /// [`new()`]: LazyLock::new + /// [`force()`]: LazyLock::force #[inline] fn deref_mut(&mut self) -> &mut T { LazyLock::force_mut(self) diff --git a/library/std/src/sync/mpsc.rs b/library/std/src/sync/mpsc.rs index 41d1dd3ce674..03d7fddc2fae 100644 --- a/library/std/src/sync/mpsc.rs +++ b/library/std/src/sync/mpsc.rs @@ -697,14 +697,14 @@ impl SyncSender { /// let sync_sender2 = sync_sender.clone(); /// /// // First thread owns sync_sender - /// thread::spawn(move || { + /// let handle1 = thread::spawn(move || { /// sync_sender.send(1).unwrap(); /// sync_sender.send(2).unwrap(); /// // Thread blocked /// }); /// /// // Second thread owns sync_sender2 - /// thread::spawn(move || { + /// let handle2 = thread::spawn(move || { /// // This will return an error and send /// // no message if the buffer is full /// let _ = sync_sender2.try_send(3); @@ -722,6 +722,10 @@ impl SyncSender { /// Ok(msg) => println!("message {msg} received"), /// Err(_) => println!("the third message was never sent"), /// } + /// + /// // Wait for threads to complete + /// handle1.join().unwrap(); + /// handle2.join().unwrap(); /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn try_send(&self, t: T) -> Result<(), TrySendError> { diff --git a/library/std/src/sync/nonpoison/mutex.rs b/library/std/src/sync/nonpoison/mutex.rs index b6861c78f001..fd1e671d7a3d 100644 --- a/library/std/src/sync/nonpoison/mutex.rs +++ b/library/std/src/sync/nonpoison/mutex.rs @@ -100,7 +100,7 @@ pub struct MutexGuard<'a, T: ?Sized + 'a> { lock: &'a Mutex, } -/// A [`MutexGuard`] is not `Send` to maximize platform portablity. +/// A [`MutexGuard`] is not `Send` to maximize platform portability. /// /// On platforms that use POSIX threads (commonly referred to as pthreads) there is a requirement to /// release mutex locks on the same thread they were acquired. diff --git a/library/std/src/sync/once_lock.rs b/library/std/src/sync/once_lock.rs index a5c3a6c46a43..b224044cbe09 100644 --- a/library/std/src/sync/once_lock.rs +++ b/library/std/src/sync/once_lock.rs @@ -16,6 +16,8 @@ use crate::sync::Once; /// A `OnceLock` can be thought of as a safe abstraction over uninitialized data that becomes /// initialized once written. /// +/// Unlike [`Mutex`](crate::sync::Mutex), `OnceLock` is never poisoned on panic. +/// /// [`OnceCell`]: crate::cell::OnceCell /// [`LazyLock`]: crate::sync::LazyLock /// [`LazyLock::new(|| ...)`]: crate::sync::LazyLock::new diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs index b901a5701a48..31889dcc10fa 100644 --- a/library/std/src/sync/poison.rs +++ b/library/std/src/sync/poison.rs @@ -2,15 +2,16 @@ //! //! # Poisoning //! -//! All synchronization objects in this module implement a strategy called "poisoning" -//! where if a thread panics while holding the exclusive access granted by the primitive, -//! the state of the primitive is set to "poisoned". -//! This information is then propagated to all other threads +//! All synchronization objects in this module implement a strategy called +//! "poisoning" where a primitive becomes poisoned if it recognizes that some +//! thread has panicked while holding the exclusive access granted by the +//! primitive. This information is then propagated to all other threads //! to signify that the data protected by this primitive is likely tainted //! (some invariant is not being upheld). //! -//! The specifics of how this "poisoned" state affects other threads -//! depend on the primitive. See [#Overview] below. +//! The specifics of how this "poisoned" state affects other threads and whether +//! the panics are recognized reliably or on a best-effort basis depend on the +//! primitive. See [Overview](#overview) below. //! //! For the alternative implementations that do not employ poisoning, //! see [`std::sync::nonpoison`]. @@ -36,14 +37,15 @@ //! - [`Mutex`]: Mutual Exclusion mechanism, which ensures that at //! most one thread at a time is able to access some data. //! -//! [`Mutex::lock()`] returns a [`LockResult`], -//! providing a way to deal with the poisoned state. -//! See [`Mutex`'s documentation](Mutex#poisoning) for more. +//! Panicking while holding the lock typically poisons the mutex, but it is +//! not guaranteed to detect this condition in all circumstances. +//! [`Mutex::lock()`] returns a [`LockResult`], providing a way to deal with +//! the poisoned state. See [`Mutex`'s documentation](Mutex#poisoning) for more. //! //! - [`Once`]: A thread-safe way to run a piece of code only once. //! Mostly useful for implementing one-time global initialization. //! -//! [`Once`] is poisoned if the piece of code passed to +//! [`Once`] is reliably poisoned if the piece of code passed to //! [`Once::call_once()`] or [`Once::call_once_force()`] panics. //! When in poisoned state, subsequent calls to [`Once::call_once()`] will panic too. //! [`Once::call_once_force()`] can be used to clear the poisoned state. @@ -53,7 +55,7 @@ //! writer at a time. In some cases, this can be more efficient than //! a mutex. //! -//! This implementation, like [`Mutex`], will become poisoned on a panic. +//! This implementation, like [`Mutex`], usually becomes poisoned on a panic. //! Note, however, that an `RwLock` may only be poisoned if a panic occurs //! while it is locked exclusively (write mode). If a panic occurs in any reader, //! then the lock will not be poisoned. diff --git a/library/std/src/sync/poison/mutex.rs b/library/std/src/sync/poison/mutex.rs index 64744f18c746..720c212c65cf 100644 --- a/library/std/src/sync/poison/mutex.rs +++ b/library/std/src/sync/poison/mutex.rs @@ -18,20 +18,69 @@ use crate::sys::sync as sys; /// # Poisoning /// /// The mutexes in this module implement a strategy called "poisoning" where a -/// mutex is considered poisoned whenever a thread panics while holding the -/// mutex. Once a mutex is poisoned, all other threads are unable to access the -/// data by default as it is likely tainted (some invariant is not being -/// upheld). +/// mutex becomes poisoned if it recognizes that the thread holding it has +/// panicked. /// -/// For a mutex, this means that the [`lock`] and [`try_lock`] methods return a +/// Once a mutex is poisoned, all other threads are unable to access the data by +/// default as it is likely tainted (some invariant is not being upheld). For a +/// mutex, this means that the [`lock`] and [`try_lock`] methods return a /// [`Result`] which indicates whether a mutex has been poisoned or not. Most /// usage of a mutex will simply [`unwrap()`] these results, propagating panics /// among threads to ensure that a possibly invalid invariant is not witnessed. /// -/// A poisoned mutex, however, does not prevent all access to the underlying -/// data. The [`PoisonError`] type has an [`into_inner`] method which will return -/// the guard that would have otherwise been returned on a successful lock. This -/// allows access to the data, despite the lock being poisoned. +/// Poisoning is only advisory: the [`PoisonError`] type has an [`into_inner`] +/// method which will return the guard that would have otherwise been returned +/// on a successful lock. This allows access to the data, despite the lock being +/// poisoned. +/// +/// In addition, the panic detection is not ideal, so even unpoisoned mutexes +/// need to be handled with care, since certain panics may have been skipped. +/// Here is a non-exhaustive list of situations where this might occur: +/// +/// - If a mutex is locked while a panic is underway, e.g. within a [`Drop`] +/// implementation or a [panic hook], panicking for the second time while the +/// lock is held will leave the mutex unpoisoned. Note that while double panic +/// usually aborts the program, [`catch_unwind`] can prevent this. +/// +/// - Locking and unlocking the mutex across different panic contexts, e.g. by +/// storing the guard to a [`Cell`] within [`Drop::drop`] and accessing it +/// outside, or vice versa, can affect poisoning status in an unexpected way. +/// +/// - Foreign exceptions do not currently trigger poisoning even in absence of +/// other panics. +/// +/// While this rarely happens in realistic code, `unsafe` code cannot rely on +/// poisoning for soundness, since the behavior of poisoning can depend on +/// outside context. Here's an example of **incorrect** use of poisoning: +/// +/// ```rust +/// use std::sync::Mutex; +/// +/// struct MutexBox { +/// data: Mutex<*mut T>, +/// } +/// +/// impl MutexBox { +/// pub fn new(value: T) -> Self { +/// Self { +/// data: Mutex::new(Box::into_raw(Box::new(value))), +/// } +/// } +/// +/// pub fn replace_with(&self, f: impl FnOnce(T) -> T) { +/// let ptr = self.data.lock().expect("poisoned"); +/// // While `f` is running, the data is moved out of `*ptr`. If `f` +/// // panics, `*ptr` keeps pointing at a dropped value. The intention +/// // is that this will poison the mutex, so the following calls to +/// // `replace_with` will panic without reading `*ptr`. But since +/// // poisoning is not guaranteed to occur if this is run from a panic +/// // hook, this can lead to use-after-free. +/// unsafe { +/// (*ptr).write(f((*ptr).read())); +/// } +/// } +/// } +/// ``` /// /// [`new`]: Self::new /// [`lock`]: Self::lock @@ -39,6 +88,9 @@ use crate::sys::sync as sys; /// [`unwrap()`]: Result::unwrap /// [`PoisonError`]: super::PoisonError /// [`into_inner`]: super::PoisonError::into_inner +/// [panic hook]: crate::panic::set_hook +/// [`catch_unwind`]: crate::panic::catch_unwind +/// [`Cell`]: crate::cell::Cell /// /// # Examples /// @@ -227,7 +279,7 @@ pub struct MutexGuard<'a, T: ?Sized + 'a> { poison: poison::Guard, } -/// A [`MutexGuard`] is not `Send` to maximize platform portablity. +/// A [`MutexGuard`] is not `Send` to maximize platform portability. /// /// On platforms that use POSIX threads (commonly referred to as pthreads) there is a requirement to /// release mutex locks on the same thread they were acquired. diff --git a/library/std/src/sync/poison/once.rs b/library/std/src/sync/poison/once.rs index 103e51954079..faf2913c5473 100644 --- a/library/std/src/sync/poison/once.rs +++ b/library/std/src/sync/poison/once.rs @@ -136,7 +136,8 @@ impl Once { /// it will *poison* this [`Once`] instance, causing all future invocations of /// `call_once` to also panic. /// - /// This is similar to [poisoning with mutexes][poison]. + /// This is similar to [poisoning with mutexes][poison], but this mechanism + /// is guaranteed to never skip panics within `f`. /// /// [poison]: struct.Mutex.html#poisoning #[inline] @@ -293,6 +294,9 @@ impl Once { /// Blocks the current thread until initialization has completed, ignoring /// poisoning. + /// + /// If this [`Once`] has been poisoned, this function blocks until it + /// becomes completed, unlike [`Once::wait()`], which panics in this case. #[stable(feature = "once_wait", since = "1.86.0")] pub fn wait_force(&self) { if !self.inner.is_completed() { diff --git a/library/std/src/sync/poison/rwlock.rs b/library/std/src/sync/poison/rwlock.rs index 934a173425a8..2c92602bc878 100644 --- a/library/std/src/sync/poison/rwlock.rs +++ b/library/std/src/sync/poison/rwlock.rs @@ -46,10 +46,12 @@ use crate::sys::sync as sys; /// /// # Poisoning /// -/// An `RwLock`, like [`Mutex`], will become poisoned on a panic. Note, however, -/// that an `RwLock` may only be poisoned if a panic occurs while it is locked -/// exclusively (write mode). If a panic occurs in any reader, then the lock -/// will not be poisoned. +/// An `RwLock`, like [`Mutex`], will [usually] become poisoned on a panic. Note, +/// however, that an `RwLock` may only be poisoned if a panic occurs while it is +/// locked exclusively (write mode). If a panic occurs in any reader, then the +/// lock will not be poisoned. +/// +/// [usually]: super::Mutex#poisoning /// /// # Examples /// diff --git a/library/std/src/sync/reentrant_lock.rs b/library/std/src/sync/reentrant_lock.rs index 727252f03a24..4140718560c6 100644 --- a/library/std/src/sync/reentrant_lock.rs +++ b/library/std/src/sync/reentrant_lock.rs @@ -1,5 +1,3 @@ -use cfg_if::cfg_if; - use crate::cell::UnsafeCell; use crate::fmt; use crate::ops::Deref; @@ -87,8 +85,8 @@ pub struct ReentrantLock { data: T, } -cfg_if!( - if #[cfg(target_has_atomic = "64")] { +cfg_select!( + target_has_atomic = "64" => { use crate::sync::atomic::{Atomic, AtomicU64, Ordering::Relaxed}; struct Tid(Atomic); @@ -110,7 +108,8 @@ cfg_if!( self.0.store(value, Relaxed); } } - } else { + } + _ => { /// Returns the address of a TLS variable. This is guaranteed to /// be unique across all currently alive threads. fn tls_addr() -> usize { diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs index f3af1f7f5991..6d4b09494a3f 100644 --- a/library/std/src/sys/alloc/mod.rs +++ b/library/std/src/sys/alloc/mod.rs @@ -68,29 +68,37 @@ unsafe fn realloc_fallback( } } -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( target_family = "unix", target_os = "wasi", target_os = "teeos", target_os = "trusty", - ))] { + ) => { mod unix; - } else if #[cfg(target_os = "windows")] { + } + target_os = "windows" => { mod windows; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; - } else if #[cfg(target_os = "solid_asp3")] { + } + target_os = "solid_asp3" => { mod solid; - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod uefi; - } else if #[cfg(target_family = "wasm")] { + } + target_family = "wasm" => { mod wasm; - } else if #[cfg(target_os = "xous")] { + } + target_os = "xous" => { mod xous; - } else if #[cfg(target_os = "zkvm")] { + } + target_os = "zkvm" => { mod zkvm; } } diff --git a/library/std/src/sys/alloc/unix.rs b/library/std/src/sys/alloc/unix.rs index a7ac4117ec90..3d369b08abc7 100644 --- a/library/std/src/sys/alloc/unix.rs +++ b/library/std/src/sys/alloc/unix.rs @@ -58,18 +58,16 @@ unsafe impl GlobalAlloc for System { } } -cfg_if::cfg_if! { +cfg_select! { // We use posix_memalign wherever possible, but some targets have very incomplete POSIX coverage // so we need a fallback for those. - if #[cfg(any( - target_os = "horizon", - target_os = "vita", - ))] { + any(target_os = "horizon", target_os = "vita") => { #[inline] unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 { unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 } } - } else { + } + _ => { #[inline] #[cfg_attr(target_os = "vxworks", allow(unused_unsafe))] unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 { diff --git a/library/std/src/sys/anonymous_pipe/mod.rs b/library/std/src/sys/anonymous_pipe/mod.rs index aa14c8b650d3..b6f464161ee2 100644 --- a/library/std/src/sys/anonymous_pipe/mod.rs +++ b/library/std/src/sys/anonymous_pipe/mod.rs @@ -1,13 +1,15 @@ #![forbid(unsafe_op_in_unsafe_fn)] -cfg_if::cfg_if! { - if #[cfg(unix)] { +cfg_select! { + unix => { mod unix; pub use unix::{AnonPipe, pipe}; - } else if #[cfg(windows)] { + } + windows => { mod windows; pub use windows::{AnonPipe, pipe}; - } else { + } + _ => { mod unsupported; pub use unsupported::{AnonPipe, pipe}; } diff --git a/library/std/src/sys/args/common.rs b/library/std/src/sys/args/common.rs index e787105a05a7..33f3794ee633 100644 --- a/library/std/src/sys/args/common.rs +++ b/library/std/src/sys/args/common.rs @@ -12,7 +12,7 @@ impl !Sync for Args {} impl Args { #[inline] - pub(super) fn new(args: Vec) -> Self { + pub fn new(args: Vec) -> Self { Args { iter: args.into_iter() } } } diff --git a/library/std/src/sys/args/mod.rs b/library/std/src/sys/args/mod.rs index 0011f55dc14e..c9627322276d 100644 --- a/library/std/src/sys/args/mod.rs +++ b/library/std/src/sys/args/mod.rs @@ -12,32 +12,39 @@ ))] mod common; -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( all(target_family = "unix", not(any(target_os = "espidf", target_os = "vita"))), target_os = "hermit", - ))] { + ) => { mod unix; pub use unix::*; - } else if #[cfg(target_family = "windows")] { + } + target_family = "windows" => { mod windows; pub use windows::*; - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::*; - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod uefi; pub use uefi::*; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; pub use wasi::*; - } else if #[cfg(target_os = "xous")] { + } + target_os = "xous" => { mod xous; pub use xous::*; - } else if #[cfg(target_os = "zkvm")] { + } + target_os = "zkvm" => { mod zkvm; pub use zkvm::*; - } else { + } + _ => { mod unsupported; pub use unsupported::*; } diff --git a/library/std/src/sys/cmath.rs b/library/std/src/sys/cmath.rs index 299ce1a6ff06..1592218ead8b 100644 --- a/library/std/src/sys/cmath.rs +++ b/library/std/src/sys/cmath.rs @@ -45,69 +45,70 @@ unsafe extern "C" { pub safe fn lgammaf128_r(n: f128, s: &mut i32) -> f128; pub safe fn erff128(n: f128) -> f128; pub safe fn erfcf128(n: f128) -> f128; - - cfg_if::cfg_if! { - if #[cfg(not(all(target_os = "windows", target_env = "msvc", target_arch = "x86")))] { - pub safe fn acosf(n: f32) -> f32; - pub safe fn asinf(n: f32) -> f32; - pub safe fn atan2f(a: f32, b: f32) -> f32; - pub safe fn atanf(n: f32) -> f32; - pub safe fn coshf(n: f32) -> f32; - pub safe fn sinhf(n: f32) -> f32; - pub safe fn tanf(n: f32) -> f32; - pub safe fn tanhf(n: f32) -> f32; - }} } -// On AIX, we don't have lgammaf_r only the f64 version, so we can -// use the f64 version lgamma_r -#[cfg(target_os = "aix")] -pub fn lgammaf_r(n: f32, s: &mut i32) -> f32 { - lgamma_r(n.into(), s) as f32 -} +cfg_select! { + all(target_os = "windows", target_env = "msvc", target_arch = "x86") => { + // On 32-bit x86 MSVC these functions aren't defined, so we just define shims + // which promote everything to f64, perform the calculation, and then demote + // back to f32. While not precisely correct should be "correct enough" for now. + #[inline] + pub fn acosf(n: f32) -> f32 { + f64::acos(n as f64) as f32 + } -// On 32-bit x86 MSVC these functions aren't defined, so we just define shims -// which promote everything to f64, perform the calculation, and then demote -// back to f32. While not precisely correct should be "correct enough" for now. -cfg_if::cfg_if! { -if #[cfg(all(target_os = "windows", target_env = "msvc", target_arch = "x86"))] { - #[inline] - pub fn acosf(n: f32) -> f32 { - f64::acos(n as f64) as f32 - } + #[inline] + pub fn asinf(n: f32) -> f32 { + f64::asin(n as f64) as f32 + } - #[inline] - pub fn asinf(n: f32) -> f32 { - f64::asin(n as f64) as f32 - } + #[inline] + pub fn atan2f(n: f32, b: f32) -> f32 { + f64::atan2(n as f64, b as f64) as f32 + } - #[inline] - pub fn atan2f(n: f32, b: f32) -> f32 { - f64::atan2(n as f64, b as f64) as f32 - } + #[inline] + pub fn atanf(n: f32) -> f32 { + f64::atan(n as f64) as f32 + } - #[inline] - pub fn atanf(n: f32) -> f32 { - f64::atan(n as f64) as f32 - } + #[inline] + pub fn coshf(n: f32) -> f32 { + f64::cosh(n as f64) as f32 + } - #[inline] - pub fn coshf(n: f32) -> f32 { - f64::cosh(n as f64) as f32 - } + #[inline] + pub fn sinhf(n: f32) -> f32 { + f64::sinh(n as f64) as f32 + } - #[inline] - pub fn sinhf(n: f32) -> f32 { - f64::sinh(n as f64) as f32 - } + #[inline] + pub fn tanf(n: f32) -> f32 { + f64::tan(n as f64) as f32 + } - #[inline] - pub fn tanf(n: f32) -> f32 { - f64::tan(n as f64) as f32 + #[inline] + pub fn tanhf(n: f32) -> f32 { + f64::tanh(n as f64) as f32 + } } - - #[inline] - pub fn tanhf(n: f32) -> f32 { - f64::tanh(n as f64) as f32 + _ => { + unsafe extern "C" { + pub safe fn acosf(n: f32) -> f32; + pub safe fn asinf(n: f32) -> f32; + pub safe fn atan2f(a: f32, b: f32) -> f32; + pub safe fn atanf(n: f32) -> f32; + pub safe fn coshf(n: f32) -> f32; + pub safe fn sinhf(n: f32) -> f32; + pub safe fn tanf(n: f32) -> f32; + pub safe fn tanhf(n: f32) -> f32; + } } -}} +} + +// On AIX, we don't have lgammaf_r only the f64 version, so we can +// use the f64 version lgamma_r +#[cfg(target_os = "aix")] +pub fn lgammaf_r(n: f32, s: &mut i32) -> f32 { + lgamma_r(n.into(), s) as f32 +} diff --git a/library/std/src/sys/configure_builtins.rs b/library/std/src/sys/configure_builtins.rs new file mode 100644 index 000000000000..9d776b778dcb --- /dev/null +++ b/library/std/src/sys/configure_builtins.rs @@ -0,0 +1,22 @@ +/// Hook into .init_array to enable LSE atomic operations at startup, if +/// supported. +#[cfg(all(target_arch = "aarch64", target_os = "linux", not(feature = "compiler-builtins-c")))] +#[used] +#[unsafe(link_section = ".init_array.90")] +static RUST_LSE_INIT: extern "C" fn() = { + extern "C" fn init_lse() { + use crate::arch; + + // This is provided by compiler-builtins::aarch64_linux. + unsafe extern "C" { + fn __rust_enable_lse(); + } + + if arch::is_aarch64_feature_detected!("lse") { + unsafe { + __rust_enable_lse(); + } + } + } + init_lse +}; diff --git a/library/std/src/sys/env/mod.rs b/library/std/src/sys/env/mod.rs index d81ff875c830..f211a9fc86b3 100644 --- a/library/std/src/sys/env/mod.rs +++ b/library/std/src/sys/env/mod.rs @@ -13,35 +13,44 @@ ))] mod common; -cfg_if::cfg_if! { - if #[cfg(target_family = "unix")] { +cfg_select! { + target_family = "unix" => { mod unix; pub use unix::*; - } else if #[cfg(target_family = "windows")] { + } + target_family = "windows" => { mod windows; pub use windows::*; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; pub use hermit::*; - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::*; - } else if #[cfg(target_os = "solid_asp3")] { + } + target_os = "solid_asp3" => { mod solid; pub use solid::*; - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod uefi; pub use uefi::*; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; pub use wasi::*; - } else if #[cfg(target_os = "xous")] { + } + target_os = "xous" => { mod xous; pub use xous::*; - } else if #[cfg(target_os = "zkvm")] { + } + target_os = "zkvm" => { mod zkvm; pub use zkvm::*; - } else { + } + _ => { mod unsupported; pub use unsupported::*; } diff --git a/library/std/src/sys/env/wasi.rs b/library/std/src/sys/env/wasi.rs index 3719f9db51eb..1327cbc3263b 100644 --- a/library/std/src/sys/env/wasi.rs +++ b/library/std/src/sys/env/wasi.rs @@ -7,8 +7,8 @@ use crate::os::wasi::prelude::*; use crate::sys::common::small_c_string::run_with_cstr; use crate::sys::pal::os::{cvt, libc}; -cfg_if::cfg_if! { - if #[cfg(target_feature = "atomics")] { +cfg_select! { + target_feature = "atomics" => { // Access to the environment must be protected by a lock in multi-threaded scenarios. use crate::sync::{PoisonError, RwLock}; static ENV_LOCK: RwLock<()> = RwLock::new(()); @@ -18,7 +18,8 @@ cfg_if::cfg_if! { pub fn env_write_lock() -> impl Drop { ENV_LOCK.write().unwrap_or_else(PoisonError::into_inner) } - } else { + } + _ => { // No need for a lock if we are single-threaded. pub fn env_read_lock() -> impl Drop { Box::new(()) diff --git a/library/std/src/sys/exit_guard.rs b/library/std/src/sys/exit_guard.rs index bd70d1782440..00b91842e9db 100644 --- a/library/std/src/sys/exit_guard.rs +++ b/library/std/src/sys/exit_guard.rs @@ -1,5 +1,5 @@ -cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { +cfg_select! { + target_os = "linux" => { /// Mitigation for /// /// On glibc, `libc::exit` has been observed to not always be thread-safe. @@ -56,7 +56,8 @@ cfg_if::cfg_if! { } } } - } else { + } + _ => { /// Mitigation for /// /// Mitigation is ***NOT*** implemented on this platform, either because this platform diff --git a/library/std/src/sys/fd/mod.rs b/library/std/src/sys/fd/mod.rs index e0f5eab69514..7cb9dd1cba9d 100644 --- a/library/std/src/sys/fd/mod.rs +++ b/library/std/src/sys/fd/mod.rs @@ -2,18 +2,22 @@ #![forbid(unsafe_op_in_unsafe_fn)] -cfg_if::cfg_if! { - if #[cfg(target_family = "unix")] { +cfg_select! { + target_family = "unix" => { mod unix; pub use unix::*; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; pub use hermit::*; - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use sgx::*; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; pub use wasi::*; } + _ => {} } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index d55e28074fe8..dbd782f50180 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -5,8 +5,8 @@ use crate::path::{Path, PathBuf}; pub mod common; -cfg_if::cfg_if! { - if #[cfg(target_family = "unix")] { +cfg_select! { + target_family = "unix" => { mod unix; use unix as imp; pub use unix::{chown, fchown, lchown, mkfifo}; @@ -16,24 +16,30 @@ cfg_if::cfg_if! { #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) use unix::CachedFileMetadata; use crate::sys::common::small_c_string::run_path_with_cstr as with_native_path; - } else if #[cfg(target_os = "windows")] { + } + target_os = "windows" => { mod windows; use windows as imp; pub use windows::{symlink_inner, junction_point}; use crate::sys::path::with_native_path; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; use hermit as imp; - } else if #[cfg(target_os = "solid_asp3")] { + } + target_os = "solid_asp3" => { mod solid; use solid as imp; - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod uefi; use uefi as imp; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; use wasi as imp; - } else { + } + _ => { mod unsupported; use unsupported as imp; } @@ -108,6 +114,21 @@ pub fn set_permissions(path: &Path, perm: FilePermissions) -> io::Result<()> { with_native_path(path, &|path| imp::set_perm(path, perm.clone())) } +#[cfg(unix)] +pub fn set_permissions_nofollow(path: &Path, perm: crate::fs::Permissions) -> io::Result<()> { + use crate::fs::OpenOptions; + use crate::os::unix::fs::OpenOptionsExt; + + OpenOptions::new().custom_flags(libc::O_NOFOLLOW).open(path)?.set_permissions(perm) +} + +#[cfg(not(unix))] +pub fn set_permissions_nofollow(_path: &Path, _perm: crate::fs::Permissions) -> io::Result<()> { + crate::unimplemented!( + "`set_permissions_nofollow` is currently only implemented on Unix platforms" + ) +} + pub fn canonicalize(path: &Path) -> io::Result { with_native_path(path, &imp::canonicalize) } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index b310db2dac48..0d710a4b2a6c 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -101,10 +101,11 @@ pub struct File(FileDesc); // https://github.com/rust-lang/rust/pull/67774 macro_rules! cfg_has_statx { ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => { - cfg_if::cfg_if! { - if #[cfg(all(target_os = "linux", target_env = "gnu"))] { + cfg_select! { + all(target_os = "linux", target_env = "gnu") => { $($then_tt)* - } else { + } + _ => { $($else_tt)* } } @@ -1263,6 +1264,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", ))] pub fn lock(&self) -> io::Result<()> { @@ -1275,6 +1278,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", )))] pub fn lock(&self) -> io::Result<()> { @@ -1286,6 +1291,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", ))] pub fn lock_shared(&self) -> io::Result<()> { @@ -1298,6 +1305,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", )))] pub fn lock_shared(&self) -> io::Result<()> { @@ -1309,6 +1318,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", ))] pub fn try_lock(&self) -> Result<(), TryLockError> { @@ -1329,6 +1340,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", )))] pub fn try_lock(&self) -> Result<(), TryLockError> { @@ -1343,6 +1356,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", ))] pub fn try_lock_shared(&self) -> Result<(), TryLockError> { @@ -1363,6 +1378,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", )))] pub fn try_lock_shared(&self) -> Result<(), TryLockError> { @@ -1377,6 +1394,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", ))] pub fn unlock(&self) -> io::Result<()> { @@ -1389,6 +1408,8 @@ impl File { target_os = "fuchsia", target_os = "linux", target_os = "netbsd", + target_os = "openbsd", + target_os = "cygwin", target_vendor = "apple", )))] pub fn unlock(&self) -> io::Result<()> { @@ -1505,8 +1526,8 @@ impl File { )), None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), }; - cfg_if::cfg_if! { - if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx"))] { + cfg_select! { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { // Redox doesn't appear to support `UTIME_OMIT`. // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore // the same as for Redox. @@ -1515,7 +1536,8 @@ impl File { io::ErrorKind::Unsupported, "setting file times not supported", )) - } else if #[cfg(target_vendor = "apple")] { + } + target_vendor = "apple" => { let mut buf = [mem::MaybeUninit::::uninit(); 3]; let mut num_times = 0; let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; @@ -1543,7 +1565,8 @@ impl File { 0 ) })?; Ok(()) - } else if #[cfg(target_os = "android")] { + } + target_os = "android" => { let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; // futimens requires Android API level 19 cvt(unsafe { @@ -1559,7 +1582,8 @@ impl File { } })?; Ok(()) - } else { + } + _ => { #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))] { use crate::sys::{time::__timespec64, weak::weak}; @@ -1677,13 +1701,14 @@ impl fmt::Debug for File { let mut buf = vec![0; libc::PATH_MAX as usize]; let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; if n == -1 { - cfg_if::cfg_if! { - if #[cfg(target_os = "netbsd")] { + cfg_select! { + target_os = "netbsd" => { // fallback to procfs as last resort let mut p = PathBuf::from("/proc/self/fd"); p.push(&fd.to_string()); return run_path_with_cstr(&p, &readlink).ok() - } else { + } + _ => { return None; } } @@ -1884,15 +1909,16 @@ pub fn symlink(original: &CStr, link: &CStr) -> io::Result<()> { } pub fn link(original: &CStr, link: &CStr) -> io::Result<()> { - cfg_if::cfg_if! { - if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita", target_env = "nto70"))] { + cfg_select! { + any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita", target_env = "nto70") => { // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves // it implementation-defined whether `link` follows symlinks, so rely on the // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior. // Android has `linkat` on newer versions, but we happen to know `link` // always has the correct behavior, so it's here as well. cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; - } else { + } + _ => { // Where we can, use `linkat` instead of `link`; see the comment above // this one for details on why. cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 9b674a251652..bb3e4bc30ca9 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -80,7 +80,7 @@ pub struct OpenOptions { attributes: u32, share_mode: u32, security_qos_flags: u32, - security_attributes: *mut c::SECURITY_ATTRIBUTES, + inherit_handle: bool, } #[derive(Clone, PartialEq, Eq, Debug)] @@ -203,7 +203,7 @@ impl OpenOptions { share_mode: c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE, attributes: 0, security_qos_flags: 0, - security_attributes: ptr::null_mut(), + inherit_handle: false, } } @@ -243,8 +243,8 @@ impl OpenOptions { // receive is `SECURITY_ANONYMOUS = 0x0`, which we can't check for later on. self.security_qos_flags = flags | c::SECURITY_SQOS_PRESENT; } - pub fn security_attributes(&mut self, attrs: *mut c::SECURITY_ATTRIBUTES) { - self.security_attributes = attrs; + pub fn inherit_handle(&mut self, inherit: bool) { + self.inherit_handle = inherit; } fn get_access_mode(&self) -> io::Result { @@ -307,12 +307,17 @@ impl File { fn open_native(path: &WCStr, opts: &OpenOptions) -> io::Result { let creation = opts.get_creation_mode()?; + let sa = c::SECURITY_ATTRIBUTES { + nLength: size_of::() as u32, + lpSecurityDescriptor: ptr::null_mut(), + bInheritHandle: opts.inherit_handle as c::BOOL, + }; let handle = unsafe { c::CreateFileW( path.as_ptr(), opts.get_access_mode()?, opts.share_mode, - opts.security_attributes, + if opts.inherit_handle { &sa } else { ptr::null() }, creation, opts.get_flags_and_attributes(), ptr::null_mut(), @@ -1601,7 +1606,7 @@ pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> { }; unsafe { let ptr = header.PathBuffer.as_mut_ptr(); - ptr.copy_from(abs_path.as_ptr().cast::>(), abs_path.len()); + ptr.copy_from(abs_path.as_ptr().cast_uninit(), abs_path.len()); let mut ret = 0; cvt(c::DeviceIoControl( diff --git a/library/std/src/sys/io/io_slice/uefi.rs b/library/std/src/sys/io/io_slice/uefi.rs new file mode 100644 index 000000000000..909cfbea0b7b --- /dev/null +++ b/library/std/src/sys/io/io_slice/uefi.rs @@ -0,0 +1,74 @@ +//! A buffer type used with `Write::write_vectored` for UEFI Networking APIs. Vectored writing to +//! File is not supported as of UEFI Spec 2.11. + +use crate::marker::PhantomData; +use crate::slice; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct IoSlice<'a> { + len: u32, + data: *const u8, + _p: PhantomData<&'a [u8]>, +} + +impl<'a> IoSlice<'a> { + #[inline] + pub fn new(buf: &'a [u8]) -> IoSlice<'a> { + let len = buf.len().try_into().unwrap(); + Self { len, data: buf.as_ptr(), _p: PhantomData } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + self.len = u32::try_from(n) + .ok() + .and_then(|n| self.len.checked_sub(n)) + .expect("advancing IoSlice beyond its length"); + unsafe { self.data = self.data.add(n) }; + } + + #[inline] + pub const fn as_slice(&self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self.data, self.len as usize) } + } +} + +#[repr(C)] +pub struct IoSliceMut<'a> { + len: u32, + data: *mut u8, + _p: PhantomData<&'a mut [u8]>, +} + +impl<'a> IoSliceMut<'a> { + #[inline] + pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { + let len = buf.len().try_into().unwrap(); + Self { len, data: buf.as_mut_ptr(), _p: PhantomData } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + self.len = u32::try_from(n) + .ok() + .and_then(|n| self.len.checked_sub(n)) + .expect("advancing IoSlice beyond its length"); + unsafe { self.data = self.data.add(n) }; + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.data, self.len as usize) } + } + + #[inline] + pub const fn into_slice(self) -> &'a mut [u8] { + unsafe { slice::from_raw_parts_mut(self.data, self.len as usize) } + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.data, self.len as usize) } + } +} diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index 4d0365d42fd9..fe8ec1dbb732 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -1,17 +1,24 @@ #![forbid(unsafe_op_in_unsafe_fn)] mod io_slice { - cfg_if::cfg_if! { - if #[cfg(any(target_family = "unix", target_os = "hermit", target_os = "solid_asp3", target_os = "trusty"))] { + cfg_select! { + any(target_family = "unix", target_os = "hermit", target_os = "solid_asp3", target_os = "trusty") => { mod iovec; pub use iovec::*; - } else if #[cfg(target_os = "windows")] { + } + target_os = "windows" => { mod windows; pub use windows::*; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; pub use wasi::*; - } else { + } + target_os = "uefi" => { + mod uefi; + pub use uefi::*; + } + _ => { mod unsupported; pub use unsupported::*; } @@ -19,17 +26,20 @@ mod io_slice { } mod is_terminal { - cfg_if::cfg_if! { - if #[cfg(any(target_family = "unix", target_os = "wasi"))] { + cfg_select! { + any(target_family = "unix", target_os = "wasi") => { mod isatty; pub use isatty::*; - } else if #[cfg(target_os = "windows")] { + } + target_os = "windows" => { mod windows; pub use windows::*; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; pub use hermit::*; - } else { + } + _ => { mod unsupported; pub use unsupported::*; } diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index f9a02b522e5e..6324c1a232af 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -1,5 +1,10 @@ #![allow(unsafe_op_in_unsafe_fn)] +/// The configure builtins provides runtime support compiler-builtin features +/// which require dynamic initialization to work as expected, e.g. aarch64 +/// outline-atomics. +mod configure_builtins; + /// The PAL (platform abstraction layer) contains platform-specific abstractions /// for implementing the features in the other submodules, e.g. UNIX file /// descriptors. diff --git a/library/std/src/sys/net/connection/socket.rs b/library/std/src/sys/net/connection/socket.rs index 7301bde6881a..aa83ed65d4c2 100644 --- a/library/std/src/sys/net/connection/socket.rs +++ b/library/std/src/sys/net/connection/socket.rs @@ -9,29 +9,34 @@ use crate::sys_common::{AsInner, FromInner}; use crate::time::Duration; use crate::{cmp, fmt, mem, ptr}; -cfg_if::cfg_if! { - if #[cfg(target_os = "hermit")] { +cfg_select! { + target_os = "hermit" => { mod hermit; pub use hermit::*; - } else if #[cfg(target_os = "solid_asp3")] { + } + target_os = "solid_asp3" => { mod solid; pub use solid::*; - } else if #[cfg(target_family = "unix")] { + } + target_family = "unix" => { mod unix; pub use unix::*; - } else if #[cfg(all(target_os = "wasi", target_env = "p2"))] { + } + all(target_os = "wasi", target_env = "p2") => { mod wasip2; pub use wasip2::*; - } else if #[cfg(target_os = "windows")] { + } + target_os = "windows" => { mod windows; pub use windows::*; } + _ => {} } use netc as c; -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", @@ -43,39 +48,44 @@ cfg_if::cfg_if! { target_os = "nto", target_os = "nuttx", target_vendor = "apple", - ))] { + ) => { use c::IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP; use c::IPV6_LEAVE_GROUP as IPV6_DROP_MEMBERSHIP; - } else { + } + _ => { use c::IPV6_ADD_MEMBERSHIP; use c::IPV6_DROP_MEMBERSHIP; } } -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( target_os = "linux", target_os = "android", target_os = "hurd", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "solaris", target_os = "illumos", target_os = "haiku", target_os = "nto", - target_os = "cygwin"))] { + target_os = "cygwin", + ) => { use libc::MSG_NOSIGNAL; - } else { + } + _ => { const MSG_NOSIGNAL: c_int = 0x0; } } -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "solaris", target_os = "illumos", - target_os = "nto"))] { + target_os = "nto", + ) => { use crate::ffi::c_uchar; type IpV4MultiCastType = c_uchar; - } else { + } + _ => { type IpV4MultiCastType = c_int; } } @@ -523,17 +533,19 @@ impl TcpListener { let (addr, len) = socket_addr_to_c(addr); cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?; - cfg_if::cfg_if! { - if #[cfg(target_os = "horizon")] { + cfg_select! { + target_os = "horizon" => { // The 3DS doesn't support a big connection backlog. Sometimes // it allows up to about 37, but other times it doesn't even // accept 32. There may be a global limitation causing this. let backlog = 20; - } else if #[cfg(target_os = "haiku")] { + } + target_os = "haiku" => { // Haiku does not support a queue length > 32 // https://github.com/haiku/haiku/blob/979a0bc487864675517fb2fab28f87dc8bf43041/headers/posix/sys/socket.h#L81 let backlog = 32; - } else { + } + _ => { // The default for all other platforms let backlog = 128; } diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index cc111f3521bc..8b5970d1494e 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -12,10 +12,11 @@ use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::time::{Duration, Instant}; use crate::{cmp, mem}; -cfg_if::cfg_if! { - if #[cfg(target_vendor = "apple")] { +cfg_select! { + target_vendor = "apple" => { use libc::SO_LINGER_SEC as SO_LINGER; - } else { + } + _ => { use libc::SO_LINGER; } } @@ -72,8 +73,8 @@ impl Socket { pub fn new_raw(fam: c_int, ty: c_int) -> io::Result { unsafe { - cfg_if::cfg_if! { - if #[cfg(any( + cfg_select! { + any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", @@ -85,7 +86,7 @@ impl Socket { target_os = "cygwin", target_os = "nto", target_os = "solaris", - ))] { + ) => { // On platforms that support it we pass the SOCK_CLOEXEC // flag to atomically create the socket and set it as // CLOEXEC. On Linux this was added in 2.6.27. @@ -98,7 +99,8 @@ impl Socket { setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?; Ok(socket) - } else { + } + _ => { let fd = cvt(libc::socket(fam, ty, 0))?; let fd = FileDesc::from_raw_fd(fd); fd.set_cloexec()?; @@ -120,8 +122,8 @@ impl Socket { unsafe { let mut fds = [0, 0]; - cfg_if::cfg_if! { - if #[cfg(any( + cfg_select! { + any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", @@ -132,11 +134,12 @@ impl Socket { target_os = "openbsd", target_os = "cygwin", target_os = "nto", - ))] { + ) => { // Like above, set cloexec atomically cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?; Ok((Socket(FileDesc::from_raw_fd(fds[0])), Socket(FileDesc::from_raw_fd(fds[1])))) - } else { + } + _ => { cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?; let a = FileDesc::from_raw_fd(fds[0]); let b = FileDesc::from_raw_fd(fds[1]); @@ -250,8 +253,8 @@ impl Socket { // atomically set the CLOEXEC flag is to use the `accept4` syscall on // platforms that support it. On Linux, this was added in 2.6.28, // glibc 2.10 and musl 0.9.5. - cfg_if::cfg_if! { - if #[cfg(any( + cfg_select! { + any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", @@ -261,12 +264,13 @@ impl Socket { target_os = "netbsd", target_os = "openbsd", target_os = "cygwin", - ))] { + ) => { unsafe { let fd = cvt_r(|| libc::accept4(self.as_raw_fd(), storage, len, libc::SOCK_CLOEXEC))?; Ok(Socket(FileDesc::from_raw_fd(fd))) } - } else { + } + _ => { unsafe { let fd = cvt_r(|| libc::accept(self.as_raw_fd(), storage, len))?; let fd = FileDesc::from_raw_fd(fd); diff --git a/library/std/src/sys/net/mod.rs b/library/std/src/sys/net/mod.rs index 646679a1cc8b..5df1fe138ab2 100644 --- a/library/std/src/sys/net/mod.rs +++ b/library/std/src/sys/net/mod.rs @@ -1,36 +1,41 @@ -cfg_if::cfg_if! { - if #[cfg(any( +cfg_select! { + any( all(target_family = "unix", not(target_os = "l4re")), target_os = "windows", target_os = "hermit", all(target_os = "wasi", target_env = "p2"), target_os = "solid_asp3", - ))] { + ) => { mod connection { mod socket; pub use socket::*; } - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod connection { mod sgx; pub use sgx::*; } - } else if #[cfg(all(target_os = "wasi", target_env = "p1"))] { + } + all(target_os = "wasi", target_env = "p1") => { mod connection { mod wasip1; pub use wasip1::*; } - } else if #[cfg(target_os = "xous")] { + } + target_os = "xous" => { mod connection { mod xous; pub use xous::*; } - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod connection { mod uefi; pub use uefi::*; } - } else { + } + _ => { mod connection { mod unsupported; pub use unsupported::*; diff --git a/library/std/src/sys/os_str/mod.rs b/library/std/src/sys/os_str/mod.rs index 345e661586d0..65c90d880495 100644 --- a/library/std/src/sys/os_str/mod.rs +++ b/library/std/src/sys/os_str/mod.rs @@ -1,13 +1,11 @@ #![forbid(unsafe_op_in_unsafe_fn)] -cfg_if::cfg_if! { - if #[cfg(any( - target_os = "windows", - target_os = "uefi", - ))] { +cfg_select! { + any(target_os = "windows", target_os = "uefi") => { mod wtf8; pub use wtf8::{Buf, Slice}; - } else { + } + _ => { mod bytes; pub use bytes::{Buf, Slice}; } diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs index 95fe4f902d33..cc4734b68190 100644 --- a/library/std/src/sys/pal/hermit/thread.rs +++ b/library/std/src/sys/pal/hermit/thread.rs @@ -115,6 +115,10 @@ impl Thread { } } +pub(crate) fn current_os_id() -> Option { + None +} + pub fn available_parallelism() -> io::Result> { unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) } } diff --git a/library/std/src/sys/pal/hermit/time.rs b/library/std/src/sys/pal/hermit/time.rs index f76a5f96c875..89a427ab88ba 100644 --- a/library/std/src/sys/pal/hermit/time.rs +++ b/library/std/src/sys/pal/hermit/time.rs @@ -25,8 +25,15 @@ impl Timespec { Timespec { t: timespec { tv_sec, tv_nsec } } } - fn sub_timespec(&self, other: &Timespec) -> Result { - if self >= other { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + const fn sub_timespec(&self, other: &Timespec) -> Result { + // FIXME: const PartialOrd + let mut cmp = self.t.tv_sec - other.t.tv_sec; + if cmp == 0 { + cmp = self.t.tv_nsec as i64 - other.t.tv_nsec as i64; + } + + if cmp >= 0 { Ok(if self.t.tv_nsec >= other.t.tv_nsec { Duration::new( (self.t.tv_sec - other.t.tv_sec) as u64, @@ -46,20 +53,22 @@ impl Timespec { } } - fn checked_add_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + const fn checked_add_duration(&self, other: &Duration) -> Option { let mut secs = self.t.tv_sec.checked_add_unsigned(other.as_secs())?; // Nano calculations can't overflow because nanos are <1B which fit // in a u32. - let mut nsec = other.subsec_nanos() + u32::try_from(self.t.tv_nsec).unwrap(); - if nsec >= NSEC_PER_SEC.try_into().unwrap() { - nsec -= u32::try_from(NSEC_PER_SEC).unwrap(); + let mut nsec = other.subsec_nanos() + self.t.tv_nsec as u32; + if nsec >= NSEC_PER_SEC as u32 { + nsec -= NSEC_PER_SEC as u32; secs = secs.checked_add(1)?; } Some(Timespec { t: timespec { tv_sec: secs, tv_nsec: nsec as _ } }) } - fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + const fn checked_sub_duration(&self, other: &Duration) -> Option { let mut secs = self.t.tv_sec.checked_sub_unsigned(other.as_secs())?; // Similar to above, nanos can't overflow. @@ -213,15 +222,18 @@ impl SystemTime { SystemTime(time) } - pub fn sub_time(&self, other: &SystemTime) -> Result { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result { self.0.sub_timespec(&other.0) } - pub fn checked_add_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_add_duration(other)?)) } - pub fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_sub_duration(other)?)) } } diff --git a/library/std/src/sys/pal/itron/thread.rs b/library/std/src/sys/pal/itron/thread.rs index 0d28051fcc46..4e14cb3cbcaa 100644 --- a/library/std/src/sys/pal/itron/thread.rs +++ b/library/std/src/sys/pal/itron/thread.rs @@ -361,6 +361,10 @@ unsafe fn terminate_and_delete_current_task() -> ! { unsafe { crate::hint::unreachable_unchecked() }; } +pub(crate) fn current_os_id() -> Option { + None +} + pub fn available_parallelism() -> io::Result> { super::unsupported() } diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index fbefc62ac88e..9376f5249f1c 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -24,60 +24,70 @@ pub mod common; -cfg_if::cfg_if! { - if #[cfg(unix)] { +cfg_select! { + unix => { mod unix; pub use self::unix::*; - } else if #[cfg(windows)] { + } + windows => { mod windows; pub use self::windows::*; - } else if #[cfg(target_os = "solid_asp3")] { + } + target_os = "solid_asp3" => { mod solid; pub use self::solid::*; - } else if #[cfg(target_os = "hermit")] { + } + target_os = "hermit" => { mod hermit; pub use self::hermit::*; - } else if #[cfg(target_os = "trusty")] { + } + target_os = "trusty" => { mod trusty; pub use self::trusty::*; - } else if #[cfg(all(target_os = "wasi", target_env = "p2"))] { + } + all(target_os = "wasi", target_env = "p2") => { mod wasip2; pub use self::wasip2::*; - } else if #[cfg(target_os = "wasi")] { + } + target_os = "wasi" => { mod wasi; pub use self::wasi::*; - } else if #[cfg(target_family = "wasm")] { + } + target_family = "wasm" => { mod wasm; pub use self::wasm::*; - } else if #[cfg(target_os = "xous")] { + } + target_os = "xous" => { mod xous; pub use self::xous::*; - } else if #[cfg(target_os = "uefi")] { + } + target_os = "uefi" => { mod uefi; pub use self::uefi::*; - } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + } + all(target_vendor = "fortanix", target_env = "sgx") => { mod sgx; pub use self::sgx::*; - } else if #[cfg(target_os = "teeos")] { + } + target_os = "teeos" => { mod teeos; pub use self::teeos::*; - } else if #[cfg(target_os = "zkvm")] { + } + target_os = "zkvm" => { mod zkvm; pub use self::zkvm::*; - } else { + } + _ => { mod unsupported; pub use self::unsupported::*; } } -cfg_if::cfg_if! { +pub const FULL_BACKTRACE_DEFAULT: bool = cfg_select! { // Fuchsia components default to full backtrace. - if #[cfg(target_os = "fuchsia")] { - pub const FULL_BACKTRACE_DEFAULT: bool = true; - } else { - pub const FULL_BACKTRACE_DEFAULT: bool = false; - } -} + target_os = "fuchsia" => true, + _ => false, +}; #[cfg(not(target_os = "uefi"))] pub type RawOsError = i32; diff --git a/library/std/src/sys/pal/sgx/thread.rs b/library/std/src/sys/pal/sgx/thread.rs index a236c3627060..1f613badcd70 100644 --- a/library/std/src/sys/pal/sgx/thread.rs +++ b/library/std/src/sys/pal/sgx/thread.rs @@ -1,6 +1,6 @@ #![cfg_attr(test, allow(dead_code))] // why is this necessary? -use super::abi::usercalls; +use super::abi::{thread, usercalls}; use super::unsupported; use crate::ffi::CStr; use crate::io; @@ -149,6 +149,10 @@ impl Thread { } } +pub(crate) fn current_os_id() -> Option { + Some(thread::current().addr().get() as u64) +} + pub fn available_parallelism() -> io::Result> { unsupported() } diff --git a/library/std/src/sys/pal/sgx/time.rs b/library/std/src/sys/pal/sgx/time.rs index db4cf2804bf1..603dae952abd 100644 --- a/library/std/src/sys/pal/sgx/time.rs +++ b/library/std/src/sys/pal/sgx/time.rs @@ -32,15 +32,22 @@ impl SystemTime { SystemTime(usercalls::insecure_time()) } - pub fn sub_time(&self, other: &SystemTime) -> Result { - self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result { + // FIXME: ok_or_else with const closures + match self.0.checked_sub(other.0) { + Some(duration) => Ok(duration), + None => Err(other.0 - self.0), + } } - pub fn checked_add_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_add(*other)?)) } - pub fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_sub(*other)?)) } } diff --git a/library/std/src/sys/pal/solid/time.rs b/library/std/src/sys/pal/solid/time.rs index c39d715c6a6f..e35e60df1a0a 100644 --- a/library/std/src/sys/pal/solid/time.rs +++ b/library/std/src/sys/pal/solid/time.rs @@ -39,7 +39,8 @@ impl SystemTime { Self(t) } - pub fn sub_time(&self, other: &SystemTime) -> Result { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result { if self.0 >= other.0 { Ok(Duration::from_secs((self.0 as u64).wrapping_sub(other.0 as u64))) } else { @@ -47,11 +48,13 @@ impl SystemTime { } } - pub fn checked_add_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_add_unsigned(other.as_secs())?)) } - pub fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_sub_unsigned(other.as_secs())?)) } } diff --git a/library/std/src/sys/pal/teeos/thread.rs b/library/std/src/sys/pal/teeos/thread.rs index a91d95626e70..1812d11e692e 100644 --- a/library/std/src/sys/pal/teeos/thread.rs +++ b/library/std/src/sys/pal/teeos/thread.rs @@ -144,6 +144,10 @@ impl Drop for Thread { } } +pub(crate) fn current_os_id() -> Option { + None +} + // Note: Both `sched_getaffinity` and `sysconf` are available but not functional on // teeos, so this function always returns an Error! pub fn available_parallelism() -> io::Result> { diff --git a/library/std/src/sys/pal/uefi/tests.rs b/library/std/src/sys/pal/uefi/tests.rs index 38658cc4e9ac..56ca999cc7e9 100644 --- a/library/std/src/sys/pal/uefi/tests.rs +++ b/library/std/src/sys/pal/uefi/tests.rs @@ -1,7 +1,13 @@ +//! These tests are not run automatically right now. Please run these tests manually by copying them +//! to a separate project when modifying any related code. + use super::alloc::*; -use super::time::*; +use super::time::system_time_internal::{from_uefi, to_uefi}; +use crate::io::{IoSlice, IoSliceMut}; use crate::time::Duration; +const SECS_IN_MINUTE: u64 = 60; + #[test] fn align() { // UEFI ABI specifies that allocation alignment minimum is always 8. So this can be @@ -23,19 +29,177 @@ fn align() { } #[test] -fn epoch() { - let t = r_efi::system::Time { - year: 1970, +fn systemtime_start() { + let t = r_efi::efi::Time { + year: 1900, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0, - timezone: r_efi::efi::UNSPECIFIED_TIMEZONE, + timezone: -1440, daylight: 0, + pad2: 0, + }; + assert_eq!(from_uefi(&t), Duration::new(0, 0)); + assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap()); + assert!(to_uefi(&from_uefi(&t), 0, 0).is_none()); +} + +#[test] +fn systemtime_utc_start() { + let t = r_efi::efi::Time { + year: 1900, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, pad1: 0, + nanosecond: 0, + timezone: 0, + daylight: 0, pad2: 0, }; - assert_eq!(system_time_internal::uefi_time_to_duration(t), Duration::new(0, 0)); + assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0)); + assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap()); + assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some()); +} + +#[test] +fn systemtime_end() { + let t = r_efi::efi::Time { + year: 9999, + month: 12, + day: 31, + hour: 23, + minute: 59, + second: 59, + pad1: 0, + nanosecond: 0, + timezone: 1440, + daylight: 0, + pad2: 0, + }; + assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some()); + assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none()); +} + +// UEFI IoSlice and IoSliceMut Tests +// +// Strictly speaking, vectored read/write types for UDP4, UDP6, TCP4, TCP6 are defined +// separately in the UEFI Spec. However, they have the same signature. These tests just ensure +// that `IoSlice` and `IoSliceMut` are compatible with the vectored types for all the +// networking protocols. + +unsafe fn to_slice(val: &T) -> &[u8] { + let len = size_of_val(val); + unsafe { crate::slice::from_raw_parts(crate::ptr::from_ref(val).cast(), len) } +} + +#[test] +fn io_slice_single() { + let mut data = [0, 1, 2, 3, 4]; + + let tcp4_frag = r_efi::protocols::tcp4::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let tcp6_frag = r_efi::protocols::tcp6::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let udp4_frag = r_efi::protocols::udp4::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let udp6_frag = r_efi::protocols::udp6::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let io_slice = IoSlice::new(&data); + + unsafe { + assert_eq!(to_slice(&io_slice), to_slice(&tcp4_frag)); + assert_eq!(to_slice(&io_slice), to_slice(&tcp6_frag)); + assert_eq!(to_slice(&io_slice), to_slice(&udp4_frag)); + assert_eq!(to_slice(&io_slice), to_slice(&udp6_frag)); + } +} + +#[test] +fn io_slice_mut_single() { + let mut data = [0, 1, 2, 3, 4]; + + let tcp4_frag = r_efi::protocols::tcp4::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let tcp6_frag = r_efi::protocols::tcp6::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let udp4_frag = r_efi::protocols::udp4::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let udp6_frag = r_efi::protocols::udp6::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let io_slice_mut = IoSliceMut::new(&mut data); + + unsafe { + assert_eq!(to_slice(&io_slice_mut), to_slice(&tcp4_frag)); + assert_eq!(to_slice(&io_slice_mut), to_slice(&tcp6_frag)); + assert_eq!(to_slice(&io_slice_mut), to_slice(&udp4_frag)); + assert_eq!(to_slice(&io_slice_mut), to_slice(&udp6_frag)); + } +} + +#[test] +fn io_slice_multi() { + let mut data = [0, 1, 2, 3, 4]; + + let tcp4_frag = r_efi::protocols::tcp4::FragmentData { + fragment_length: data.len().try_into().unwrap(), + fragment_buffer: data.as_mut_ptr().cast(), + }; + let rhs = + [tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag.clone(), tcp4_frag]; + let lhs = [ + IoSlice::new(&data), + IoSlice::new(&data), + IoSlice::new(&data), + IoSlice::new(&data), + IoSlice::new(&data), + ]; + + unsafe { + assert_eq!(to_slice(&lhs), to_slice(&rhs)); + } +} + +#[test] +fn io_slice_basic() { + let data = [0, 1, 2, 3, 4]; + let mut io_slice = IoSlice::new(&data); + + assert_eq!(data, io_slice.as_slice()); + io_slice.advance(2); + assert_eq!(&data[2..], io_slice.as_slice()); +} + +#[test] +fn io_slice_mut_basic() { + let data = [0, 1, 2, 3, 4]; + let mut data_clone = [0, 1, 2, 3, 4]; + let mut io_slice_mut = IoSliceMut::new(&mut data_clone); + + assert_eq!(data, io_slice_mut.as_slice()); + assert_eq!(data, io_slice_mut.as_mut_slice()); + + io_slice_mut.advance(2); + assert_eq!(&data[2..], io_slice_mut.into_slice()); } diff --git a/library/std/src/sys/pal/uefi/thread.rs b/library/std/src/sys/pal/uefi/thread.rs index 75c364362b2d..47a48008c76d 100644 --- a/library/std/src/sys/pal/uefi/thread.rs +++ b/library/std/src/sys/pal/uefi/thread.rs @@ -56,6 +56,10 @@ impl Thread { } } +pub(crate) fn current_os_id() -> Option { + None +} + pub fn available_parallelism() -> io::Result> { // UEFI is single threaded Ok(NonZero::new(1).unwrap()) diff --git a/library/std/src/sys/pal/uefi/time.rs b/library/std/src/sys/pal/uefi/time.rs index eeb2c35ffbbc..36ce3f7ef962 100644 --- a/library/std/src/sys/pal/uefi/time.rs +++ b/library/std/src/sys/pal/uefi/time.rs @@ -1,16 +1,42 @@ use crate::time::Duration; -const SECS_IN_MINUTE: u64 = 60; -const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60; -const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct Instant(Duration); +/// When a Timezone is specified, the stored Duration is in UTC. If timezone is unspecified, then +/// the timezone is assumed to be in UTC. +/// +/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00 with timezone -1440 as anchor #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct SystemTime(Duration); -pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); +pub const UNIX_EPOCH: SystemTime = SystemTime::from_uefi(r_efi::efi::Time { + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + nanosecond: 0, + timezone: 0, + daylight: 0, + pad1: 0, + pad2: 0, +}); + +const MAX_UEFI_TIME: SystemTime = SystemTime::from_uefi(r_efi::efi::Time { + year: 9999, + month: 12, + day: 31, + hour: 23, + minute: 59, + second: 59, + nanosecond: 999_999_999, + timezone: 1440, + daylight: 0, + pad1: 0, + pad2: 0, +}); impl Instant { pub fn now() -> Instant { @@ -40,20 +66,45 @@ impl Instant { } impl SystemTime { + pub(crate) const fn from_uefi(t: r_efi::efi::Time) -> Self { + Self(system_time_internal::from_uefi(&t)) + } + + #[expect(dead_code)] + pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option { + system_time_internal::to_uefi(&self.0, timezone, daylight) + } + pub fn now() -> SystemTime { system_time_internal::now() .unwrap_or_else(|| panic!("time not implemented on this platform")) } - pub fn sub_time(&self, other: &SystemTime) -> Result { - self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn sub_time(&self, other: &SystemTime) -> Result { + // FIXME: ok_or_else with const closures + match self.0.checked_sub(other.0) { + Some(duration) => Ok(duration), + None => Err(other.0 - self.0), + } } - pub fn checked_add_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_add(*other)?)) + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_add_duration(&self, other: &Duration) -> Option { + let temp = self.0.checked_add(*other)?; + + // Check if can be represented in UEFI + // FIXME: const PartialOrd + let mut cmp = temp.as_secs() - MAX_UEFI_TIME.0.as_secs(); + if cmp == 0 { + cmp = temp.subsec_nanos() as u64 - MAX_UEFI_TIME.0.subsec_nanos() as u64; + } + + if cmp <= 0 { Some(SystemTime(temp)) } else { None } } - pub fn checked_sub_duration(&self, other: &Duration) -> Option { + #[rustc_const_unstable(feature = "const_system_time", issue = "144517")] + pub const fn checked_sub_duration(&self, other: &Duration) -> Option { Some(SystemTime(self.0.checked_sub(*other)?)) } } @@ -66,51 +117,132 @@ pub(crate) mod system_time_internal { use crate::mem::MaybeUninit; use crate::ptr::NonNull; + const SECS_IN_MINUTE: u64 = 60; + const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60; + const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; + const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE; + pub fn now() -> Option { let runtime_services: NonNull = helpers::runtime_services()?; let mut t: MaybeUninit

, languages: Vec<&'static str>, - rustdoc_compiler: Option, + /// Compiler whose rustdoc should be used to document things using `mdbook-spec`. + build_compiler: Option, } impl Step for RustbookSrc

{ @@ -148,8 +150,8 @@ impl Step for RustbookSrc

{ let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook); - if let Some(compiler) = self.rustdoc_compiler { - let mut rustdoc = builder.rustdoc(compiler); + if let Some(compiler) = self.build_compiler { + let mut rustdoc = builder.rustdoc_for_compiler(compiler); rustdoc.pop(); let old_path = env::var_os("PATH").unwrap_or_default(); let new_path = @@ -191,11 +193,21 @@ impl Step for RustbookSrc

{ builder.maybe_open_in_browser::

(index) } } + + fn metadata(&self) -> Option { + let mut metadata = StepMetadata::doc(&format!("{} (book)", self.name), self.target); + if let Some(compiler) = self.build_compiler { + metadata = metadata.built_by(compiler); + } + + Some(metadata) + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct TheBook { - compiler: Compiler, + /// Compiler whose rustdoc will be used to generated documentation. + build_compiler: Compiler, target: TargetSelection, } @@ -210,7 +222,7 @@ impl Step for TheBook { fn make_run(run: RunConfig<'_>) { run.builder.ensure(TheBook { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: prepare_doc_compiler(run.builder, run.target, run.builder.top_stage), target: run.target, }); } @@ -227,7 +239,7 @@ impl Step for TheBook { fn run(self, builder: &Builder<'_>) { builder.require_submodule("src/doc/book", None); - let compiler = self.compiler; + let build_compiler = self.build_compiler; let target = self.target; let absolute_path = builder.src.join("src/doc/book"); @@ -240,7 +252,7 @@ impl Step for TheBook { src: absolute_path.clone(), parent: Some(self), languages: vec![], - rustdoc_compiler: None, + build_compiler: None, }); // building older edition redirects @@ -253,7 +265,7 @@ impl Step for TheBook { // treat the other editions as not having a parent. parent: Option::::None, languages: vec![], - rustdoc_compiler: None, + build_compiler: None, }); } @@ -261,20 +273,20 @@ impl Step for TheBook { let shared_assets = builder.ensure(SharedAssets { target }); // build the redirect pages - let _guard = builder.msg_doc(compiler, "book redirect pages", target); + let _guard = builder.msg(Kind::Doc, "book redirect pages", None, build_compiler, target); for file in t!(fs::read_dir(redirect_path)) { let file = t!(file); let path = file.path(); let path = path.to_str().unwrap(); - invoke_rustdoc(builder, compiler, &shared_assets, target, path); + invoke_rustdoc(builder, build_compiler, &shared_assets, target, path); } } } fn invoke_rustdoc( builder: &Builder<'_>, - compiler: Compiler, + build_compiler: Compiler, shared_assets: &SharedAssetsPaths, target: TargetSelection, markdown: &str, @@ -286,7 +298,7 @@ fn invoke_rustdoc( let header = builder.src.join("src/doc/redirect.inc"); let footer = builder.src.join("src/doc/footer.inc"); - let mut cmd = builder.rustdoc_cmd(compiler); + let mut cmd = builder.rustdoc_cmd(build_compiler); let out = out.join("book"); @@ -315,7 +327,7 @@ fn invoke_rustdoc( #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Standalone { - compiler: Compiler, + build_compiler: Compiler, target: TargetSelection, } @@ -330,7 +342,11 @@ impl Step for Standalone { fn make_run(run: RunConfig<'_>) { run.builder.ensure(Standalone { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: prepare_doc_compiler( + run.builder, + run.builder.host_target, + run.builder.top_stage, + ), target: run.target, }); } @@ -345,8 +361,8 @@ impl Step for Standalone { /// In the end, this is just a glorified wrapper around rustdoc! fn run(self, builder: &Builder<'_>) { let target = self.target; - let compiler = self.compiler; - let _guard = builder.msg_doc(compiler, "standalone", target); + let build_compiler = self.build_compiler; + let _guard = builder.msg(Kind::Doc, "standalone", None, build_compiler, target); let out = builder.doc_out(target); t!(fs::create_dir_all(&out)); @@ -365,7 +381,7 @@ impl Step for Standalone { } let html = out.join(filename).with_extension("html"); - let rustdoc = builder.rustdoc(compiler); + let rustdoc = builder.rustdoc_for_compiler(build_compiler); if up_to_date(&path, &html) && up_to_date(&footer, &html) && up_to_date(&favicon, &html) @@ -376,7 +392,7 @@ impl Step for Standalone { continue; } - let mut cmd = builder.rustdoc_cmd(compiler); + let mut cmd = builder.rustdoc_cmd(build_compiler); cmd.arg("--html-after-content") .arg(&footer) @@ -413,11 +429,15 @@ impl Step for Standalone { builder.open_in_browser(index); } } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc("standalone", self.target).built_by(self.build_compiler)) + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Releases { - compiler: Compiler, + build_compiler: Compiler, target: TargetSelection, } @@ -432,7 +452,11 @@ impl Step for Releases { fn make_run(run: RunConfig<'_>) { run.builder.ensure(Releases { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: prepare_doc_compiler( + run.builder, + run.builder.host_target, + run.builder.top_stage, + ), target: run.target, }); } @@ -444,15 +468,12 @@ impl Step for Releases { /// the headline added. In the end, the conversion is done by Rustdoc. fn run(self, builder: &Builder<'_>) { let target = self.target; - let compiler = self.compiler; - let _guard = builder.msg_doc(compiler, "releases", target); + let build_compiler = self.build_compiler; + let _guard = builder.msg(Kind::Doc, "releases", None, build_compiler, target); let out = builder.doc_out(target); t!(fs::create_dir_all(&out)); - builder.ensure(Standalone { - compiler: builder.compiler(builder.top_stage, builder.config.host_target), - target, - }); + builder.ensure(Standalone { build_compiler, target }); let version_info = builder.ensure(SharedAssets { target: self.target }).version_info; @@ -463,7 +484,7 @@ impl Step for Releases { let html = out.join("releases.html"); let tmppath = out.join("releases.md"); let inpath = builder.src.join("RELEASES.md"); - let rustdoc = builder.rustdoc(compiler); + let rustdoc = builder.rustdoc_for_compiler(build_compiler); if !up_to_date(&inpath, &html) || !up_to_date(&footer, &html) || !up_to_date(&favicon, &html) @@ -476,7 +497,7 @@ impl Step for Releases { t!(tmpfile.write_all(b"% Rust Release Notes\n\n")); t!(io::copy(&mut t!(fs::File::open(&inpath)), &mut tmpfile)); mem::drop(tmpfile); - let mut cmd = builder.rustdoc_cmd(compiler); + let mut cmd = builder.rustdoc_cmd(build_compiler); cmd.arg("--html-after-content") .arg(&footer) @@ -509,6 +530,10 @@ impl Step for Releases { builder.open_in_browser(&html); } } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc("releases", self.target).built_by(self.build_compiler)) + } } #[derive(Debug, Clone)] @@ -554,22 +579,29 @@ impl Step for SharedAssets { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +/// Document the standard library using `build_compiler`. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Std { - pub stage: u32, - pub target: TargetSelection, - pub format: DocumentationFormat, + build_compiler: Compiler, + target: TargetSelection, + format: DocumentationFormat, crates: Vec, } impl Std { - pub(crate) fn new(stage: u32, target: TargetSelection, format: DocumentationFormat) -> Self { - Std { stage, target, format, crates: vec![] } + pub(crate) fn from_build_compiler( + build_compiler: Compiler, + target: TargetSelection, + format: DocumentationFormat, + ) -> Self { + Std { build_compiler, target, format, crates: vec![] } } } impl Step for Std { - type Output = (); + /// Path to a directory with the built documentation. + type Output = PathBuf; + const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -584,7 +616,7 @@ impl Step for Std { return; } run.builder.ensure(Std { - stage: run.builder.top_stage, + build_compiler: run.builder.compiler(run.builder.top_stage, run.builder.host_target), target: run.target, format: if run.builder.config.cmd.json() { DocumentationFormat::Json @@ -599,8 +631,7 @@ impl Step for Std { /// /// This will generate all documentation for the standard library and its /// dependencies. This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { - let stage = self.stage; + fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; let crates = if self.crates.is_empty() { builder @@ -642,32 +673,32 @@ impl Step for Std { // For `--index-page` and `--output-format=json`. extra_args.push("-Zunstable-options"); - doc_std(builder, self.format, stage, target, &out, &extra_args, &crates); - - // Don't open if the format is json - if let DocumentationFormat::Json = self.format { - return; - } + doc_std(builder, self.format, self.build_compiler, target, &out, &extra_args, &crates); - if builder.paths.iter().any(|path| path.ends_with("library")) { - // For `x.py doc library --open`, open `std` by default. - let index = out.join("std").join("index.html"); - builder.open_in_browser(index); - } else { - for requested_crate in crates { - if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) { - let index = out.join(requested_crate).join("index.html"); - builder.open_in_browser(index); - break; + // Open if the format is HTML + if let DocumentationFormat::Html = self.format { + if builder.paths.iter().any(|path| path.ends_with("library")) { + // For `x.py doc library --open`, open `std` by default. + let index = out.join("std").join("index.html"); + builder.open_in_browser(index); + } else { + for requested_crate in crates { + if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) { + let index = out.join(requested_crate).join("index.html"); + builder.open_in_browser(index); + break; + } } } } + + out } fn metadata(&self) -> Option { Some( StepMetadata::doc("std", self.target) - .stage(self.stage) + .built_by(self.build_compiler) .with_metadata(format!("crates=[{}]", self.crates.join(","))), ) } @@ -684,7 +715,7 @@ impl Step for Std { /// or remote link. const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"]; -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub enum DocumentationFormat { Html, Json, @@ -703,26 +734,31 @@ impl DocumentationFormat { fn doc_std( builder: &Builder<'_>, format: DocumentationFormat, - stage: u32, + build_compiler: Compiler, target: TargetSelection, out: &Path, extra_args: &[&str], requested_crates: &[String], ) { - let compiler = builder.compiler(stage, builder.config.host_target); - let target_doc_dir_name = if format == DocumentationFormat::Json { "json-doc" } else { "doc" }; - let target_dir = builder.stage_out(compiler, Mode::Std).join(target).join(target_doc_dir_name); + let target_dir = + builder.stage_out(build_compiler, Mode::Std).join(target).join(target_doc_dir_name); // This is directory where the compiler will place the output of the command. // We will then copy the files from this directory into the final `out` directory, the specified // as a function parameter. let out_dir = target_dir.join(target).join("doc"); - let mut cargo = - builder::Cargo::new(builder, compiler, Mode::Std, SourceType::InTree, target, Kind::Doc); + let mut cargo = builder::Cargo::new( + builder, + build_compiler, + Mode::Std, + SourceType::InTree, + target, + Kind::Doc, + ); - compile::std_cargo(builder, target, compiler.stage, &mut cargo); + compile::std_cargo(builder, target, &mut cargo); cargo .arg("--no-deps") .arg("--target-dir") @@ -748,34 +784,53 @@ fn doc_std( let description = format!("library{} in {} format", crate_description(requested_crates), format.as_str()); - let _guard = builder.msg_doc(compiler, description, target); + let _guard = builder.msg(Kind::Doc, description, None, build_compiler, target); cargo.into_cmd().run(builder); builder.cp_link_r(&out_dir, out); } +/// Prepare a compiler that will be able to document something for `target` at `stage`. +fn prepare_doc_compiler(builder: &Builder<'_>, target: TargetSelection, stage: u32) -> Compiler { + assert!(stage > 0, "Cannot document anything in stage 0"); + let build_compiler = builder.compiler(stage - 1, builder.host_target); + builder.std(build_compiler, target); + build_compiler +} + +/// Document the compiler for the given `target` using rustdoc from `build_compiler`. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustc { - pub stage: u32, - pub target: TargetSelection, + build_compiler: Compiler, + target: TargetSelection, crates: Vec, } impl Rustc { - pub(crate) fn new(stage: u32, target: TargetSelection, builder: &Builder<'_>) -> Self { + /// Document `stage` compiler for the given `target`. + pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self { + let build_compiler = prepare_doc_compiler(builder, target, stage); + Self::from_build_compiler(builder, build_compiler, target) + } + + fn from_build_compiler( + builder: &Builder<'_>, + build_compiler: Compiler, + target: TargetSelection, + ) -> Self { let crates = builder .in_tree_crates("rustc-main", Some(target)) .into_iter() .map(|krate| krate.name.to_string()) .collect(); - Self { stage, target, crates } + Self { build_compiler, target, crates } } } impl Step for Rustc { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -785,11 +840,7 @@ impl Step for Rustc { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Rustc { - stage: run.builder.top_stage, - target: run.target, - crates: run.make_run_crates(Alias::Compiler), - }); + run.builder.ensure(Rustc::for_stage(run.builder, run.builder.top_stage, run.target)); } /// Generates compiler documentation. @@ -799,7 +850,6 @@ impl Step for Rustc { /// we do not merge it with the other documentation from std, test and /// proc_macros. This is largely just a wrapper around `cargo doc`. fn run(self, builder: &Builder<'_>) { - let stage = self.stage; let target = self.target; // This is the intended out directory for compiler documentation. @@ -808,21 +858,21 @@ impl Step for Rustc { // Build the standard library, so that proc-macros can use it. // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.) - let compiler = builder.compiler(stage, builder.config.host_target); - builder.std(compiler, builder.config.host_target); + let build_compiler = self.build_compiler; + builder.std(build_compiler, builder.config.host_target); - let _guard = builder.msg_sysroot_tool( + let _guard = builder.msg( Kind::Doc, - stage, format!("compiler{}", crate_description(&self.crates)), - compiler.host, + Mode::Rustc, + build_compiler, target, ); // Build cargo command. let mut cargo = builder::Cargo::new( builder, - compiler, + build_compiler, Mode::Rustc, SourceType::InTree, target, @@ -840,7 +890,7 @@ impl Step for Rustc { // If there is any bug, please comment out the next line. cargo.rustdocflag("--generate-link-to-definition"); - compile::rustc_cargo(builder, &mut cargo, target, &compiler, &self.crates); + compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. @@ -855,7 +905,7 @@ impl Step for Rustc { let mut to_open = None; - let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target).join("doc"); + let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc"); for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses // relative links. @@ -877,7 +927,7 @@ impl Step for Rustc { symlink_dir_force(&builder.config, &out, &out_dir); // Cargo puts proc macros in `target/doc` even if you pass `--target` // explicitly (https://github.com/rust-lang/cargo/issues/7677). - let proc_macro_out_dir = builder.stage_out(compiler, Mode::Rustc).join("doc"); + let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc"); symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); cargo.into_cmd().run(builder); @@ -901,25 +951,31 @@ impl Step for Rustc { builder.open_in_browser(index); } } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc("rustc", self.target).built_by(self.build_compiler)) + } } macro_rules! tool_doc { ( $tool: ident, $path: literal, - $(rustc_tool = $rustc_tool:literal, )? + $(rustc_private_tool = $rustc_private_tool:literal, )? $(is_library = $is_library:expr,)? $(crates = $crates:expr)? ) => { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct $tool { + build_compiler: Compiler, + mode: Mode, target: TargetSelection, } impl Step for $tool { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -927,15 +983,26 @@ macro_rules! tool_doc { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure($tool { target: run.target }); + let target = run.target; + let (build_compiler, mode) = if true $(&& $rustc_private_tool)? { + // Rustdoc needs the rustc sysroot available to build. + let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target); + + // Build rustc docs so that we generate relative links. + run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target)); + + (compilers.build_compiler(), Mode::ToolRustc) + } else { + // bootstrap/host tools have to be documented with the stage 0 compiler + (prepare_doc_compiler(run.builder, target, 1), Mode::ToolBootstrap) + }; + + run.builder.ensure($tool { build_compiler, mode, target }); } - /// Generates compiler documentation. + /// Generates documentation for a tool. /// - /// This will generate all documentation for compiler and dependencies. - /// Compiler documentation is distributed separately, so we make sure - /// we do not merge it with the other documentation from std, test and - /// proc_macros. This is largely just a wrapper around `cargo doc`. + /// This is largely just a wrapper around `cargo doc`. fn run(self, builder: &Builder<'_>) { let mut source_type = SourceType::InTree; @@ -944,31 +1011,17 @@ macro_rules! tool_doc { builder.require_submodule(&submodule_path, None); } - let stage = builder.top_stage; - let target = self.target; + let $tool { build_compiler, mode, target } = self; // This is the intended out directory for compiler documentation. let out = builder.compiler_doc_out(target); t!(fs::create_dir_all(&out)); - let compiler = builder.compiler(stage, builder.config.host_target); - builder.std(compiler, target); - - if true $(&& $rustc_tool)? { - // Build rustc docs so that we generate relative links. - builder.ensure(Rustc::new(stage, target, builder)); - - // Rustdoc needs the rustc sysroot available to build. - // FIXME: is there a way to only ensure `check::Rustc` here? Last time I tried it failed - // with strange errors, but only on a full bors test ... - builder.ensure(compile::Rustc::new(compiler, target)); - } - // Build cargo command. let mut cargo = prepare_tool_cargo( builder, - compiler, - Mode::ToolRustc, + build_compiler, + mode, target, Kind::Doc, $path, @@ -995,7 +1048,7 @@ macro_rules! tool_doc { cargo.rustdocflag("--show-type-layout"); cargo.rustdocflag("--generate-link-to-definition"); - let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc"); + let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc"); $(for krate in $crates { let dir_name = krate.replace("-", "_"); t!(fs::create_dir_all(out_dir.join(&*dir_name))); @@ -1003,10 +1056,10 @@ macro_rules! tool_doc { // Symlink compiler docs to the output directory of rustdoc documentation. symlink_dir_force(&builder.config, &out, &out_dir); - let proc_macro_out_dir = builder.stage_out(compiler, Mode::ToolRustc).join("doc"); + let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc"); symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); - let _guard = builder.msg_doc(compiler, stringify!($tool).to_lowercase(), target); + let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target); cargo.into_cmd().run(builder); if !builder.config.dry_run() { @@ -1018,6 +1071,10 @@ macro_rules! tool_doc { })? } } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc(stringify!($tool), self.target).built_by(self.build_compiler)) + } } } } @@ -1026,7 +1083,7 @@ macro_rules! tool_doc { tool_doc!( BuildHelper, "src/build_helper", - rustc_tool = false, + rustc_private_tool = false, is_library = true, crates = ["build_helper"] ); @@ -1037,7 +1094,7 @@ tool_doc!(Miri, "src/tools/miri", crates = ["miri"]); tool_doc!( Cargo, "src/tools/cargo", - rustc_tool = false, + rustc_private_tool = false, crates = [ "cargo", "cargo-credential", @@ -1051,38 +1108,38 @@ tool_doc!( "rustfix", ] ); -tool_doc!(Tidy, "src/tools/tidy", rustc_tool = false, crates = ["tidy"]); +tool_doc!(Tidy, "src/tools/tidy", rustc_private_tool = false, crates = ["tidy"]); tool_doc!( Bootstrap, "src/bootstrap", - rustc_tool = false, + rustc_private_tool = false, is_library = true, crates = ["bootstrap"] ); tool_doc!( RunMakeSupport, "src/tools/run-make-support", - rustc_tool = false, + rustc_private_tool = false, is_library = true, crates = ["run_make_support"] ); tool_doc!( Compiletest, "src/tools/compiletest", - rustc_tool = false, + rustc_private_tool = false, is_library = true, crates = ["compiletest"] ); -#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct ErrorIndex { - pub target: TargetSelection, + compilers: RustcPrivateCompilers, } impl Step for ErrorIndex { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1090,17 +1147,29 @@ impl Step for ErrorIndex { } fn make_run(run: RunConfig<'_>) { - let target = run.target; - run.builder.ensure(ErrorIndex { target }); + run.builder.ensure(ErrorIndex { + compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target), + }); } /// Generates the HTML rendered error-index by running the /// `error_index_generator` tool. fn run(self, builder: &Builder<'_>) { - builder.info(&format!("Documenting error index ({})", self.target)); - let out = builder.doc_out(self.target); + builder.info(&format!("Documenting error index ({})", self.compilers.target())); + let out = builder.doc_out(self.compilers.target()); t!(fs::create_dir_all(&out)); - tool::ErrorIndex::command(builder).arg("html").arg(out).arg(&builder.version).run(builder); + tool::ErrorIndex::command(builder, self.compilers) + .arg("html") + .arg(out) + .arg(&builder.version) + .run(builder); + } + + fn metadata(&self) -> Option { + Some( + StepMetadata::doc("error-index", self.compilers.target()) + .built_by(self.compilers.build_compiler()), + ) } } @@ -1112,7 +1181,7 @@ pub struct UnstableBookGen { impl Step for UnstableBookGen { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1160,17 +1229,26 @@ fn symlink_dir_force(config: &Config, original: &Path, link: &Path) { ); } -#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)] +/// Builds the Rust compiler book. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RustcBook { - pub compiler: Compiler, - pub target: TargetSelection, - pub validate: bool, + build_compiler: Compiler, + target: TargetSelection, + /// Test that the examples of lints in the book produce the correct lints in the expected + /// format. + validate: bool, +} + +impl RustcBook { + pub fn validate(build_compiler: Compiler, target: TargetSelection) -> Self { + Self { build_compiler, target, validate: true } + } } impl Step for RustcBook { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1178,8 +1256,17 @@ impl Step for RustcBook { } fn make_run(run: RunConfig<'_>) { + // Bump the stage to 2, because the rustc book requires an in-tree compiler. + // At the same time, since this step is enabled by default, we don't want `x doc` to fail + // in stage 1. + let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 { + run.builder.top_stage + } else { + 2 + }; + run.builder.ensure(RustcBook { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: prepare_doc_compiler(run.builder, run.target, stage), target: run.target, validate: false, }); @@ -1197,10 +1284,10 @@ impl Step for RustcBook { builder.cp_link_r(&builder.src.join("src/doc/rustc"), &out_base); builder.info(&format!("Generating lint docs ({})", self.target)); - let rustc = builder.rustc(self.compiler); + let rustc = builder.rustc(self.build_compiler); // The tool runs `rustc` for extracting output examples, so it needs a // functional sysroot. - builder.std(self.compiler, self.target); + builder.std(self.build_compiler, self.target); let mut cmd = builder.tool_cmd(Tool::LintDocs); cmd.arg("--src"); cmd.arg(builder.src.join("compiler")); @@ -1226,14 +1313,9 @@ impl Step for RustcBook { // If the lib directories are in an unusual location (changed in // bootstrap.toml), then this needs to explicitly update the dylib search // path. - builder.add_rustc_lib_path(self.compiler, &mut cmd); - let doc_generator_guard = builder.msg( - Kind::Run, - self.compiler.stage, - "lint-docs", - self.compiler.host, - self.target, - ); + builder.add_rustc_lib_path(self.build_compiler, &mut cmd); + let doc_generator_guard = + builder.msg(Kind::Run, "lint-docs", None, self.build_compiler, self.target); cmd.run(builder); drop(doc_generator_guard); @@ -1244,15 +1326,18 @@ impl Step for RustcBook { src: out_base, parent: Some(self), languages: vec![], - rustdoc_compiler: None, + build_compiler: None, }); } } -#[derive(Ord, PartialOrd, Debug, Clone, Hash, PartialEq, Eq)] +/// Documents the reference. +/// It has to always be done using a stage 1+ compiler, because it references in-tree +/// compiler/stdlib concepts. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Reference { - pub compiler: Compiler, - pub target: TargetSelection, + build_compiler: Compiler, + target: TargetSelection, } impl Step for Reference { @@ -1265,8 +1350,19 @@ impl Step for Reference { } fn make_run(run: RunConfig<'_>) { + // Bump the stage to 2, because the reference requires an in-tree compiler. + // At the same time, since this step is enabled by default, we don't want `x doc` to fail + // in stage 1. + // FIXME: create a shared method on builder for auto-bumping, and print some warning when + // it happens. + let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 { + run.builder.top_stage + } else { + 2 + }; + run.builder.ensure(Reference { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: prepare_doc_compiler(run.builder, run.target, stage), target: run.target, }); } @@ -1277,14 +1373,14 @@ impl Step for Reference { // This is needed for generating links to the standard library using // the mdbook-spec plugin. - builder.std(self.compiler, builder.config.host_target); + builder.std(self.build_compiler, builder.config.host_target); // Run rustbook/mdbook to generate the HTML pages. builder.ensure(RustbookSrc { target: self.target, name: "reference".to_owned(), src: builder.src.join("src/doc/reference"), - rustdoc_compiler: Some(self.compiler), + build_compiler: Some(self.build_compiler), parent: Some(self), languages: vec![], }); diff --git a/src/bootstrap/src/core/build_steps/gcc.rs b/src/bootstrap/src/core/build_steps/gcc.rs index d4cbbe60921f..77c9622a9bf0 100644 --- a/src/bootstrap/src/core/build_steps/gcc.rs +++ b/src/bootstrap/src/core/build_steps/gcc.rs @@ -12,6 +12,7 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::OnceLock; +use crate::FileType; use crate::core::builder::{Builder, Cargo, Kind, RunConfig, ShouldRun, Step}; use crate::core::config::TargetSelection; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; @@ -28,10 +29,25 @@ pub struct GccOutput { pub libgccjit: PathBuf, } +impl GccOutput { + /// Install the required libgccjit library file(s) to the specified `path`. + pub fn install_to(&self, builder: &Builder<'_>, directory: &Path) { + // At build time, cg_gcc has to link to libgccjit.so (the unversioned symbol). + // However, at runtime, it will by default look for libgccjit.so.0. + // So when we install the built libgccjit.so file to the target `directory`, we add it there + // with the `.0` suffix. + let mut target_filename = self.libgccjit.file_name().unwrap().to_str().unwrap().to_string(); + target_filename.push_str(".0"); + + let dst = directory.join(target_filename); + builder.copy_link(&self.libgccjit, &dst, FileType::NativeLibrary); + } +} + impl Step for Gcc { type Output = GccOutput; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/gcc").alias("gcc") @@ -61,7 +77,6 @@ impl Step for Gcc { } build_gcc(&metadata, builder, target); - create_lib_alias(builder, &libgccjit_path); t!(metadata.stamp.write()); @@ -69,15 +84,6 @@ impl Step for Gcc { } } -/// Creates a libgccjit.so.0 alias next to libgccjit.so if it does not -/// already exist -fn create_lib_alias(builder: &Builder<'_>, libgccjit: &PathBuf) { - let lib_alias = libgccjit.parent().unwrap().join("libgccjit.so.0"); - if !lib_alias.exists() { - t!(builder.symlink_file(libgccjit, lib_alias)); - } -} - pub struct Meta { stamp: BuildStamp, out_dir: PathBuf, @@ -116,7 +122,7 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option { // Download from upstream CI - let root = ci_gcc_root(&builder.config); + let root = ci_gcc_root(&builder.config, target); let gcc_stamp = BuildStamp::new(&root).with_prefix("gcc").add_stamp(&upstream); if !gcc_stamp.is_up_to_date() && !builder.config.dry_run() { builder.config.download_ci_gcc(&upstream, &root); @@ -124,7 +130,6 @@ fn try_download_gcc(builder: &Builder<'_>, target: TargetSelection) -> Option { @@ -281,8 +286,8 @@ pub fn add_cg_gcc_cargo_flags(cargo: &mut Cargo, gcc: &GccOutput) { /// The absolute path to the downloaded GCC artifacts. #[cfg(not(test))] -fn ci_gcc_root(config: &crate::Config) -> PathBuf { - config.out.join(config.host_target).join("ci-gcc") +fn ci_gcc_root(config: &crate::Config, target: TargetSelection) -> PathBuf { + config.out.join(target).join("ci-gcc") } /// Detect whether GCC sources have been modified locally or not. diff --git a/src/bootstrap/src/core/build_steps/install.rs b/src/bootstrap/src/core/build_steps/install.rs index 4513a138e192..4457258e9cdd 100644 --- a/src/bootstrap/src/core/build_steps/install.rs +++ b/src/bootstrap/src/core/build_steps/install.rs @@ -12,7 +12,7 @@ use crate::core::config::{Config, TargetSelection}; use crate::utils::exec::command; use crate::utils::helpers::t; use crate::utils::tarball::GeneratedTarball; -use crate::{CodegenBackendKind, Compiler, Kind}; +use crate::{Compiler, Kind}; #[cfg(target_os = "illumos")] const SHELL: &str = "bash"; @@ -68,7 +68,13 @@ fn install_sh( host: Option, tarball: &GeneratedTarball, ) { - let _guard = builder.msg(Kind::Install, stage, package, host, host); + let _guard = builder.msg( + Kind::Install, + package, + None, + (host.unwrap_or(builder.host_target), stage), + host, + ); let prefix = default_path(&builder.config.prefix, "/usr/local"); let sysconfdir = prefix.join(default_path(&builder.config.sysconfdir, "/etc")); @@ -157,7 +163,7 @@ macro_rules! install { $($name:ident, $condition_name: ident = $path_or_alias: literal, $default_cond:expr, - only_hosts: $only_hosts:expr, + IS_HOST: $IS_HOST:expr, $run_item:block $(, $c:ident)*;)+) => { $( #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -177,7 +183,7 @@ macro_rules! install { impl Step for $name { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = $only_hosts; + const IS_HOST: bool = $IS_HOST; $(const $c: bool = true;)* fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -200,11 +206,11 @@ macro_rules! install { } install!((self, builder, _config), - Docs, path = "src/doc", _config.docs, only_hosts: false, { + Docs, path = "src/doc", _config.docs, IS_HOST: false, { let tarball = builder.ensure(dist::Docs { host: self.target }).expect("missing docs"); install_sh(builder, "docs", self.compiler.stage, Some(self.target), &tarball); }; - Std, path = "library/std", true, only_hosts: false, { + Std, path = "library/std", true, IS_HOST: false, { // `expect` should be safe, only None when host != build, but this // only runs when host == build let tarball = builder.ensure(dist::Std { @@ -213,15 +219,15 @@ install!((self, builder, _config), }).expect("missing std"); install_sh(builder, "std", self.compiler.stage, Some(self.target), &tarball); }; - Cargo, alias = "cargo", Self::should_build(_config), only_hosts: true, { + Cargo, alias = "cargo", Self::should_build(_config), IS_HOST: true, { let tarball = builder - .ensure(dist::Cargo { compiler: self.compiler, target: self.target }) + .ensure(dist::Cargo { build_compiler: self.compiler, target: self.target }) .expect("missing cargo"); install_sh(builder, "cargo", self.compiler.stage, Some(self.target), &tarball); }; - RustAnalyzer, alias = "rust-analyzer", Self::should_build(_config), only_hosts: true, { + RustAnalyzer, alias = "rust-analyzer", Self::should_build(_config), IS_HOST: true, { if let Some(tarball) = - builder.ensure(dist::RustAnalyzer { compiler: self.compiler, target: self.target }) + builder.ensure(dist::RustAnalyzer { build_compiler: self.compiler, target: self.target }) { install_sh(builder, "rust-analyzer", self.compiler.stage, Some(self.target), &tarball); } else { @@ -230,14 +236,14 @@ install!((self, builder, _config), ); } }; - Clippy, alias = "clippy", Self::should_build(_config), only_hosts: true, { + Clippy, alias = "clippy", Self::should_build(_config), IS_HOST: true, { let tarball = builder - .ensure(dist::Clippy { compiler: self.compiler, target: self.target }) + .ensure(dist::Clippy { build_compiler: self.compiler, target: self.target }) .expect("missing clippy"); install_sh(builder, "clippy", self.compiler.stage, Some(self.target), &tarball); }; - Miri, alias = "miri", Self::should_build(_config), only_hosts: true, { - if let Some(tarball) = builder.ensure(dist::Miri { compiler: self.compiler, target: self.target }) { + Miri, alias = "miri", Self::should_build(_config), IS_HOST: true, { + if let Some(tarball) = builder.ensure(dist::Miri { build_compiler: self.compiler, target: self.target }) { install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball); } else { // Miri is only available on nightly @@ -246,7 +252,7 @@ install!((self, builder, _config), ); } }; - LlvmTools, alias = "llvm-tools", _config.llvm_tools_enabled && _config.llvm_enabled(_config.host_target), only_hosts: true, { + LlvmTools, alias = "llvm-tools", _config.llvm_tools_enabled && _config.llvm_enabled(_config.host_target), IS_HOST: true, { if let Some(tarball) = builder.ensure(dist::LlvmTools { target: self.target }) { install_sh(builder, "llvm-tools", self.compiler.stage, Some(self.target), &tarball); } else { @@ -255,9 +261,9 @@ install!((self, builder, _config), ); } }; - Rustfmt, alias = "rustfmt", Self::should_build(_config), only_hosts: true, { + Rustfmt, alias = "rustfmt", Self::should_build(_config), IS_HOST: true, { if let Some(tarball) = builder.ensure(dist::Rustfmt { - compiler: self.compiler, + build_compiler: self.compiler, target: self.target }) { install_sh(builder, "rustfmt", self.compiler.stage, Some(self.target), &tarball); @@ -267,16 +273,16 @@ install!((self, builder, _config), ); } }; - Rustc, path = "compiler/rustc", true, only_hosts: true, { + Rustc, path = "compiler/rustc", true, IS_HOST: true, { let tarball = builder.ensure(dist::Rustc { compiler: builder.compiler(builder.top_stage, self.target), }); install_sh(builder, "rustc", self.compiler.stage, Some(self.target), &tarball); }; - RustcCodegenCranelift, alias = "rustc-codegen-cranelift", Self::should_build(_config), only_hosts: true, { - if let Some(tarball) = builder.ensure(dist::CodegenBackend { - compiler: self.compiler, - backend: CodegenBackendKind::Cranelift, + RustcCodegenCranelift, alias = "rustc-codegen-cranelift", Self::should_build(_config), IS_HOST: true, { + if let Some(tarball) = builder.ensure(dist::CraneliftCodegenBackend { + build_compiler: self.compiler, + target: self.target }) { install_sh(builder, "rustc-codegen-cranelift", self.compiler.stage, Some(self.target), &tarball); } else { @@ -286,7 +292,7 @@ install!((self, builder, _config), ); } }; - LlvmBitcodeLinker, alias = "llvm-bitcode-linker", Self::should_build(_config), only_hosts: true, { + LlvmBitcodeLinker, alias = "llvm-bitcode-linker", Self::should_build(_config), IS_HOST: true, { if let Some(tarball) = builder.ensure(dist::LlvmBitcodeLinker { build_compiler: self.compiler, target: self.target }) { install_sh(builder, "llvm-bitcode-linker", self.compiler.stage, Some(self.target), &tarball); } else { @@ -305,7 +311,7 @@ pub struct Src { impl Step for Src { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let config = &run.builder.config; diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index b2056f5cf378..024cac2f2fee 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -15,8 +15,6 @@ use std::sync::OnceLock; use std::{env, fs}; use build_helper::git::PathFreshness; -#[cfg(feature = "tracing")] -use tracing::instrument; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata}; use crate::core::config::{Config, TargetSelection}; @@ -196,7 +194,10 @@ pub(crate) fn detect_llvm_freshness(config: &Config, is_git: bool) -> PathFreshn /// /// This checks the build triple platform to confirm we're usable at all, and if LLVM /// with/without assertions is available. -pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) -> bool { +pub(crate) fn is_ci_llvm_available_for_target( + host_target: &TargetSelection, + asserts: bool, +) -> bool { // This is currently all tier 1 targets and tier 2 targets with host tools // (since others may not have CI artifacts) // https://doc.rust-lang.org/rustc/platform-support.html#tier-1 @@ -219,10 +220,6 @@ pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) -> ("armv7-unknown-linux-gnueabihf", false), ("loongarch64-unknown-linux-gnu", false), ("loongarch64-unknown-linux-musl", false), - ("mips-unknown-linux-gnu", false), - ("mips64-unknown-linux-gnuabi64", false), - ("mips64el-unknown-linux-gnuabi64", false), - ("mipsel-unknown-linux-gnu", false), ("powerpc-unknown-linux-gnu", false), ("powerpc64-unknown-linux-gnu", false), ("powerpc64le-unknown-linux-gnu", false), @@ -235,8 +232,8 @@ pub(crate) fn is_ci_llvm_available_for_target(config: &Config, asserts: bool) -> ("x86_64-unknown-netbsd", false), ]; - if !supported_platforms.contains(&(&*config.host_target.triple, asserts)) - && (asserts || !supported_platforms.contains(&(&*config.host_target.triple, true))) + if !supported_platforms.contains(&(&*host_target.triple, asserts)) + && (asserts || !supported_platforms.contains(&(&*host_target.triple, true))) { return false; } @@ -252,7 +249,7 @@ pub struct Llvm { impl Step for Llvm { type Output = LlvmResult; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/llvm-project").path("src/llvm-project/llvm") @@ -263,15 +260,6 @@ impl Step for Llvm { } /// Compile LLVM for `target`. - #[cfg_attr( - feature = "tracing", - instrument( - level = "debug", - name = "Llvm::run", - skip_all, - fields(target = ?self.target), - ), - )] fn run(self, builder: &Builder<'_>) -> LlvmResult { let target = self.target; let target_native = if self.target.starts_with("riscv") { @@ -421,6 +409,13 @@ impl Step for Llvm { ldflags.shared.push(" -latomic"); } + if target.starts_with("arm64ec") { + // MSVC linker requires the -machine:arm64ec flag to be passed to + // know it's linking as Arm64EC (vs Arm64X). + ldflags.exe.push(" -machine:arm64ec"); + ldflags.shared.push(" -machine:arm64ec"); + } + if target.is_msvc() { cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded"); cfg.static_crt(true); @@ -898,7 +893,7 @@ pub struct Enzyme { impl Step for Enzyme { type Output = PathBuf; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/enzyme/enzyme") @@ -909,15 +904,6 @@ impl Step for Enzyme { } /// Compile Enzyme for `target`. - #[cfg_attr( - feature = "tracing", - instrument( - level = "debug", - name = "Enzyme::run", - skip_all, - fields(target = ?self.target), - ), - )] fn run(self, builder: &Builder<'_>) -> PathBuf { builder.require_submodule( "src/tools/enzyme", @@ -986,6 +972,7 @@ impl Step for Enzyme { .env("LLVM_CONFIG_REAL", &llvm_config) .define("LLVM_ENABLE_ASSERTIONS", "ON") .define("ENZYME_EXTERNAL_SHARED_LIB", "ON") + .define("ENZYME_BC_LOADER", "OFF") .define("LLVM_DIR", builder.llvm_out(target)); cfg.build(); @@ -1002,7 +989,7 @@ pub struct Lld { impl Step for Lld { type Output = PathBuf; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/llvm-project/lld") @@ -1528,8 +1515,12 @@ impl Step for Libunwind { // FIXME: https://github.com/alexcrichton/cc-rs/issues/545#issuecomment-679242845 let mut count = 0; - for entry in fs::read_dir(&out_dir).unwrap() { - let file = entry.unwrap().path().canonicalize().unwrap(); + let mut files = fs::read_dir(&out_dir) + .unwrap() + .map(|entry| entry.unwrap().path().canonicalize().unwrap()) + .collect::>(); + files.sort(); + for file in files { if file.is_file() && file.extension() == Some(OsStr::new("o")) { // Object file name without the hash prefix is "Unwind-EHABI", "Unwind-seh" or "libunwind". let base_name = unhashed_basename(&file); diff --git a/src/bootstrap/src/core/build_steps/perf.rs b/src/bootstrap/src/core/build_steps/perf.rs index 4d61b38c876d..108b7f90c149 100644 --- a/src/bootstrap/src/core/build_steps/perf.rs +++ b/src/bootstrap/src/core/build_steps/perf.rs @@ -157,7 +157,7 @@ Consider setting `rust.debuginfo-level = 1` in `bootstrap.toml`."#); if let Some(opts) = args.cmd.shared_opts() && opts.profiles.contains(&Profile::Doc) { - builder.ensure(Rustdoc { compiler }); + builder.ensure(Rustdoc { target_compiler: compiler }); } let sysroot = builder.ensure(Sysroot::new(compiler)); diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index b2293fdd9b52..c6288f638471 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -9,7 +9,7 @@ use clap_complete::{Generator, shells}; use crate::core::build_steps::dist::distdir; use crate::core::build_steps::test; -use crate::core::build_steps::tool::{self, SourceType, Tool}; +use crate::core::build_steps::tool::{self, RustcPrivateCompilers, SourceType, Tool}; use crate::core::build_steps::vendor::{Vendor, default_paths_to_vendor}; use crate::core::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; use crate::core::config::TargetSelection; @@ -17,12 +17,12 @@ use crate::core::config::flags::get_completion; use crate::utils::exec::command; use crate::{Mode, t}; -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct BuildManifest; impl Step for BuildManifest { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/build-manifest") @@ -56,12 +56,12 @@ impl Step for BuildManifest { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct BumpStage0; impl Step for BumpStage0 { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/bump-stage0") @@ -78,12 +78,12 @@ impl Step for BumpStage0 { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct ReplaceVersionPlaceholder; impl Step for ReplaceVersionPlaceholder { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/replace-version-placeholder") @@ -107,7 +107,6 @@ pub struct Miri { impl Step for Miri { type Output = (); - const ONLY_HOSTS: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/miri") @@ -135,13 +134,13 @@ impl Step for Miri { } // This compiler runs on the host, we'll just use it for the target. - let target_compiler = builder.compiler(stage, target); - let miri_build = builder.ensure(tool::Miri { compiler: target_compiler, target }); - // Rustc tools are off by one stage, so use the build compiler to run miri. + let compilers = RustcPrivateCompilers::new(builder, stage, target); + let miri_build = builder.ensure(tool::Miri::from_compilers(compilers)); let host_compiler = miri_build.build_compiler; // Get a target sysroot for Miri. - let miri_sysroot = test::Miri::build_miri_sysroot(builder, target_compiler, target); + let miri_sysroot = + test::Miri::build_miri_sysroot(builder, compilers.target_compiler(), target); // # Run miri. // Running it via `cargo run` as that figures out the right dylib path. @@ -170,12 +169,12 @@ impl Step for Miri { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct CollectLicenseMetadata; impl Step for CollectLicenseMetadata { type Output = PathBuf; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/collect-license-metadata") @@ -201,12 +200,12 @@ impl Step for CollectLicenseMetadata { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct GenerateCopyright; impl Step for GenerateCopyright { type Output = Vec; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/generate-copyright") @@ -265,12 +264,12 @@ impl Step for GenerateCopyright { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct GenerateWindowsSys; impl Step for GenerateWindowsSys { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/generate-windows-sys") @@ -327,12 +326,12 @@ impl Step for GenerateCompletions { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct UnicodeTableGenerator; impl Step for UnicodeTableGenerator { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/unicode-table-generator") @@ -349,12 +348,12 @@ impl Step for UnicodeTableGenerator { } } -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct FeaturesStatusDump; impl Step for FeaturesStatusDump { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/features-status-dump") @@ -409,14 +408,14 @@ impl Step for CyclicStep { /// /// The coverage-dump tool is an internal detail of coverage tests, so this run /// step is only needed when testing coverage-dump manually. -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct CoverageDump; impl Step for CoverageDump { type Output = (); const DEFAULT: bool = false; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/coverage-dump") @@ -438,7 +437,7 @@ pub struct Rustfmt; impl Step for Rustfmt { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/rustfmt") @@ -465,8 +464,8 @@ impl Step for Rustfmt { std::process::exit(1); } - let compiler = builder.compiler(stage, host); - let rustfmt_build = builder.ensure(tool::Rustfmt { compiler, target: host }); + let compilers = RustcPrivateCompilers::new(builder, stage, host); + let rustfmt_build = builder.ensure(tool::Rustfmt::from_compilers(compilers)); let mut rustfmt = tool::prepare_tool_cargo( builder, diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 477ff9553a48..21733c5d9e3f 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -21,7 +21,6 @@ pub(crate) struct MirOptPanicAbortSyntheticTarget { impl Step for MirOptPanicAbortSyntheticTarget { type Output = TargetSelection; const DEFAULT: bool = true; - const ONLY_HOSTS: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.never() diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 119fa4237bcf..56e7582a6ffd 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4,12 +4,12 @@ //! However, this contains ~all test parts we expect people to be able to build and run locally. use std::collections::HashSet; +use std::env::split_paths; use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::{env, fs, iter}; -#[cfg(feature = "tracing")] -use tracing::instrument; +use build_helper::exit; use crate::core::build_steps::compile::{Std, run_cargo}; use crate::core::build_steps::doc::DocumentationFormat; @@ -17,7 +17,10 @@ use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::get_completion_paths; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; -use crate::core::build_steps::tool::{self, COMPILETEST_ALLOW_FEATURES, SourceType, Tool}; +use crate::core::build_steps::tool::{ + self, COMPILETEST_ALLOW_FEATURES, RustcPrivateCompilers, SourceType, Tool, ToolTargetBuildMode, + get_tool_target_compiler, +}; use crate::core::build_steps::toolstate::ToolState; use crate::core::build_steps::{compile, dist, llvm}; use crate::core::builder::{ @@ -29,8 +32,8 @@ use crate::core::config::flags::{Subcommand, get_completion}; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::exec::{BootstrapCommand, command}; use crate::utils::helpers::{ - self, LldThreads, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var, linker_args, - linker_flags, t, target_supports_cranelift_backend, up_to_date, + self, LldThreads, add_dylib_path, add_rustdoc_cargo_linker_args, dylib_path, dylib_path_var, + linker_args, linker_flags, t, target_supports_cranelift_backend, up_to_date, }; use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; use crate::{CLang, CodegenBackendKind, DocTests, GitRepo, Mode, PathSet, debug, envify}; @@ -46,7 +49,7 @@ pub struct CrateBootstrap { impl Step for CrateBootstrap { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -104,7 +107,7 @@ pub struct Linkcheck { impl Step for Linkcheck { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = true; /// Runs the `linkchecker` tool as compiled in `stage` by the `host` compiler. @@ -156,8 +159,7 @@ You can skip linkcheck with --skip src/tools/linkchecker" let linkchecker = builder.tool_cmd(Tool::Linkchecker); // Run the linkchecker. - let _guard = - builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host); + let _guard = builder.msg(Kind::Test, "Linkcheck", None, compiler, bootstrap_host); let _time = helpers::timeit(builder); linkchecker.delay_failure().arg(builder.out.join(host).join("doc")).run(builder); } @@ -185,7 +187,7 @@ pub struct HtmlCheck { impl Step for HtmlCheck { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -207,10 +209,10 @@ impl Step for HtmlCheck { } // Ensure that a few different kinds of documentation are available. builder.default_doc(&[]); - builder.ensure(crate::core::build_steps::doc::Rustc::new( + builder.ensure(crate::core::build_steps::doc::Rustc::for_stage( + builder, builder.top_stage, self.target, - builder, )); builder @@ -226,20 +228,32 @@ impl Step for HtmlCheck { /// order to test cargo. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Cargotest { - stage: u32, + build_compiler: Compiler, host: TargetSelection, } impl Step for Cargotest { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/cargotest") } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Cargotest { stage: run.builder.top_stage, host: run.target }); + if run.builder.top_stage == 0 { + eprintln!( + "ERROR: running cargotest with stage 0 is currently unsupported. Use at least stage 1." + ); + exit!(1); + } + // We want to build cargo stage N (where N == top_stage), and rustc stage N, + // and test both of these together. + // So we need to get a build compiler stage N-1 to build the stage N components. + run.builder.ensure(Cargotest { + build_compiler: run.builder.compiler(run.builder.top_stage - 1, run.target), + host: run.target, + }); } /// Runs the `cargotest` tool as compiled in `stage` by the `host` compiler. @@ -247,9 +261,19 @@ impl Step for Cargotest { /// This tool in `src/tools` will check out a few Rust projects and run `cargo /// test` to ensure that we don't regress the test suites there. fn run(self, builder: &Builder<'_>) { - let compiler = builder.compiler(self.stage, self.host); - builder.ensure(compile::Rustc::new(compiler, compiler.host)); - let cargo = builder.ensure(tool::Cargo { compiler, target: compiler.host }); + // cargotest's staging has several pieces: + // consider ./x test cargotest --stage=2. + // + // The test goal is to exercise a (stage 2 cargo, stage 2 rustc) pair through a stage 2 + // cargotest tool. + // To produce the stage 2 cargo and cargotest, we need to do so with the stage 1 rustc and std. + // Importantly, the stage 2 rustc being tested (`tested_compiler`) via stage 2 cargotest is + // the rustc built by an earlier stage 1 rustc (the build_compiler). These are two different + // compilers! + let cargo = + builder.ensure(tool::Cargo::from_build_compiler(self.build_compiler, self.host)); + let tested_compiler = builder.compiler(self.build_compiler.stage + 1, self.host); + builder.std(tested_compiler, self.host); // Note that this is a short, cryptic, and not scoped directory name. This // is currently to minimize the length of path on Windows where we otherwise @@ -262,23 +286,22 @@ impl Step for Cargotest { cmd.arg(&cargo.tool_path) .arg(&out_dir) .args(builder.config.test_args()) - .env("RUSTC", builder.rustc(compiler)) - .env("RUSTDOC", builder.rustdoc(compiler)); - add_rustdoc_cargo_linker_args( - &mut cmd, - builder, - compiler.host, - LldThreads::No, - compiler.stage, - ); + .env("RUSTC", builder.rustc(tested_compiler)) + .env("RUSTDOC", builder.rustdoc_for_compiler(tested_compiler)); + add_rustdoc_cargo_linker_args(&mut cmd, builder, tested_compiler.host, LldThreads::No); cmd.delay_failure().run(builder); } + + fn metadata(&self) -> Option { + Some(StepMetadata::test("cargotest", self.host).stage(self.build_compiler.stage + 1)) + } } /// Runs `cargo test` for cargo itself. +/// We label these tests as "cargo self-tests". #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Cargo { - stage: u32, + build_compiler: Compiler, host: TargetSelection, } @@ -288,42 +311,40 @@ impl Cargo { impl Step for Cargo { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path(Self::CRATE_PATH) } fn make_run(run: RunConfig<'_>) { - // If stage is explicitly set or not lower than 2, keep it. Otherwise, make sure it's at least 2 - // as tests for this step don't work with a lower stage. - let stage = if run.builder.config.is_explicit_stage() || run.builder.top_stage >= 2 { - run.builder.top_stage - } else { - 2 - }; - - run.builder.ensure(Cargo { stage, host: run.target }); + run.builder.ensure(Cargo { + build_compiler: get_tool_target_compiler( + run.builder, + ToolTargetBuildMode::Build(run.target), + ), + host: run.target, + }); } /// Runs `cargo test` for `cargo` packaged with Rust. fn run(self, builder: &Builder<'_>) { - let stage = self.stage; - - if stage < 2 { - eprintln!("WARNING: cargo tests on stage {stage} may not behave well."); - eprintln!("HELP: consider using stage 2"); - } - - let compiler = builder.compiler(stage, self.host); - - let cargo = builder.ensure(tool::Cargo { compiler, target: self.host }); - let compiler = cargo.build_compiler; + // When we do a "stage 1 cargo self-test", it means that we test the stage 1 rustc + // using stage 1 cargo. So we actually build cargo using the stage 0 compiler, and then + // run its tests against the stage 1 compiler (called `tested_compiler` below). + builder.ensure(tool::Cargo::from_build_compiler(self.build_compiler, self.host)); + + let tested_compiler = builder.compiler(self.build_compiler.stage + 1, self.host); + builder.std(tested_compiler, self.host); + // We also need to build rustdoc for cargo tests + // It will be located in the bindir of `tested_compiler`, so we don't need to explicitly + // pass its path to Cargo. + builder.rustdoc_for_compiler(tested_compiler); let cargo = tool::prepare_tool_cargo( builder, - compiler, - Mode::ToolRustc, + self.build_compiler, + Mode::ToolTarget, self.host, Kind::Test, Self::CRATE_PATH, @@ -340,7 +361,25 @@ impl Step for Cargo { // Forcibly disable tests using nightly features since any changes to // those features won't be able to land. cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1"); - cargo.env("PATH", path_for_cargo(builder, compiler)); + + // Configure PATH to find the right rustc. NB. we have to use PATH + // and not RUSTC because the Cargo test suite has tests that will + // fail if rustc is not spelled `rustc`. + cargo.env("PATH", bin_path_for_cargo(builder, tested_compiler)); + + // The `cargo` command configured above has dylib dir path set to the `build_compiler`'s + // libdir. That causes issues in cargo test, because the programs that cargo compiles are + // incorrectly picking that libdir, even though they should be picking the + // `tested_compiler`'s libdir. We thus have to override the precedence here. + let mut existing_dylib_paths = cargo + .get_envs() + .find(|(k, _)| *k == OsStr::new(dylib_path_var())) + .and_then(|(_, v)| v) + .map(|value| split_paths(value).collect::>()) + .unwrap_or_default(); + existing_dylib_paths.insert(0, builder.rustc_libdir(tested_compiler)); + add_dylib_path(existing_dylib_paths, &mut cargo); + // Cargo's test suite uses `CARGO_RUSTC_CURRENT_DIR` to determine the path that `file!` is // relative to. Cargo no longer sets this env var, so we have to do that. This has to be the // same value as `-Zroot-dir`. @@ -352,7 +391,7 @@ impl Step for Cargo { crates: vec!["cargo".into()], target: self.host.triple.to_string(), host: self.host.triple.to_string(), - stage, + stage: self.build_compiler.stage + 1, }, builder, ); @@ -364,13 +403,12 @@ impl Step for Cargo { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RustAnalyzer { - stage: u32, - host: TargetSelection, + compilers: RustcPrivateCompilers, } impl Step for RustAnalyzer { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -378,19 +416,18 @@ impl Step for RustAnalyzer { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Self { stage: run.builder.top_stage, host: run.target }); + run.builder.ensure(Self { + compilers: RustcPrivateCompilers::new( + run.builder, + run.builder.top_stage, + run.builder.host_target, + ), + }); } /// Runs `cargo test` for rust-analyzer fn run(self, builder: &Builder<'_>) { - let stage = self.stage; - let host = self.host; - let compiler = builder.compiler(stage, host); - let compiler = tool::get_tool_rustc_compiler(builder, compiler); - - // We don't need to build the whole Rust Analyzer for the proc-macro-srv test suite, - // but we do need the standard library to be present. - builder.ensure(compile::Rustc::new(compiler, host)); + let host = self.compilers.target(); let workspace_path = "src/tools/rust-analyzer"; // until the whole RA test suite runs on `i686`, we only run @@ -398,7 +435,7 @@ impl Step for RustAnalyzer { let crate_path = "src/tools/rust-analyzer/crates/proc-macro-srv"; let mut cargo = tool::prepare_tool_cargo( builder, - compiler, + self.compilers.build_compiler(), Mode::ToolRustc, host, Kind::Test, @@ -425,49 +462,51 @@ impl Step for RustAnalyzer { /// Runs `cargo test` for rustfmt. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustfmt { - stage: u32, - host: TargetSelection, + compilers: RustcPrivateCompilers, } impl Step for Rustfmt { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/rustfmt") } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Rustfmt { stage: run.builder.top_stage, host: run.target }); + run.builder.ensure(Rustfmt { + compilers: RustcPrivateCompilers::new( + run.builder, + run.builder.top_stage, + run.builder.host_target, + ), + }); } /// Runs `cargo test` for rustfmt. fn run(self, builder: &Builder<'_>) { - let stage = self.stage; - let host = self.host; - let compiler = builder.compiler(stage, host); - - let tool_result = builder.ensure(tool::Rustfmt { compiler, target: self.host }); - let compiler = tool_result.build_compiler; + let tool_result = builder.ensure(tool::Rustfmt::from_compilers(self.compilers)); + let build_compiler = tool_result.build_compiler; + let target = self.compilers.target(); let mut cargo = tool::prepare_tool_cargo( builder, - compiler, + build_compiler, Mode::ToolRustc, - host, + target, Kind::Test, "src/tools/rustfmt", SourceType::InTree, &[], ); - let dir = testdir(builder, compiler.host); + let dir = testdir(builder, target); t!(fs::create_dir_all(&dir)); cargo.env("RUSTFMT_TEST_DIR", dir); cargo.add_rustc_lib_path(builder); - run_cargo_test(cargo, &[], &[], "rustfmt", host, builder); + run_cargo_test(cargo, &[], &[], "rustfmt", target, builder); } } @@ -499,8 +538,7 @@ impl Miri { cargo.env("MIRI_SYSROOT", &miri_sysroot); let mut cargo = BootstrapCommand::from(cargo); - let _guard = - builder.msg(Kind::Build, compiler.stage, "miri sysroot", compiler.host, target); + let _guard = builder.msg(Kind::Build, "miri sysroot", Mode::ToolRustc, compiler, target); cargo.run(builder); // # Determine where Miri put its sysroot. @@ -521,7 +559,6 @@ impl Miri { impl Step for Miri { type Output = (); - const ONLY_HOSTS: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/miri") @@ -542,12 +579,14 @@ impl Step for Miri { } // This compiler runs on the host, we'll just use it for the target. - let target_compiler = builder.compiler(stage, host); + let compilers = RustcPrivateCompilers::new(builder, stage, host); // Build our tools. - let miri = builder.ensure(tool::Miri { compiler: target_compiler, target: host }); + let miri = builder.ensure(tool::Miri::from_compilers(compilers)); // the ui tests also assume cargo-miri has been built - builder.ensure(tool::CargoMiri { compiler: target_compiler, target: host }); + builder.ensure(tool::CargoMiri::from_compilers(compilers)); + + let target_compiler = compilers.target_compiler(); // We also need sysroots, for Miri and for the host (the latter for build scripts). // This is for the tests so everything is done with the target compiler. @@ -599,7 +638,8 @@ impl Step for Miri { cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg()); { - let _guard = builder.msg_sysroot_tool(Kind::Test, stage, "miri", host, target); + let _guard = + builder.msg(Kind::Test, "miri", Mode::ToolRustc, miri.build_compiler, target); let _time = helpers::timeit(builder); cargo.run(builder); } @@ -615,11 +655,11 @@ impl Step for Miri { cargo.args(["tests/pass", "tests/panic"]); { - let _guard = builder.msg_sysroot_tool( + let _guard = builder.msg( Kind::Test, - stage, "miri (mir-opt-level 4)", - host, + Mode::ToolRustc, + miri.build_compiler, target, ); let _time = helpers::timeit(builder); @@ -638,7 +678,6 @@ pub struct CargoMiri { impl Step for CargoMiri { type Output = (); - const ONLY_HOSTS: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/miri/cargo-miri") @@ -659,7 +698,7 @@ impl Step for CargoMiri { } // This compiler runs on the host, we'll just use it for the target. - let compiler = builder.compiler(stage, host); + let build_compiler = builder.compiler(stage, host); // Run `cargo miri test`. // This is just a smoke test (Miri's own CI invokes this in a bunch of different ways and ensures @@ -667,7 +706,7 @@ impl Step for CargoMiri { // itself executes properly under Miri, and that all the logic in `cargo-miri` does not explode. let mut cargo = tool::prepare_tool_cargo( builder, - compiler, + build_compiler, Mode::ToolStd, // it's unclear what to use here, we're not building anything just doing a smoke test! target, Kind::MiriTest, @@ -692,7 +731,8 @@ impl Step for CargoMiri { // Finally, run everything. let mut cargo = BootstrapCommand::from(cargo); { - let _guard = builder.msg_sysroot_tool(Kind::Test, stage, "cargo-miri", host, target); + let _guard = + builder.msg(Kind::Test, "cargo-miri", Mode::ToolRustc, (host, stage), target); let _time = helpers::timeit(builder); cargo.run(builder); } @@ -716,10 +756,6 @@ impl Step for CompiletestTest { } /// Runs `cargo test` for compiletest. - #[cfg_attr( - feature = "tracing", - instrument(level = "debug", name = "CompiletestTest::run", skip_all) - )] fn run(self, builder: &Builder<'_>) { let host = self.host; @@ -749,6 +785,12 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the SourceType::InTree, &[], ); + + // Used for `compiletest` self-tests to have the path to the *staged* compiler. Getting this + // right is important, as `compiletest` is intended to only support one target spec JSON + // format, namely that of the staged compiler. + cargo.env("TEST_RUSTC", builder.rustc(compiler)); + cargo.allow_features(COMPILETEST_ALLOW_FEATURES); run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder); } @@ -756,12 +798,12 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Clippy { - host: TargetSelection, + compilers: RustcPrivateCompilers, } impl Step for Clippy { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -769,38 +811,46 @@ impl Step for Clippy { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Clippy { host: run.target }); + run.builder.ensure(Clippy { + compilers: RustcPrivateCompilers::new( + run.builder, + run.builder.top_stage, + run.builder.host_target, + ), + }); } /// Runs `cargo test` for clippy. fn run(self, builder: &Builder<'_>) { - let stage = builder.top_stage; - let host = self.host; + let target = self.compilers.target(); + // We need to carefully distinguish the compiler that builds clippy, and the compiler // that is linked into the clippy being tested. `target_compiler` is the latter, // and it must also be used by clippy's test runner to build tests and their dependencies. - let target_compiler = builder.compiler(stage, host); + let compilers = self.compilers; + let target_compiler = compilers.target_compiler(); - let tool_result = builder.ensure(tool::Clippy { compiler: target_compiler, target: host }); - let tool_compiler = tool_result.build_compiler; + let tool_result = builder.ensure(tool::Clippy::from_compilers(compilers)); + let build_compiler = tool_result.build_compiler; let mut cargo = tool::prepare_tool_cargo( builder, - tool_compiler, + build_compiler, Mode::ToolRustc, - host, + target, Kind::Test, "src/tools/clippy", SourceType::InTree, &[], ); - cargo.env("RUSTC_TEST_SUITE", builder.rustc(tool_compiler)); - cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(tool_compiler)); - let host_libs = builder.stage_out(tool_compiler, Mode::ToolRustc).join(builder.cargo_dir()); + cargo.env("RUSTC_TEST_SUITE", builder.rustc(build_compiler)); + cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(build_compiler)); + let host_libs = + builder.stage_out(build_compiler, Mode::ToolRustc).join(builder.cargo_dir()); cargo.env("HOST_LIBS", host_libs); // Build the standard library that the tests can use. - builder.std(target_compiler, host); + builder.std(target_compiler, target); cargo.env("TEST_SYSROOT", builder.sysroot(target_compiler)); cargo.env("TEST_RUSTC", builder.rustc(target_compiler)); cargo.env("TEST_RUSTC_LIB", builder.rustc_libdir(target_compiler)); @@ -823,10 +873,9 @@ impl Step for Clippy { } cargo.add_rustc_lib_path(builder); - let cargo = prepare_cargo_test(cargo, &[], &[], host, builder); + let cargo = prepare_cargo_test(cargo, &[], &[], target, builder); - let _guard = - builder.msg_sysroot_tool(Kind::Test, tool_compiler.stage, "clippy", host, host); + let _guard = builder.msg(Kind::Test, "clippy", Mode::ToolRustc, build_compiler, target); // Clippy reports errors if it blessed the outputs if cargo.allow_failure().run(builder) { @@ -840,10 +889,7 @@ impl Step for Clippy { } } -fn path_for_cargo(builder: &Builder<'_>, compiler: Compiler) -> OsString { - // Configure PATH to find the right rustc. NB. we have to use PATH - // and not RUSTC because the Cargo test suite has tests that will - // fail if rustc is not spelled `rustc`. +fn bin_path_for_cargo(builder: &Builder<'_>, compiler: Compiler) -> OsString { let path = builder.sysroot(compiler).join("bin"); let old_path = env::var_os("PATH").unwrap_or_default(); env::join_paths(iter::once(path).chain(env::split_paths(&old_path))).expect("") @@ -857,7 +903,7 @@ pub struct RustdocTheme { impl Step for RustdocTheme { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/rustdoc-themes") @@ -878,9 +924,9 @@ impl Step for RustdocTheme { .env("RUSTC_SYSROOT", builder.sysroot(self.compiler)) .env("RUSTDOC_LIBDIR", builder.sysroot_target_libdir(self.compiler, self.compiler.host)) .env("CFG_RELEASE_CHANNEL", &builder.config.channel) - .env("RUSTDOC_REAL", builder.rustdoc(self.compiler)) + .env("RUSTDOC_REAL", builder.rustdoc_for_compiler(self.compiler)) .env("RUSTC_BOOTSTRAP", "1"); - cmd.args(linker_args(builder, self.compiler.host, LldThreads::No, self.compiler.stage)); + cmd.args(linker_args(builder, self.compiler.host, LldThreads::No)); cmd.delay_failure().run(builder); } @@ -894,7 +940,7 @@ pub struct RustdocJSStd { impl Step for RustdocJSStd { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let default = run.builder.config.nodejs.is_some(); @@ -929,16 +975,16 @@ impl Step for RustdocJSStd { command.arg("--test-file").arg(path); } } - builder.ensure(crate::core::build_steps::doc::Std::new( - builder.top_stage, + builder.ensure(crate::core::build_steps::doc::Std::from_build_compiler( + builder.compiler(builder.top_stage, builder.host_target), self.target, DocumentationFormat::Html, )); let _guard = builder.msg( Kind::Test, - builder.top_stage, "rustdoc-js-std", - builder.config.host_target, + None, + (builder.config.host_target, builder.top_stage), self.target, ); command.run(builder); @@ -954,7 +1000,7 @@ pub struct RustdocJSNotStd { impl Step for RustdocJSNotStd { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let default = run.builder.config.nodejs.is_some(); @@ -1009,7 +1055,7 @@ pub struct RustdocGUI { impl Step for RustdocGUI { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1037,7 +1083,11 @@ impl Step for RustdocGUI { let mut cmd = builder.tool_cmd(Tool::RustdocGUITest); let out_dir = builder.test_out(self.target).join("rustdoc-gui"); - build_stamp::clear_if_dirty(builder, &out_dir, &builder.rustdoc(self.compiler)); + build_stamp::clear_if_dirty( + builder, + &out_dir, + &builder.rustdoc_for_compiler(self.compiler), + ); if let Some(src) = builder.config.src.to_str() { cmd.arg("--rust-src").arg(src); @@ -1053,16 +1103,10 @@ impl Step for RustdocGUI { cmd.arg("--jobs").arg(builder.jobs().to_string()); - cmd.env("RUSTDOC", builder.rustdoc(self.compiler)) + cmd.env("RUSTDOC", builder.rustdoc_for_compiler(self.compiler)) .env("RUSTC", builder.rustc(self.compiler)); - add_rustdoc_cargo_linker_args( - &mut cmd, - builder, - self.compiler.host, - LldThreads::No, - self.compiler.stage, - ); + add_rustdoc_cargo_linker_args(&mut cmd, builder, self.compiler.host, LldThreads::No); for path in &builder.paths { if let Some(p) = helpers::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) { @@ -1089,13 +1133,7 @@ impl Step for RustdocGUI { } let _time = helpers::timeit(builder); - let _guard = builder.msg_sysroot_tool( - Kind::Test, - self.compiler.stage, - "rustdoc-gui", - self.compiler.host, - self.target, - ); + let _guard = builder.msg(Kind::Test, "rustdoc-gui", None, self.compiler, self.target); try_run_tests(builder, &mut cmd, true); } } @@ -1110,7 +1148,7 @@ pub struct Tidy; impl Step for Tidy { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; /// Runs the `tidy` tool. /// @@ -1228,7 +1266,7 @@ macro_rules! test { mode: $mode:expr, suite: $suite:expr, default: $default:expr - $( , only_hosts: $only_hosts:expr )? // default: false + $( , IS_HOST: $IS_HOST:expr )? // default: false $( , compare_mode: $compare_mode:expr )? // default: None $( , )? // optional trailing comma } @@ -1243,10 +1281,10 @@ macro_rules! test { impl Step for $name { type Output = (); const DEFAULT: bool = $default; - const ONLY_HOSTS: bool = (const { + const IS_HOST: bool = (const { #[allow(unused_assignments, unused_mut)] let mut value = false; - $( value = $only_hosts; )? + $( value = $IS_HOST; )? value }); @@ -1294,7 +1332,7 @@ pub struct CrateRunMakeSupport { impl Step for CrateRunMakeSupport { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/run-make-support") @@ -1331,7 +1369,7 @@ pub struct CrateBuildHelper { impl Step for CrateBuildHelper { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/build_helper") @@ -1399,7 +1437,7 @@ test!(UiFullDeps { mode: "ui", suite: "ui-fulldeps", default: true, - only_hosts: true, + IS_HOST: true, }); test!(Rustdoc { @@ -1407,14 +1445,14 @@ test!(Rustdoc { mode: "rustdoc", suite: "rustdoc", default: true, - only_hosts: true, + IS_HOST: true, }); test!(RustdocUi { path: "tests/rustdoc-ui", mode: "ui", suite: "rustdoc-ui", default: true, - only_hosts: true, + IS_HOST: true, }); test!(RustdocJson { @@ -1422,7 +1460,7 @@ test!(RustdocJson { mode: "rustdoc-json", suite: "rustdoc-json", default: true, - only_hosts: true, + IS_HOST: true, }); test!(Pretty { @@ -1430,7 +1468,7 @@ test!(Pretty { mode: "pretty", suite: "pretty", default: true, - only_hosts: true, + IS_HOST: true, }); test!(RunMake { path: "tests/run-make", mode: "run-make", suite: "run-make", default: true }); @@ -1461,7 +1499,7 @@ impl Step for Coverage { type Output = (); const DEFAULT: bool = true; /// Compiletest will automatically skip the "coverage-run" tests if necessary. - const ONLY_HOSTS: bool = false; + const IS_HOST: bool = false; fn should_run(mut run: ShouldRun<'_>) -> ShouldRun<'_> { // Support various invocation styles, including: @@ -1540,7 +1578,7 @@ test!(CoverageRunRustdoc { mode: "coverage-run", suite: "coverage-run-rustdoc", default: true, - only_hosts: true, + IS_HOST: true, }); // For the mir-opt suite we do not use macros, as we need custom behavior when blessing. @@ -1553,7 +1591,6 @@ pub struct MirOpt { impl Step for MirOpt { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.suite_path("tests/mir-opt") @@ -1657,7 +1694,11 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // bootstrap compiler. // NOTE: Only stage 1 is special cased because we need the rustc_private artifacts to match the // running compiler in stage 2 when plugins run. + let query_compiler; let (stage, stage_id) = if suite == "ui-fulldeps" && compiler.stage == 1 { + // Even when using the stage 0 compiler, we also need to provide the stage 1 compiler + // so that compiletest can query it for target information. + query_compiler = Some(compiler); // At stage 0 (stage - 1) we are using the stage0 compiler. Using `self.target` can lead // finding an incorrect compiler path on cross-targets, as the stage 0 is always equal to // `build.build` in the configuration. @@ -1666,6 +1707,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let test_stage = compiler.stage + 1; (test_stage, format!("stage{test_stage}-{build}")) } else { + query_compiler = None; let stage = compiler.stage; (stage, format!("stage{stage}-{target}")) }; @@ -1710,6 +1752,9 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--compile-lib-path").arg(builder.rustc_libdir(compiler)); cmd.arg("--run-lib-path").arg(builder.sysroot_target_libdir(compiler, target)); cmd.arg("--rustc-path").arg(builder.rustc(compiler)); + if let Some(query_compiler) = query_compiler { + cmd.arg("--query-rustc-path").arg(builder.rustc(query_compiler)); + } // Minicore auxiliary lib for `no_core` tests that need `core` stubs in cross-compilation // scenarios. @@ -1723,7 +1768,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // If we're using `--stage 0`, we should provide the bootstrap cargo. builder.initial_cargo.clone() } else { - builder.ensure(tool::Cargo { compiler, target: compiler.host }).tool_path + builder.ensure(tool::Cargo::from_build_compiler(compiler, compiler.host)).tool_path }; cmd.arg("--cargo-path").arg(cargo_path); @@ -1742,7 +1787,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the || mode == "rustdoc-json" || suite == "coverage-run-rustdoc" { - cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler)); + cmd.arg("--rustdoc-path").arg(builder.rustdoc_for_compiler(compiler)); } if mode == "rustdoc-json" { @@ -1785,10 +1830,27 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--host").arg(&*compiler.host.triple); cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.host_target)); - if let Some(codegen_backend) = builder.config.default_codegen_backend(compiler.host) { - // Tells compiletest which codegen backend is used by default by the compiler. + if let Some(codegen_backend) = builder.config.cmd.test_codegen_backend() { + if !builder.config.enabled_codegen_backends(compiler.host).contains(codegen_backend) { + eprintln!( + "\ +ERROR: No configured backend named `{name}` +HELP: You can add it into `bootstrap.toml` in `rust.codegen-backends = [{name:?}]`", + name = codegen_backend.name(), + ); + crate::exit!(1); + } + // Tells compiletest that we want to use this codegen in particular and to override + // the default one. + cmd.arg("--override-codegen-backend").arg(codegen_backend.name()); + // Tells compiletest which codegen backend to use. // It is used to e.g. ignore tests that don't support that codegen backend. - cmd.arg("--codegen-backend").arg(codegen_backend.name()); + cmd.arg("--default-codegen-backend").arg(codegen_backend.name()); + } else { + // Tells compiletest which codegen backend to use. + // It is used to e.g. ignore tests that don't support that codegen backend. + cmd.arg("--default-codegen-backend") + .arg(builder.config.default_codegen_backend(compiler.host).unwrap().name()); } if builder.build.config.llvm_enzyme { @@ -1879,7 +1941,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the } let mut hostflags = flags.clone(); - hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No, compiler.stage)); + hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No)); let mut targetflags = flags; @@ -2177,9 +2239,10 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let _group = builder.msg( Kind::Test, - compiler.stage, format!("compiletest suite={suite} mode={mode}"), - compiler.host, + // FIXME: compiletest sometimes behaves as ToolStd, we could expose that difference here + Mode::ToolBootstrap, + compiler, target, ); try_run_tests(builder, &mut cmd, false); @@ -2221,7 +2284,7 @@ struct BookTest { impl Step for BookTest { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.never() @@ -2258,7 +2321,7 @@ impl BookTest { // mdbook just executes a binary named "rustdoc", so we need to update // PATH so that it points to our rustdoc. - let mut rustdoc_path = builder.rustdoc(compiler); + let mut rustdoc_path = builder.rustdoc_for_compiler(compiler); rustdoc_path.pop(); let old_path = env::var_os("PATH").unwrap_or_default(); let new_path = env::join_paths(iter::once(rustdoc_path).chain(env::split_paths(&old_path))) @@ -2321,9 +2384,9 @@ impl BookTest { builder.add_rust_test_threads(&mut rustbook_cmd); let _guard = builder.msg( Kind::Test, - compiler.stage, format_args!("mdbook {}", self.path.display()), - compiler.host, + None, + compiler, compiler.host, ); let _time = helpers::timeit(builder); @@ -2342,8 +2405,7 @@ impl BookTest { builder.std(compiler, host); - let _guard = - builder.msg(Kind::Test, compiler.stage, format!("book {}", self.name), host, host); + let _guard = builder.msg(Kind::Test, format!("book {}", self.name), None, compiler, host); // Do a breadth-first traversal of the `src/doc` directory and just run // tests for all files that end in `*.md` @@ -2388,7 +2450,7 @@ macro_rules! test_book { impl Step for $name { type Output = (); const DEFAULT: bool = $default; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path($path) @@ -2442,13 +2504,13 @@ test_book!( #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ErrorIndex { - compiler: Compiler, + compilers: RustcPrivateCompilers, } impl Step for ErrorIndex { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { // Also add `error-index` here since that is what appears in the error message @@ -2460,8 +2522,12 @@ impl Step for ErrorIndex { // error_index_generator depends on librustdoc. Use the compiler that // is normally used to build rustdoc for other tests (like compiletest // tests in tests/rustdoc) so that it shares the same artifacts. - let compiler = run.builder.compiler(run.builder.top_stage, run.builder.config.host_target); - run.builder.ensure(ErrorIndex { compiler }); + let compilers = RustcPrivateCompilers::new( + run.builder, + run.builder.top_stage, + run.builder.config.host_target, + ); + run.builder.ensure(ErrorIndex { compilers }); } /// Runs the error index generator tool to execute the tests located in the error @@ -2471,24 +2537,30 @@ impl Step for ErrorIndex { /// generate a markdown file from the error indexes of the code base which is /// then passed to `rustdoc --test`. fn run(self, builder: &Builder<'_>) { - let compiler = self.compiler; + // The compiler that we are testing + let target_compiler = self.compilers.target_compiler(); - let dir = testdir(builder, compiler.host); + let dir = testdir(builder, target_compiler.host); t!(fs::create_dir_all(&dir)); let output = dir.join("error-index.md"); - let mut tool = tool::ErrorIndex::command(builder); + let mut tool = tool::ErrorIndex::command(builder, self.compilers); tool.arg("markdown").arg(&output); - let guard = - builder.msg(Kind::Test, compiler.stage, "error-index", compiler.host, compiler.host); + let guard = builder.msg( + Kind::Test, + "error-index", + None, + self.compilers.build_compiler(), + target_compiler.host, + ); let _time = helpers::timeit(builder); tool.run_capture(builder); drop(guard); // The tests themselves need to link to std, so make sure it is // available. - builder.std(compiler, compiler.host); - markdown_test(builder, compiler, &output); + builder.std(target_compiler, target_compiler.host); + markdown_test(builder, target_compiler, &output); } } @@ -2534,7 +2606,7 @@ pub struct CrateLibrustc { impl Step for CrateLibrustc { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.crate_or_deps("rustc-main").path("compiler") @@ -2580,9 +2652,8 @@ fn run_cargo_test<'a>( let compiler = cargo.compiler(); let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, target, builder); let _time = helpers::timeit(builder); - let _group = description.into().and_then(|what| { - builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target) - }); + let _group = + description.into().and_then(|what| builder.msg(Kind::Test, what, None, compiler, target)); #[cfg(feature = "build-metrics")] builder.metrics.begin_test_suite( @@ -2678,7 +2749,7 @@ fn prepare_cargo_test( /// FIXME(Zalathar): Try to split this into two separate steps: a user-visible /// step for testing standard library crates, and an internal step used for both /// library crates and compiler crates. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Crate { pub compiler: Compiler, pub target: TargetSelection, @@ -2786,7 +2857,7 @@ impl Step for Crate { .arg("--manifest-path") .arg(builder.src.join("library/sysroot/Cargo.toml")); } else { - compile::std_cargo(builder, target, compiler.stage, &mut cargo); + compile::std_cargo(builder, target, &mut cargo); } } Mode::Rustc => { @@ -2820,7 +2891,7 @@ pub struct CrateRustdoc { impl Step for CrateRustdoc { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.paths(&["src/librustdoc", "src/tools/rustdoc"]) @@ -2912,7 +2983,7 @@ pub struct CrateRustdocJsonTypes { impl Step for CrateRustdocJsonTypes { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/rustdoc-json-types") @@ -3101,13 +3172,14 @@ pub struct Bootstrap; impl Step for Bootstrap { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; /// Tests the build system itself. fn run(self, builder: &Builder<'_>) { let host = builder.config.host_target; - let compiler = builder.compiler(0, host); - let _guard = builder.msg(Kind::Test, 0, "bootstrap", host, host); + let build_compiler = builder.compiler(0, host); + let _guard = + builder.msg(Kind::Test, "bootstrap", Mode::ToolBootstrap, build_compiler, host); // Some tests require cargo submodule to be present. builder.build.require_submodule("src/tools/cargo", None); @@ -3126,7 +3198,7 @@ impl Step for Bootstrap { let mut cargo = tool::prepare_tool_cargo( builder, - compiler, + build_compiler, Mode::ToolBootstrap, host, Kind::Test, @@ -3170,7 +3242,7 @@ pub struct TierCheck { impl Step for TierCheck { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/tier-check") @@ -3206,9 +3278,9 @@ impl Step for TierCheck { let _guard = builder.msg( Kind::Test, - self.compiler.stage, "platform support check", - self.compiler.host, + None, + self.compiler, self.compiler.host, ); BootstrapCommand::from(cargo).delay_failure().run(builder); @@ -3224,7 +3296,7 @@ pub struct LintDocs { impl Step for LintDocs { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/lint-docs") @@ -3240,11 +3312,8 @@ impl Step for LintDocs { /// Tests that the lint examples in the rustc book generate the correct /// lints and have the expected format. fn run(self, builder: &Builder<'_>) { - builder.ensure(crate::core::build_steps::doc::RustcBook { - compiler: self.compiler, - target: self.target, - validate: true, - }); + builder + .ensure(crate::core::build_steps::doc::RustcBook::validate(self.compiler, self.target)); } } @@ -3253,16 +3322,16 @@ pub struct RustInstaller; impl Step for RustInstaller { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = true; /// Ensure the version placeholder replacement tool builds fn run(self, builder: &Builder<'_>) { let bootstrap_host = builder.config.host_target; - let compiler = builder.compiler(0, bootstrap_host); + let build_compiler = builder.compiler(0, bootstrap_host); let cargo = tool::prepare_tool_cargo( builder, - compiler, + build_compiler, Mode::ToolBootstrap, bootstrap_host, Kind::Test, @@ -3271,13 +3340,8 @@ impl Step for RustInstaller { &[], ); - let _guard = builder.msg( - Kind::Test, - compiler.stage, - "rust-installer", - bootstrap_host, - bootstrap_host, - ); + let _guard = + builder.msg(Kind::Test, "rust-installer", None, build_compiler, bootstrap_host); run_cargo_test(cargo, &[], &[], None, bootstrap_host, builder); // We currently don't support running the test.sh script outside linux(?) environments. @@ -3288,7 +3352,7 @@ impl Step for RustInstaller { } let mut cmd = command(builder.src.join("src/tools/rust-installer/test.sh")); - let tmpdir = testdir(builder, compiler.host).join("rust-installer"); + let tmpdir = testdir(builder, build_compiler.host).join("rust-installer"); let _ = std::fs::remove_dir_all(&tmpdir); let _ = std::fs::create_dir_all(&tmpdir); cmd.current_dir(&tmpdir); @@ -3378,7 +3442,7 @@ pub struct CodegenCranelift { impl Step for CodegenCranelift { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.paths(&["compiler/rustc_codegen_cranelift"]) @@ -3408,7 +3472,11 @@ impl Step for CodegenCranelift { return; } - if !builder.config.codegen_backends(run.target).contains(&CodegenBackendKind::Cranelift) { + if !builder + .config + .enabled_codegen_backends(run.target) + .contains(&CodegenBackendKind::Cranelift) + { builder.info("cranelift not in rust.codegen-backends. skipping"); return; } @@ -3502,7 +3570,7 @@ pub struct CodegenGCC { impl Step for CodegenGCC { type Output = (); const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.paths(&["compiler/rustc_codegen_gcc"]) @@ -3535,7 +3603,7 @@ impl Step for CodegenGCC { return; } - if !builder.config.codegen_backends(run.target).contains(&CodegenBackendKind::Gcc) { + if !builder.config.enabled_codegen_backends(run.target).contains(&CodegenBackendKind::Gcc) { builder.info("gcc not in rust.codegen-backends. skipping"); return; } @@ -3635,7 +3703,7 @@ pub struct TestFloatParse { impl Step for TestFloatParse { type Output = (); - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -3696,12 +3764,12 @@ impl Step for TestFloatParse { /// Runs the tool `src/tools/collect-license-metadata` in `ONLY_CHECK=1` mode, /// which verifies that `license-metadata.json` is up-to-date and therefore /// running the tool normally would not update anything. -#[derive(Debug, PartialOrd, Ord, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct CollectLicenseMetadata; impl Step for CollectLicenseMetadata { type Output = PathBuf; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/collect-license-metadata") diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index f5fa33b98f3b..b62c9a906b79 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -13,9 +13,6 @@ use std::ffi::OsStr; use std::path::PathBuf; use std::{env, fs}; -#[cfg(feature = "tracing")] -use tracing::instrument; - use crate::core::build_steps::compile::is_lto_stage; use crate::core::build_steps::toolstate::ToolState; use crate::core::build_steps::{compile, llvm}; @@ -26,7 +23,7 @@ use crate::core::builder::{ use crate::core::config::{DebuginfoLevel, RustcLto, TargetSelection}; use crate::utils::exec::{BootstrapCommand, command}; use crate::utils::helpers::{add_dylib_path, exe, t}; -use crate::{Compiler, FileType, Kind, Mode, gha}; +use crate::{Compiler, FileType, Kind, Mode}; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum SourceType { @@ -58,43 +55,14 @@ struct ToolBuild { artifact_kind: ToolArtifactKind, } -impl Builder<'_> { - #[track_caller] - pub(crate) fn msg_tool( - &self, - kind: Kind, - mode: Mode, - tool: &str, - build_stage: u32, - host: &TargetSelection, - target: &TargetSelection, - ) -> Option { - match mode { - // depends on compiler stage, different to host compiler - Mode::ToolRustc => self.msg_sysroot_tool( - kind, - build_stage, - format_args!("tool {tool}"), - *host, - *target, - ), - // doesn't depend on compiler, same as host compiler - _ => self.msg(kind, build_stage, format_args!("tool {tool}"), *host, *target), - } - } -} - /// Result of the tool build process. Each `Step` in this module is responsible /// for using this type as `type Output = ToolBuildResult;` #[derive(Clone)] pub struct ToolBuildResult { /// Artifact path of the corresponding tool that was built. pub tool_path: PathBuf, - /// Compiler used to build the tool. For non-`ToolRustc` tools this is equal to `target_compiler`. - /// For `ToolRustc` this is one stage before of the `target_compiler`. + /// Compiler used to build the tool. pub build_compiler: Compiler, - /// Target compiler passed to `Step`. - pub target_compiler: Compiler, } impl Step for ToolBuild { @@ -108,22 +76,15 @@ impl Step for ToolBuild { /// /// This will build the specified tool with the specified `host` compiler in /// `stage` into the normal cargo output directory. - fn run(mut self, builder: &Builder<'_>) -> ToolBuildResult { + fn run(self, builder: &Builder<'_>) -> ToolBuildResult { let target = self.target; let mut tool = self.tool; let path = self.path; - let target_compiler = self.build_compiler; - self.build_compiler = if self.mode == Mode::ToolRustc { - get_tool_rustc_compiler(builder, self.build_compiler) - } else { - self.build_compiler - }; - match self.mode { Mode::ToolRustc => { - // If compiler was forced, its artifacts should have been prepared earlier. - if !self.build_compiler.is_forced_compiler() { + // FIXME: remove this, it's only needed for download-rustc... + if !self.build_compiler.is_forced_compiler() && builder.download_rustc() { builder.std(self.build_compiler, self.build_compiler.host); builder.ensure(compile::Rustc::new(self.build_compiler, target)); } @@ -180,15 +141,8 @@ impl Step for ToolBuild { cargo.args(self.cargo_args); - let _guard = builder.msg_tool( - Kind::Build, - self.mode, - self.tool, - // A stage N tool is built with the stage N-1 compiler. - self.build_compiler.stage + 1, - &self.build_compiler.host, - &self.target, - ); + let _guard = + builder.msg(Kind::Build, self.tool, self.mode, self.build_compiler, self.target); // we check this below let build_success = compile::stream_cargo(builder, cargo, vec![], &mut |_| {}); @@ -216,7 +170,7 @@ impl Step for ToolBuild { .join(format!("lib{tool}.rlib")), }; - ToolBuildResult { tool_path, build_compiler: self.build_compiler, target_compiler } + ToolBuildResult { tool_path, build_compiler: self.build_compiler } } } } @@ -272,6 +226,14 @@ pub fn prepare_tool_cargo( // own copy cargo.env("LZMA_API_STATIC", "1"); + // Build jemalloc on AArch64 with support for page sizes up to 64K + // See: https://github.com/rust-lang/rust/pull/135081 + // Note that `miri` always uses jemalloc. As such, there is no checking of the jemalloc build flag. + // See also the "JEMALLOC_SYS_WITH_LG_PAGE" setting in the compile build step. + if target.starts_with("aarch64") && env::var_os("JEMALLOC_SYS_WITH_LG_PAGE").is_none() { + cargo.env("JEMALLOC_SYS_WITH_LG_PAGE", "16"); + } + // CFG_RELEASE is needed by rustfmt (and possibly other tools) which // import rustc-ap-rustc_attr which requires this to be set for the // `#[cfg(version(...))]` attribute. @@ -346,31 +308,11 @@ pub fn prepare_tool_cargo( cargo } -/// Handle stage-off logic for `ToolRustc` tools when necessary. -pub(crate) fn get_tool_rustc_compiler( - builder: &Builder<'_>, - target_compiler: Compiler, -) -> Compiler { - if target_compiler.is_forced_compiler() { - return target_compiler; - } - - if builder.download_rustc() && target_compiler.stage == 1 { - // We shouldn't drop to stage0 compiler when using CI rustc. - return builder.compiler(1, builder.config.host_target); - } - - // Similar to `compile::Assemble`, build with the previous stage's compiler. Otherwise - // we'd have stageN/bin/rustc and stageN/bin/$rustc_tool be effectively different stage - // compilers, which isn't what we want. Rustc tools should be linked in the same way as the - // compiler it's paired with, so it must be built with the previous stage compiler. - builder.compiler(target_compiler.stage.saturating_sub(1), builder.config.host_target) -} - /// Determines how to build a `ToolTarget`, i.e. which compiler should be used to compile it. /// The compiler stage is automatically bumped if we need to cross-compile a stage 1 tool. pub enum ToolTargetBuildMode { - /// Build the tool using rustc that corresponds to the selected CLI stage. + /// Build the tool for the given `target` using rustc that corresponds to the top CLI + /// stage. Build(TargetSelection), /// Build the tool so that it can be attached to the sysroot of the passed compiler. /// Since we always dist stage 2+, the compiler that builds the tool in this case has to be @@ -402,7 +344,10 @@ pub(crate) fn get_tool_target_compiler( } else { // If we are cross-compiling a stage 1 tool, we cannot do that with a stage 0 compiler, // so we auto-bump the tool's stage to 2, which means we need a stage 1 compiler. - builder.compiler(build_compiler_stage.max(1), builder.host_target) + let build_compiler = builder.compiler(build_compiler_stage.max(1), builder.host_target); + // We also need the host stdlib to compile host code (proc macros/build scripts) + builder.std(build_compiler, builder.host_target); + build_compiler }; builder.std(compiler, target); compiler @@ -412,13 +357,13 @@ pub(crate) fn get_tool_target_compiler( /// tools directory. fn copy_link_tool_bin( builder: &Builder<'_>, - compiler: Compiler, + build_compiler: Compiler, target: TargetSelection, mode: Mode, name: &str, ) -> PathBuf { - let cargo_out = builder.cargo_out(compiler, mode, target).join(exe(name, target)); - let bin = builder.tools_dir(compiler).join(exe(name, target)); + let cargo_out = builder.cargo_out(build_compiler, mode, target).join(exe(name, target)); + let bin = builder.tools_dir(build_compiler).join(exe(name, target)); builder.copy_link(&cargo_out, &bin, FileType::Executable); bin } @@ -475,14 +420,6 @@ macro_rules! bootstrap_tool { }); } - #[cfg_attr( - feature = "tracing", - instrument( - level = "debug", - name = $tool_name, - skip_all, - ), - )] fn run(self, builder: &Builder<'_>) -> ToolBuildResult { $( for submodule in $submodules { @@ -624,20 +561,20 @@ impl Step for RustcPerf { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct ErrorIndex { - pub compiler: Compiler, + compilers: RustcPrivateCompilers, } impl ErrorIndex { - pub fn command(builder: &Builder<'_>) -> BootstrapCommand { + pub fn command(builder: &Builder<'_>, compilers: RustcPrivateCompilers) -> BootstrapCommand { // Error-index-generator links with the rustdoc library, so we need to add `rustc_lib_paths` // for rustc_private and libLLVM.so, and `sysroot_lib` for libstd, etc. - let host = builder.config.host_target; - let compiler = builder.compiler_for(builder.top_stage, host, host); - let mut cmd = command(builder.ensure(ErrorIndex { compiler }).tool_path); - let mut dylib_paths = builder.rustc_lib_paths(compiler); - dylib_paths.push(builder.sysroot_target_libdir(compiler, compiler.host)); + let mut cmd = command(builder.ensure(ErrorIndex { compilers }).tool_path); + + let target_compiler = compilers.target_compiler(); + let mut dylib_paths = builder.rustc_lib_paths(target_compiler); + dylib_paths.push(builder.sysroot_target_libdir(target_compiler, target_compiler.host)); add_dylib_path(dylib_paths, &mut cmd); cmd } @@ -656,14 +593,19 @@ impl Step for ErrorIndex { // src/tools/error-index-generator` which almost nobody does. // Normally, `x.py test` or `x.py doc` will use the // `ErrorIndex::command` function instead. - let compiler = run.builder.compiler(run.builder.top_stage, run.builder.config.host_target); - run.builder.ensure(ErrorIndex { compiler }); + run.builder.ensure(ErrorIndex { + compilers: RustcPrivateCompilers::new( + run.builder, + run.builder.top_stage, + run.builder.host_target, + ), + }); } fn run(self, builder: &Builder<'_>) -> ToolBuildResult { builder.ensure(ToolBuild { - build_compiler: self.compiler, - target: self.compiler.host, + build_compiler: self.compilers.build_compiler, + target: self.compilers.target(), tool: "error_index_generator", mode: Mode::ToolRustc, path: "src/tools/error_index_generator", @@ -674,6 +616,13 @@ impl Step for ErrorIndex { artifact_kind: ToolArtifactKind::Binary, }) } + + fn metadata(&self) -> Option { + Some( + StepMetadata::build("error-index", self.compilers.target()) + .built_by(self.compilers.build_compiler), + ) + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -719,43 +668,48 @@ impl Step for RemoteTestServer { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +/// Represents `Rustdoc` that either comes from the external stage0 sysroot or that is built +/// locally. +/// Rustdoc is special, because it both essentially corresponds to a `Compiler` (that can be +/// externally provided), but also to a `ToolRustc` tool. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustdoc { - /// This should only ever be 0 or 2. - /// We sometimes want to reference the "bootstrap" rustdoc, which is why this option is here. - pub compiler: Compiler, + /// If the stage of `target_compiler` is `0`, then rustdoc is externally provided. + /// Otherwise it is built locally. + pub target_compiler: Compiler, } impl Step for Rustdoc { - type Output = ToolBuildResult; + /// Path to the built rustdoc binary. + type Output = PathBuf; + const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/rustdoc").path("src/librustdoc") } fn make_run(run: RunConfig<'_>) { - run.builder - .ensure(Rustdoc { compiler: run.builder.compiler(run.builder.top_stage, run.target) }); + run.builder.ensure(Rustdoc { + target_compiler: run.builder.compiler(run.builder.top_stage, run.target), + }); } - fn run(self, builder: &Builder<'_>) -> ToolBuildResult { - let target_compiler = self.compiler; + fn run(self, builder: &Builder<'_>) -> Self::Output { + let target_compiler = self.target_compiler; let target = target_compiler.host; + // If stage is 0, we use a prebuilt rustdoc from stage0 if target_compiler.stage == 0 { if !target_compiler.is_snapshot(builder) { panic!("rustdoc in stage 0 must be snapshot rustdoc"); } - return ToolBuildResult { - tool_path: builder.initial_rustdoc.clone(), - build_compiler: target_compiler, - target_compiler, - }; + return builder.initial_rustdoc.clone(); } + // If stage is higher, we build rustdoc instead let bin_rustdoc = || { let sysroot = builder.sysroot(target_compiler); let bindir = sysroot.join("bin"); @@ -767,10 +721,7 @@ impl Step for Rustdoc { // If CI rustc is enabled and we haven't modified the rustdoc sources, // use the precompiled rustdoc from CI rustc's sysroot to speed up bootstrapping. - if builder.download_rustc() - && target_compiler.stage > 0 - && builder.rust_info().is_managed_git_subrepository() - { + if builder.download_rustc() && builder.rust_info().is_managed_git_subrepository() { let files_to_track = &["src/librustdoc", "src/tools/rustdoc", "src/rustdoc-json-types"]; // Check if unchanged @@ -783,12 +734,7 @@ impl Step for Rustdoc { let bin_rustdoc = bin_rustdoc(); builder.copy_link(&precompiled_rustdoc, &bin_rustdoc, FileType::Executable); - - return ToolBuildResult { - tool_path: bin_rustdoc, - build_compiler: target_compiler, - target_compiler, - }; + return bin_rustdoc; } } @@ -804,9 +750,10 @@ impl Step for Rustdoc { extra_features.push("jemalloc".to_string()); } - let ToolBuildResult { tool_path, build_compiler, target_compiler } = - builder.ensure(ToolBuild { - build_compiler: target_compiler, + let compilers = RustcPrivateCompilers::from_target_compiler(builder, target_compiler); + let tool_path = builder + .ensure(ToolBuild { + build_compiler: compilers.build_compiler, target, // Cargo adds a number of paths to the dylib search path on windows, which results in // the wrong rustdoc being executed. To avoid the conflicting rustdocs, we name the "tool" @@ -819,44 +766,47 @@ impl Step for Rustdoc { allow_features: "", cargo_args: Vec::new(), artifact_kind: ToolArtifactKind::Binary, - }); - - // don't create a stage0-sysroot/bin directory. - if target_compiler.stage > 0 { - if builder.config.rust_debuginfo_level_tools == DebuginfoLevel::None { - // Due to LTO a lot of debug info from C++ dependencies such as jemalloc can make it into - // our final binaries - compile::strip_debug(builder, target, &tool_path); - } - let bin_rustdoc = bin_rustdoc(); - builder.copy_link(&tool_path, &bin_rustdoc, FileType::Executable); - ToolBuildResult { tool_path: bin_rustdoc, build_compiler, target_compiler } - } else { - ToolBuildResult { tool_path, build_compiler, target_compiler } + }) + .tool_path; + + if builder.config.rust_debuginfo_level_tools == DebuginfoLevel::None { + // Due to LTO a lot of debug info from C++ dependencies such as jemalloc can make it into + // our final binaries + compile::strip_debug(builder, target, &tool_path); } + let bin_rustdoc = bin_rustdoc(); + builder.copy_link(&tool_path, &bin_rustdoc, FileType::Executable); + bin_rustdoc } fn metadata(&self) -> Option { Some( - StepMetadata::build("rustdoc", self.compiler.host) - // rustdoc is ToolRustc, so stage N rustdoc is built by stage N-1 rustc - // FIXME: make this stage deduction automatic somehow - // FIXME: log the compiler that actually built ToolRustc steps - .stage(self.compiler.stage.saturating_sub(1)), + StepMetadata::build("rustdoc", self.target_compiler.host) + .stage(self.target_compiler.stage), ) } } +/// Builds the cargo tool. +/// Note that it can be built using a stable compiler. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Cargo { - pub compiler: Compiler, - pub target: TargetSelection, + build_compiler: Compiler, + target: TargetSelection, +} + +impl Cargo { + /// Returns `Cargo` that will be **compiled** by the passed compiler, for the given + /// `target`. + pub fn from_build_compiler(build_compiler: Compiler, target: TargetSelection) -> Self { + Self { build_compiler, target } + } } impl Step for Cargo { type Output = ToolBuildResult; const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -865,7 +815,10 @@ impl Step for Cargo { fn make_run(run: RunConfig<'_>) { run.builder.ensure(Cargo { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), + build_compiler: get_tool_target_compiler( + run.builder, + ToolTargetBuildMode::Build(run.target), + ), target: run.target, }); } @@ -873,19 +826,30 @@ impl Step for Cargo { fn run(self, builder: &Builder<'_>) -> ToolBuildResult { builder.build.require_submodule("src/tools/cargo", None); + builder.std(self.build_compiler, builder.host_target); + builder.std(self.build_compiler, self.target); + builder.ensure(ToolBuild { - build_compiler: self.compiler, + build_compiler: self.build_compiler, target: self.target, tool: "cargo", - mode: Mode::ToolRustc, + mode: Mode::ToolTarget, path: "src/tools/cargo", source_type: SourceType::Submodule, extra_features: Vec::new(), - allow_features: "", + // Cargo is compilable with a stable compiler, but since we run in bootstrap, + // with RUSTC_BOOTSTRAP being set, some "clever" build scripts enable specialization + // based on this, which breaks stuff. We thus have to explicitly allow these features + // here. + allow_features: "min_specialization,specialization", cargo_args: Vec::new(), artifact_kind: ToolArtifactKind::Binary, }) } + + fn metadata(&self) -> Option { + Some(StepMetadata::build("cargo", self.target).built_by(self.build_compiler)) + } } /// Represents a built LldWrapper, the `lld-wrapper` tool itself, and a directory @@ -918,7 +882,7 @@ impl LldWrapper { impl Step for LldWrapper { type Output = BuiltLldWrapper; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/lld-wrapper") @@ -934,15 +898,6 @@ impl Step for LldWrapper { }); } - #[cfg_attr( - feature = "tracing", - instrument( - level = "debug", - name = "LldWrapper::run", - skip_all, - fields(build_compiler = ?self.build_compiler), - ), - )] fn run(self, builder: &Builder<'_>) -> Self::Output { let lld_dir = builder.ensure(llvm::Lld { target: self.target }); let tool = builder.ensure(ToolBuild { @@ -1019,7 +974,7 @@ impl WasmComponentLd { impl Step for WasmComponentLd { type Output = ToolBuildResult; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/wasm-component-ld") @@ -1035,15 +990,6 @@ impl Step for WasmComponentLd { }); } - #[cfg_attr( - feature = "tracing", - instrument( - level = "debug", - name = "WasmComponentLd::run", - skip_all, - fields(build_compiler = ?self.build_compiler), - ), - )] fn run(self, builder: &Builder<'_>) -> ToolBuildResult { builder.ensure(ToolBuild { build_compiler: self.build_compiler, @@ -1066,8 +1012,13 @@ impl Step for WasmComponentLd { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RustAnalyzer { - pub compiler: Compiler, - pub target: TargetSelection, + compilers: RustcPrivateCompilers, +} + +impl RustAnalyzer { + pub fn from_compilers(compilers: RustcPrivateCompilers) -> Self { + Self { compilers } + } } impl RustAnalyzer { @@ -1077,7 +1028,7 @@ impl RustAnalyzer { impl Step for RustAnalyzer { type Output = ToolBuildResult; const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1086,15 +1037,16 @@ impl Step for RustAnalyzer { fn make_run(run: RunConfig<'_>) { run.builder.ensure(RustAnalyzer { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), - target: run.target, + compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target), }); } fn run(self, builder: &Builder<'_>) -> ToolBuildResult { + let build_compiler = self.compilers.build_compiler; + let target = self.compilers.target(); builder.ensure(ToolBuild { - build_compiler: self.compiler, - target: self.target, + build_compiler, + target, tool: "rust-analyzer", mode: Mode::ToolRustc, path: "src/tools/rust-analyzer", @@ -1105,18 +1057,31 @@ impl Step for RustAnalyzer { artifact_kind: ToolArtifactKind::Binary, }) } + + fn metadata(&self) -> Option { + Some( + StepMetadata::build("rust-analyzer", self.compilers.target()) + .built_by(self.compilers.build_compiler), + ) + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RustAnalyzerProcMacroSrv { - pub compiler: Compiler, - pub target: TargetSelection, + compilers: RustcPrivateCompilers, +} + +impl RustAnalyzerProcMacroSrv { + pub fn from_compilers(compilers: RustcPrivateCompilers) -> Self { + Self { compilers } + } } impl Step for RustAnalyzerProcMacroSrv { - type Output = Option; + type Output = ToolBuildResult; + const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1131,15 +1096,14 @@ impl Step for RustAnalyzerProcMacroSrv { fn make_run(run: RunConfig<'_>) { run.builder.ensure(RustAnalyzerProcMacroSrv { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), - target: run.target, + compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target), }); } - fn run(self, builder: &Builder<'_>) -> Option { + fn run(self, builder: &Builder<'_>) -> Self::Output { let tool_result = builder.ensure(ToolBuild { - build_compiler: self.compiler, - target: self.target, + build_compiler: self.compilers.build_compiler, + target: self.compilers.target(), tool: "rust-analyzer-proc-macro-srv", mode: Mode::ToolRustc, path: "src/tools/rust-analyzer/crates/proc-macro-srv-cli", @@ -1152,7 +1116,7 @@ impl Step for RustAnalyzerProcMacroSrv { // Copy `rust-analyzer-proc-macro-srv` to `/libexec/` // so that r-a can use it. - let libexec_path = builder.sysroot(self.compiler).join("libexec"); + let libexec_path = builder.sysroot(self.compilers.target_compiler).join("libexec"); t!(fs::create_dir_all(&libexec_path)); builder.copy_link( &tool_result.tool_path, @@ -1160,7 +1124,14 @@ impl Step for RustAnalyzerProcMacroSrv { FileType::Executable, ); - Some(tool_result) + tool_result + } + + fn metadata(&self) -> Option { + Some( + StepMetadata::build("rust-analyzer-proc-macro-srv", self.compilers.target()) + .built_by(self.compilers.build_compiler), + ) } } @@ -1200,7 +1171,7 @@ impl LlvmBitcodeLinker { impl Step for LlvmBitcodeLinker { type Output = ToolBuildResult; const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; @@ -1215,10 +1186,6 @@ impl Step for LlvmBitcodeLinker { }); } - #[cfg_attr( - feature = "tracing", - instrument(level = "debug", name = "LlvmBitcodeLinker::run", skip_all) - )] fn run(self, builder: &Builder<'_>) -> ToolBuildResult { builder.ensure(ToolBuild { build_compiler: self.build_compiler, @@ -1254,7 +1221,7 @@ pub enum LibcxxVersion { impl Step for LibcxxVersionTool { type Output = LibcxxVersion; const DEFAULT: bool = false; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.never() @@ -1302,7 +1269,92 @@ impl Step for LibcxxVersionTool { } } -macro_rules! tool_extended { +/// Represents which compilers are involved in the compilation of a tool +/// that depends on compiler internals (`rustc_private`). +/// Their compilation looks like this: +/// +/// - `build_compiler` (stage N-1) builds `target_compiler` (stage N) to produce .rlibs +/// - These .rlibs are copied into the sysroot of `build_compiler` +/// - `build_compiler` (stage N-1) builds `` (stage N) +/// - `` links to .rlibs from `target_compiler` +/// +/// Eventually, this could also be used for .rmetas and check builds, but so far we only deal with +/// normal builds here. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct RustcPrivateCompilers { + /// Compiler that builds the tool and that builds `target_compiler`. + build_compiler: Compiler, + /// Compiler to which .rlib artifacts the tool links to. + /// The host target of this compiler corresponds to the target of the tool. + target_compiler: Compiler, +} + +impl RustcPrivateCompilers { + /// Create compilers for a `rustc_private` tool with the given `stage` and for the given + /// `target`. + pub fn new(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self { + let build_compiler = Self::build_compiler_from_stage(builder, stage); + + // This is the compiler we'll link to + // FIXME: make 100% sure that `target_compiler` was indeed built with `build_compiler`... + let target_compiler = builder.compiler(build_compiler.stage + 1, target); + + Self { build_compiler, target_compiler } + } + + pub fn from_build_and_target_compiler( + build_compiler: Compiler, + target_compiler: Compiler, + ) -> Self { + Self { build_compiler, target_compiler } + } + + /// Create rustc tool compilers from the build compiler. + pub fn from_build_compiler( + builder: &Builder<'_>, + build_compiler: Compiler, + target: TargetSelection, + ) -> Self { + let target_compiler = builder.compiler(build_compiler.stage + 1, target); + Self { build_compiler, target_compiler } + } + + /// Create rustc tool compilers from the target compiler. + pub fn from_target_compiler(builder: &Builder<'_>, target_compiler: Compiler) -> Self { + Self { + build_compiler: Self::build_compiler_from_stage(builder, target_compiler.stage), + target_compiler, + } + } + + fn build_compiler_from_stage(builder: &Builder<'_>, stage: u32) -> Compiler { + assert!(stage > 0); + + if builder.download_rustc() && stage == 1 { + // We shouldn't drop to stage0 compiler when using CI rustc. + builder.compiler(1, builder.config.host_target) + } else { + builder.compiler(stage - 1, builder.config.host_target) + } + } + + pub fn build_compiler(&self) -> Compiler { + self.build_compiler + } + + pub fn target_compiler(&self) -> Compiler { + self.target_compiler + } + + /// Target of the tool being compiled + pub fn target(&self) -> TargetSelection { + self.target_compiler.host + } +} + +/// Creates a step that builds an extended `Mode::ToolRustc` tool +/// and installs it into the sysroot of a corresponding compiler. +macro_rules! tool_rustc_extended { ( $name:ident { path: $path:expr, @@ -1316,17 +1368,24 @@ macro_rules! tool_extended { ) => { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct $name { - pub compiler: Compiler, - pub target: TargetSelection, + compilers: RustcPrivateCompilers, + } + + impl $name { + pub fn from_compilers(compilers: RustcPrivateCompilers) -> Self { + Self { + compilers, + } + } } impl Step for $name { type Output = ToolBuildResult; const DEFAULT: bool = true; // Overridden by `should_run_tool_build_step` - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - should_run_tool_build_step( + should_run_extended_rustc_tool( run, $tool_name, $path, @@ -1336,17 +1395,15 @@ macro_rules! tool_extended { fn make_run(run: RunConfig<'_>) { run.builder.ensure($name { - compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target), - target: run.target, + compilers: RustcPrivateCompilers::new(run.builder, run.builder.top_stage, run.target), }); } fn run(self, builder: &Builder<'_>) -> ToolBuildResult { - let Self { compiler, target } = self; - run_tool_build_step( + let Self { compilers } = self; + build_extended_rustc_tool( builder, - compiler, - target, + compilers, $tool_name, $path, None $( .or(Some(&$add_bins_to_sysroot)) )?, @@ -1356,18 +1413,16 @@ macro_rules! tool_extended { } fn metadata(&self) -> Option { - // FIXME: refactor extended tool steps to make the build_compiler explicit, - // it is offset by one now for rustc tools Some( - StepMetadata::build($tool_name, self.target) - .built_by(self.compiler.with_stage(self.compiler.stage.saturating_sub(1))) + StepMetadata::build($tool_name, self.compilers.target()) + .built_by(self.compilers.build_compiler) ) } } } } -fn should_run_tool_build_step<'a>( +fn should_run_extended_rustc_tool<'a>( run: ShouldRun<'a>, tool_name: &'static str, path: &'static str, @@ -1391,39 +1446,38 @@ fn should_run_tool_build_step<'a>( ) } -#[expect(clippy::too_many_arguments)] // silence overeager clippy lint -fn run_tool_build_step( +fn build_extended_rustc_tool( builder: &Builder<'_>, - compiler: Compiler, - target: TargetSelection, + compilers: RustcPrivateCompilers, tool_name: &'static str, path: &'static str, add_bins_to_sysroot: Option<&[&str]>, add_features: Option, TargetSelection, &mut Vec)>, cargo_args: Option<&[&'static str]>, ) -> ToolBuildResult { + let target = compilers.target(); let mut extra_features = Vec::new(); if let Some(func) = add_features { func(builder, target, &mut extra_features); } - let ToolBuildResult { tool_path, build_compiler, target_compiler } = - builder.ensure(ToolBuild { - build_compiler: compiler, - target, - tool: tool_name, - mode: Mode::ToolRustc, - path, - extra_features, - source_type: SourceType::InTree, - allow_features: "", - cargo_args: cargo_args.unwrap_or_default().iter().map(|s| String::from(*s)).collect(), - artifact_kind: ToolArtifactKind::Binary, - }); - + let build_compiler = compilers.build_compiler; + let ToolBuildResult { tool_path, .. } = builder.ensure(ToolBuild { + build_compiler, + target, + tool: tool_name, + mode: Mode::ToolRustc, + path, + extra_features, + source_type: SourceType::InTree, + allow_features: "", + cargo_args: cargo_args.unwrap_or_default().iter().map(|s| String::from(*s)).collect(), + artifact_kind: ToolArtifactKind::Binary, + }); + + let target_compiler = compilers.target_compiler; if let Some(add_bins_to_sysroot) = add_bins_to_sysroot && !add_bins_to_sysroot.is_empty() - && target_compiler.stage > 0 { let bindir = builder.sysroot(target_compiler).join("bin"); t!(fs::create_dir_all(&bindir)); @@ -1435,25 +1489,25 @@ fn run_tool_build_step( // Return a path into the bin dir. let path = bindir.join(exe(tool_name, target_compiler.host)); - ToolBuildResult { tool_path: path, build_compiler, target_compiler } + ToolBuildResult { tool_path: path, build_compiler } } else { - ToolBuildResult { tool_path, build_compiler, target_compiler } + ToolBuildResult { tool_path, build_compiler } } } -tool_extended!(Cargofmt { +tool_rustc_extended!(Cargofmt { path: "src/tools/rustfmt", tool_name: "cargo-fmt", stable: true, add_bins_to_sysroot: ["cargo-fmt"] }); -tool_extended!(CargoClippy { +tool_rustc_extended!(CargoClippy { path: "src/tools/clippy", tool_name: "cargo-clippy", stable: true, add_bins_to_sysroot: ["cargo-clippy"] }); -tool_extended!(Clippy { +tool_rustc_extended!(Clippy { path: "src/tools/clippy", tool_name: "clippy-driver", stable: true, @@ -1464,7 +1518,7 @@ tool_extended!(Clippy { } } }); -tool_extended!(Miri { +tool_rustc_extended!(Miri { path: "src/tools/miri", tool_name: "miri", stable: false, @@ -1472,13 +1526,13 @@ tool_extended!(Miri { // Always compile also tests when building miri. Otherwise feature unification can cause rebuilds between building and testing miri. cargo_args: &["--all-targets"], }); -tool_extended!(CargoMiri { +tool_rustc_extended!(CargoMiri { path: "src/tools/miri/cargo-miri", tool_name: "cargo-miri", stable: false, add_bins_to_sysroot: ["cargo-miri"] }); -tool_extended!(Rustfmt { +tool_rustc_extended!(Rustfmt { path: "src/tools/rustfmt", tool_name: "rustfmt", stable: true, @@ -1496,7 +1550,7 @@ impl TestFloatParse { impl Step for TestFloatParse { type Output = ToolBuildResult; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; const DEFAULT: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs index 0caeb328811a..7b860ceb9432 100644 --- a/src/bootstrap/src/core/build_steps/vendor.rs +++ b/src/bootstrap/src/core/build_steps/vendor.rs @@ -53,7 +53,7 @@ pub(crate) struct Vendor { impl Step for Vendor { type Output = VendorOutput; const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; + const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.alias("placeholder").default_condition(true) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 6b3236ef47ef..721924034123 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -101,6 +101,7 @@ pub struct Cargo { impl Cargo { /// Calls [`Builder::cargo`] and [`Cargo::configure_linker`] to prepare an invocation of `cargo` /// to be run. + #[track_caller] pub fn new( builder: &Builder<'_>, compiler: Compiler, @@ -115,7 +116,7 @@ impl Cargo { // No need to configure the target linker for these command types. Kind::Clean | Kind::Check | Kind::Format | Kind::Setup => {} _ => { - cargo.configure_linker(builder, mode); + cargo.configure_linker(builder); } } @@ -139,6 +140,7 @@ impl Cargo { /// Same as [`Cargo::new`] except this one doesn't configure the linker with /// [`Cargo::configure_linker`]. + #[track_caller] pub fn new_for_mir_opt_tests( builder: &Builder<'_>, compiler: Compiler, @@ -186,6 +188,32 @@ impl Cargo { self } + /// Append a value to an env var of the cargo command instance. + /// If the variable was unset previously, this is equivalent to [`Cargo::env`]. + /// If the variable was already set, this will append `delimiter` and then `value` to it. + /// + /// Note that this only considers the existence of the env. var. configured on this `Cargo` + /// instance. It does not look at the environment of this process. + pub fn append_to_env( + &mut self, + key: impl AsRef, + value: impl AsRef, + delimiter: impl AsRef, + ) -> &mut Cargo { + assert_ne!(key.as_ref(), "RUSTFLAGS"); + assert_ne!(key.as_ref(), "RUSTDOCFLAGS"); + + let key = key.as_ref(); + if let Some((_, Some(previous_value))) = self.command.get_envs().find(|(k, _)| *k == key) { + let mut combined: OsString = previous_value.to_os_string(); + combined.push(delimiter.as_ref()); + combined.push(value.as_ref()); + self.env(key, combined) + } else { + self.env(key, value) + } + } + pub fn add_rustc_lib_path(&mut self, builder: &Builder<'_>) { builder.add_rustc_lib_path(self.compiler, &mut self.command); } @@ -209,7 +237,7 @@ impl Cargo { // FIXME(onur-ozkan): Add coverage to make sure modifications to this function // doesn't cause cache invalidations (e.g., #130108). - fn configure_linker(&mut self, builder: &Builder<'_>, mode: Mode) -> &mut Cargo { + fn configure_linker(&mut self, builder: &Builder<'_>) -> &mut Cargo { let target = self.target; let compiler = self.compiler; @@ -264,12 +292,7 @@ impl Cargo { } } - // We use the snapshot compiler when building host code (build scripts/proc macros) of - // `Mode::Std` tools, so we need to determine the current stage here to pass the proper - // linker args (e.g. -C vs -Z). - // This should stay synchronized with the [cargo] function. - let host_stage = if mode == Mode::Std { 0 } else { compiler.stage }; - for arg in linker_args(builder, compiler.host, LldThreads::Yes, host_stage) { + for arg in linker_args(builder, compiler.host, LldThreads::Yes) { self.hostflags.arg(&arg); } @@ -279,10 +302,10 @@ impl Cargo { } // We want to set -Clinker using Cargo, therefore we only call `linker_flags` and not // `linker_args` here. - for flag in linker_flags(builder, target, LldThreads::Yes, compiler.stage) { + for flag in linker_flags(builder, target, LldThreads::Yes) { self.rustflags.arg(&flag); } - for arg in linker_args(builder, target, LldThreads::Yes, compiler.stage) { + for arg in linker_args(builder, target, LldThreads::Yes) { self.rustdocflags.arg(&arg); } @@ -401,6 +424,7 @@ impl From for BootstrapCommand { impl Builder<'_> { /// Like [`Builder::cargo`], but only passes flags that are valid for all commands. + #[track_caller] pub fn bare_cargo( &self, compiler: Compiler, @@ -438,6 +462,15 @@ impl Builder<'_> { let out_dir = self.stage_out(compiler, mode); cargo.env("CARGO_TARGET_DIR", &out_dir); + // Bootstrap makes a lot of assumptions about the artifacts produced in the target + // directory. If users override the "build directory" using `build-dir` + // (https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-dir), then + // bootstrap couldn't find these artifacts. So we forcefully override that option to our + // target directory here. + // In the future, we could attempt to read the build-dir location from Cargo and actually + // respect it. + cargo.env("CARGO_BUILD_BUILD_DIR", &out_dir); + // Found with `rg "init_env_logger\("`. If anyone uses `init_env_logger` // from out of tree it shouldn't matter, since x.py is only used for // building in-tree. @@ -476,6 +509,7 @@ impl Builder<'_> { /// scoped by `mode`'s output directory, it will pass the `--target` flag for the specified /// `target`, and will be executing the Cargo command `cmd`. `cmd` can be `miri-cmd` for /// commands to be run with Miri. + #[track_caller] fn cargo( &self, compiler: Compiler, @@ -495,10 +529,16 @@ impl Builder<'_> { build_stamp::clear_if_dirty(self, &out_dir, &backend); } + if self.config.cmd.timings() { + cargo.arg("--timings"); + } + if cmd_kind == Kind::Doc { let my_out = match mode { // This is the intended out directory for compiler documentation. - Mode::Rustc | Mode::ToolRustc => self.compiler_doc_out(target), + Mode::Rustc | Mode::ToolRustc | Mode::ToolBootstrap => { + self.compiler_doc_out(target) + } Mode::Std => { if self.config.cmd.json() { out_dir.join(target).join("json-doc") @@ -508,7 +548,7 @@ impl Builder<'_> { } _ => panic!("doc mode {mode:?} not expected"), }; - let rustdoc = self.rustdoc(compiler); + let rustdoc = self.rustdoc_for_compiler(compiler); build_stamp::clear_if_dirty(self, &my_out, &rustdoc); } @@ -822,7 +862,7 @@ impl Builder<'_> { } let rustdoc_path = match cmd_kind { - Kind::Doc | Kind::Test | Kind::MiriTest => self.rustdoc(compiler), + Kind::Doc | Kind::Test | Kind::MiriTest => self.rustdoc_for_compiler(compiler), _ => PathBuf::from("/path/to/nowhere/rustdoc/not/required"), }; @@ -1286,7 +1326,12 @@ impl Builder<'_> { if let Some(limit) = limit && (build_compiler_stage == 0 - || self.config.default_codegen_backend(target).unwrap_or_default().is_llvm()) + || self + .config + .default_codegen_backend(target) + .cloned() + .unwrap_or_default() + .is_llvm()) { rustflags.arg(&format!("-Cllvm-args=-import-instr-limit={limit}")); } diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 96289a63785e..043cb1c2666f 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -16,6 +16,7 @@ use tracing::instrument; pub use self::cargo::{Cargo, cargo_profile_var}; pub use crate::Compiler; use crate::core::build_steps::compile::{Std, StdLink}; +use crate::core::build_steps::tool::RustcPrivateCompilers; use crate::core::build_steps::{ check, clean, clippy, compile, dist, doc, gcc, install, llvm, run, setup, test, tool, vendor, }; @@ -77,7 +78,7 @@ impl Deref for Builder<'_> { /// type's [`Debug`] implementation. /// /// (Trying to debug-print `dyn Any` results in the unhelpful `"Any { .. }"`.) -trait AnyDebug: Any + Debug {} +pub trait AnyDebug: Any + Debug {} impl AnyDebug for T {} impl dyn AnyDebug { /// Equivalent to `::downcast_ref`. @@ -99,8 +100,13 @@ pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash { /// by `Step::should_run`. const DEFAULT: bool = false; - /// If true, then this rule should be skipped if --target was specified, but --host was not - const ONLY_HOSTS: bool = false; + /// If this value is true, then the values of `run.target` passed to the `make_run` function of + /// this Step will be determined based on the `--host` flag. + /// If this value is false, then they will be determined based on the `--target` flag. + /// + /// A corollary of the above is that if this is set to true, then the step will be skipped if + /// `--target` was specified, but `--host` was explicitly set to '' (empty string). + const IS_HOST: bool = false; /// Primary function to implement `Step` logic. /// @@ -159,6 +165,10 @@ impl StepMetadata { Self::new(name, target, Kind::Check) } + pub fn clippy(name: &str, target: TargetSelection) -> Self { + Self::new(name, target, Kind::Clippy) + } + pub fn doc(name: &str, target: TargetSelection) -> Self { Self::new(name, target, Kind::Doc) } @@ -197,6 +207,14 @@ impl StepMetadata { // For everything else, a stage N things gets built by a stage N-1 compiler. .map(|compiler| if self.name == "std" { compiler.stage } else { compiler.stage + 1 })) } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn get_target(&self) -> TargetSelection { + self.target + } } pub struct RunConfig<'a> { @@ -285,7 +303,7 @@ pub fn crate_description(crates: &[impl AsRef]) -> String { struct StepDescription { default: bool, - only_hosts: bool, + is_host: bool, should_run: fn(ShouldRun<'_>) -> ShouldRun<'_>, make_run: fn(RunConfig<'_>), name: &'static str, @@ -487,7 +505,7 @@ impl StepDescription { fn from(kind: Kind) -> StepDescription { StepDescription { default: S::DEFAULT, - only_hosts: S::ONLY_HOSTS, + is_host: S::IS_HOST, should_run: S::should_run, make_run: S::make_run, name: std::any::type_name::(), @@ -503,7 +521,7 @@ impl StepDescription { } // Determine the targets participating in this rule. - let targets = if self.only_hosts { &builder.hosts } else { &builder.targets }; + let targets = if self.is_host { &builder.hosts } else { &builder.targets }; for target in targets { let run = RunConfig { builder, paths: pathsets.clone(), target: *target }; @@ -938,6 +956,9 @@ impl Step for Libdir { } } +#[cfg(feature = "tracing")] +pub const STEP_SPAN_TARGET: &str = "STEP"; + impl<'a> Builder<'a> { fn get_step_descriptions(kind: Kind) -> Vec { macro_rules! describe { @@ -950,7 +971,8 @@ impl<'a> Builder<'a> { compile::Std, compile::Rustc, compile::Assemble, - compile::CodegenBackend, + compile::CraneliftCodegenBackend, + compile::GccCodegenBackend, compile::StartupObjects, tool::BuildManifest, tool::Rustbook, @@ -1020,7 +1042,8 @@ impl<'a> Builder<'a> { Kind::Check | Kind::Fix => describe!( check::Rustc, check::Rustdoc, - check::CodegenBackend, + check::CraneliftCodegenBackend, + check::GccCodegenBackend, check::Clippy, check::Miri, check::CargoMiri, @@ -1141,7 +1164,7 @@ impl<'a> Builder<'a> { dist::JsonDocs, dist::Mingw, dist::Rustc, - dist::CodegenBackend, + dist::CraneliftCodegenBackend, dist::Std, dist::RustcDev, dist::Analysis, @@ -1257,7 +1280,7 @@ impl<'a> Builder<'a> { pub fn new(build: &Build) -> Builder<'_> { let paths = &build.config.paths; let (kind, paths) = match build.config.cmd { - Subcommand::Build => (Kind::Build, &paths[..]), + Subcommand::Build { .. } => (Kind::Build, &paths[..]), Subcommand::Check { .. } => (Kind::Check, &paths[..]), Subcommand::Clippy { .. } => (Kind::Clippy, &paths[..]), Subcommand::Fix => (Kind::Fix, &paths[..]), @@ -1535,12 +1558,43 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s .map(|entry| entry.path()) } - pub fn rustdoc(&self, compiler: Compiler) -> PathBuf { - self.ensure(tool::Rustdoc { compiler }).tool_path + /// Returns a path to `Rustdoc` that "belongs" to the `target_compiler`. + /// It can be either a stage0 rustdoc or a locally built rustdoc that *links* to + /// `target_compiler`. + pub fn rustdoc_for_compiler(&self, target_compiler: Compiler) -> PathBuf { + self.ensure(tool::Rustdoc { target_compiler }) } - pub fn cargo_clippy_cmd(&self, run_compiler: Compiler) -> BootstrapCommand { - if run_compiler.stage == 0 { + pub fn cargo_miri_cmd(&self, run_compiler: Compiler) -> BootstrapCommand { + assert!(run_compiler.stage > 0, "miri can not be invoked at stage 0"); + + let compilers = + RustcPrivateCompilers::new(self, run_compiler.stage, self.build.host_target); + assert_eq!(run_compiler, compilers.target_compiler()); + + // Prepare the tools + let miri = self.ensure(tool::Miri::from_compilers(compilers)); + let cargo_miri = self.ensure(tool::CargoMiri::from_compilers(compilers)); + // Invoke cargo-miri, make sure it can find miri and cargo. + let mut cmd = command(cargo_miri.tool_path); + cmd.env("MIRI", &miri.tool_path); + cmd.env("CARGO", &self.initial_cargo); + // Need to add the `run_compiler` libs. Those are the libs produces *by* `build_compiler` + // in `tool::ToolBuild` step, so they match the Miri we just built. However this means they + // are actually living one stage up, i.e. we are running `stage1-tools-bin/miri` with the + // libraries in `stage1/lib`. This is an unfortunate off-by-1 caused (possibly) by the fact + // that Miri doesn't have an "assemble" step like rustc does that would cross the stage boundary. + // We can't use `add_rustc_lib_path` as that's a NOP on Windows but we do need these libraries + // added to the PATH due to the stage mismatch. + // Also see https://github.com/rust-lang/rust/pull/123192#issuecomment-2028901503. + add_dylib_path(self.rustc_lib_paths(run_compiler), &mut cmd); + cmd + } + + /// Create a Cargo command for running Clippy. + /// The used Clippy is (or in the case of stage 0, already was) built using `build_compiler`. + pub fn cargo_clippy_cmd(&self, build_compiler: Compiler) -> BootstrapCommand { + if build_compiler.stage == 0 { let cargo_clippy = self .config .initial_cargo_clippy @@ -1552,12 +1606,15 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s return cmd; } - let _ = - self.ensure(tool::Clippy { compiler: run_compiler, target: self.build.host_target }); - let cargo_clippy = self - .ensure(tool::CargoClippy { compiler: run_compiler, target: self.build.host_target }); + // If we're linting something with build_compiler stage N, we want to build Clippy stage N + // and use that to lint it. That is why we use the `build_compiler` as the target compiler + // for RustcPrivateCompilers. We will use build compiler stage N-1 to build Clippy stage N. + let compilers = RustcPrivateCompilers::from_target_compiler(self, build_compiler); + + let _ = self.ensure(tool::Clippy::from_compilers(compilers)); + let cargo_clippy = self.ensure(tool::CargoClippy::from_compilers(compilers)); let mut dylib_path = helpers::dylib_path(); - dylib_path.insert(0, self.sysroot(run_compiler).join("lib")); + dylib_path.insert(0, self.sysroot(build_compiler).join("lib")); let mut cmd = command(cargo_clippy.tool_path); cmd.env(helpers::dylib_path_var(), env::join_paths(&dylib_path).unwrap()); @@ -1565,29 +1622,6 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s cmd } - pub fn cargo_miri_cmd(&self, run_compiler: Compiler) -> BootstrapCommand { - assert!(run_compiler.stage > 0, "miri can not be invoked at stage 0"); - // Prepare the tools - let miri = - self.ensure(tool::Miri { compiler: run_compiler, target: self.build.host_target }); - let cargo_miri = - self.ensure(tool::CargoMiri { compiler: run_compiler, target: self.build.host_target }); - // Invoke cargo-miri, make sure it can find miri and cargo. - let mut cmd = command(cargo_miri.tool_path); - cmd.env("MIRI", &miri.tool_path); - cmd.env("CARGO", &self.initial_cargo); - // Need to add the `run_compiler` libs. Those are the libs produces *by* `build_compiler` - // in `tool::ToolBuild` step, so they match the Miri we just built. However this means they - // are actually living one stage up, i.e. we are running `stage0-tools-bin/miri` with the - // libraries in `stage1/lib`. This is an unfortunate off-by-1 caused (possibly) by the fact - // that Miri doesn't have an "assemble" step like rustc does that would cross the stage boundary. - // We can't use `add_rustc_lib_path` as that's a NOP on Windows but we do need these libraries - // added to the PATH due to the stage mismatch. - // Also see https://github.com/rust-lang/rust/pull/123192#issuecomment-2028901503. - add_dylib_path(self.rustc_lib_paths(run_compiler), &mut cmd); - cmd - } - pub fn rustdoc_cmd(&self, compiler: Compiler) -> BootstrapCommand { let mut cmd = command(self.bootstrap_out.join("rustdoc")); cmd.env("RUSTC_STAGE", compiler.stage.to_string()) @@ -1596,7 +1630,7 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s // equivalently to rustc. .env("RUSTDOC_LIBDIR", self.rustc_libdir(compiler)) .env("CFG_RELEASE_CHANNEL", &self.config.channel) - .env("RUSTDOC_REAL", self.rustdoc(compiler)) + .env("RUSTDOC_REAL", self.rustdoc_for_compiler(compiler)) .env("RUSTC_BOOTSTRAP", "1"); cmd.arg("-Wrustdoc::invalid_codeblock_attributes"); @@ -1605,7 +1639,7 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s cmd.arg("-Dwarnings"); } cmd.arg("-Znormalize-docs"); - cmd.args(linker_args(self, compiler.host, LldThreads::Yes, compiler.stage)); + cmd.args(linker_args(self, compiler.host, LldThreads::Yes)); cmd } @@ -1655,21 +1689,52 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s panic!("{}", out); } if let Some(out) = self.cache.get(&step) { - self.verbose_than(1, || println!("{}c {:?}", " ".repeat(stack.len()), step)); - + #[cfg(feature = "tracing")] + { + if let Some(parent) = stack.last() { + let mut graph = self.build.step_graph.borrow_mut(); + graph.register_cached_step(&step, parent, self.config.dry_run()); + } + } return out; } - self.verbose_than(1, || println!("{}> {:?}", " ".repeat(stack.len()), step)); + + #[cfg(feature = "tracing")] + { + let parent = stack.last(); + let mut graph = self.build.step_graph.borrow_mut(); + graph.register_step_execution(&step, parent, self.config.dry_run()); + } + stack.push(Box::new(step.clone())); } #[cfg(feature = "build-metrics")] self.metrics.enter_step(&step, self); + if self.config.print_step_timings && !self.config.dry_run() { + println!("[TIMING:start] {}", pretty_print_step(&step)); + } + let (out, dur) = { let start = Instant::now(); let zero = Duration::new(0, 0); let parent = self.time_spent_on_dependencies.replace(zero); + + #[cfg(feature = "tracing")] + let _span = { + // Keep the target and field names synchronized with `setup_tracing`. + let span = tracing::info_span!( + target: STEP_SPAN_TARGET, + // We cannot use a dynamic name here, so instead we record the actual step name + // in the step_name field. + "step", + step_name = pretty_step_name::(), + args = step_debug_args(&step) + ); + span.entered() + }; + let out = step.clone().run(self); let dur = start.elapsed(); let deps = self.time_spent_on_dependencies.replace(parent + dur); @@ -1677,13 +1742,9 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s }; if self.config.print_step_timings && !self.config.dry_run() { - let step_string = format!("{step:?}"); - let brace_index = step_string.find('{').unwrap_or(0); - let type_string = type_name::(); println!( - "[TIMING] {} {} -- {}.{:03}", - &type_string.strip_prefix("bootstrap::").unwrap_or(type_string), - &step_string[brace_index..], + "[TIMING:end] {} -- {}.{:03}", + pretty_print_step(&step), dur.as_secs(), dur.subsec_millis() ); @@ -1697,7 +1758,6 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s let cur_step = stack.pop().expect("step stack empty"); assert_eq!(cur_step.downcast_ref(), Some(&step)); } - self.verbose_than(1, || println!("{}< {:?}", " ".repeat(self.stack.borrow().len()), step)); self.cache.put(step, out.clone()); out } @@ -1705,11 +1765,11 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s /// Ensure that a given step is built *only if it's supposed to be built by default*, returning /// its output. This will cache the step, so it's safe (and good!) to call this as often as /// needed to ensure that all dependencies are build. - pub(crate) fn ensure_if_default>>( + pub(crate) fn ensure_if_default>( &'a self, step: S, kind: Kind, - ) -> S::Output { + ) -> Option { let desc = StepDescription::from::(kind); let should_run = (desc.should_run)(ShouldRun::new(self, desc.kind)); @@ -1721,7 +1781,7 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s } // Only execute if it's supposed to run as default - if desc.default && should_run.is_really_default() { self.ensure(step) } else { None } + if desc.default && should_run.is_really_default() { Some(self.ensure(step)) } else { None } } /// Checks if any of the "should_run" paths is in the `Builder` paths. @@ -1770,6 +1830,30 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s } } +/// Return qualified step name, e.g. `compile::Rustc`. +pub fn pretty_step_name() -> String { + // Normalize step type path to only keep the module and the type name + let path = type_name::().rsplit("::").take(2).collect::>(); + path.into_iter().rev().collect::>().join("::") +} + +/// Renders `step` using its `Debug` implementation and extract the field arguments out of it. +fn step_debug_args(step: &S) -> String { + let step_dbg_repr = format!("{step:?}"); + + // Some steps do not have any arguments, so they do not have the braces + match (step_dbg_repr.find('{'), step_dbg_repr.rfind('}')) { + (Some(brace_start), Some(brace_end)) => { + step_dbg_repr[brace_start + 1..brace_end - 1].trim().to_string() + } + _ => String::new(), + } +} + +fn pretty_print_step(step: &S) -> String { + format!("{} {{ {} }}", pretty_step_name::(), step_debug_args(step)) +} + impl<'a> AsRef for Builder<'a> { fn as_ref(&self) -> &ExecutionContext { self.exec_ctx() diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index f012645b7ef5..a2fe546c60aa 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -257,38 +257,6 @@ fn parse_config_download_rustc_at(path: &Path, download_rustc: &str, ci: bool) - ) } -mod defaults { - use pretty_assertions::assert_eq; - - use super::{TEST_TRIPLE_1, TEST_TRIPLE_2, configure, first, run_build}; - use crate::Config; - use crate::core::builder::*; - - #[test] - fn doc_default() { - let mut config = configure("doc", &[TEST_TRIPLE_1], &[TEST_TRIPLE_1]); - config.compiler_docs = true; - config.cmd = Subcommand::Doc { open: false, json: false }; - let mut cache = run_build(&[], config); - let a = TargetSelection::from_user(TEST_TRIPLE_1); - - // error_index_generator uses stage 0 to share rustdoc artifacts with the - // rustdoc tool. - assert_eq!(first(cache.all::()), &[doc::ErrorIndex { target: a },]); - assert_eq!( - first(cache.all::()), - &[tool::ErrorIndex { compiler: Compiler::new(1, a) }] - ); - // docs should be built with the stage0 compiler, not with the stage0 artifacts. - // recall that rustdoc is off-by-one: `stage` is the compiler rustdoc is _linked_ to, - // not the one it was built by. - assert_eq!( - first(cache.all::()), - &[tool::Rustdoc { compiler: Compiler::new(1, a) },] - ); - } -} - mod dist { use pretty_assertions::assert_eq; @@ -316,34 +284,6 @@ mod dist { let target = TargetSelection::from_user(TEST_TRIPLE_1); assert!(build.llvm_out(target).ends_with("llvm")); } - - #[test] - fn doc_ci() { - let mut config = configure(&[TEST_TRIPLE_1], &[TEST_TRIPLE_1]); - config.compiler_docs = true; - config.cmd = Subcommand::Doc { open: false, json: false }; - let build = Build::new(config); - let mut builder = Builder::new(&build); - builder.run_step_descriptions(&Builder::get_step_descriptions(Kind::Doc), &[]); - let a = TargetSelection::from_user(TEST_TRIPLE_1); - - // error_index_generator uses stage 1 to share rustdoc artifacts with the - // rustdoc tool. - assert_eq!( - first(builder.cache.all::()), - &[doc::ErrorIndex { target: a },] - ); - assert_eq!( - first(builder.cache.all::()), - &[tool::ErrorIndex { compiler: Compiler::new(1, a) }] - ); - // This is actually stage 1, but Rustdoc::run swaps out the compiler with - // stage minus 1 if --stage is not 0. Very confusing! - assert_eq!( - first(builder.cache.all::()), - &[tool::Rustdoc { compiler: Compiler::new(2, a) },] - ); - } } mod sysroot_target_dirs { @@ -569,36 +509,6 @@ fn test_is_builder_target() { } } -#[test] -fn test_get_tool_rustc_compiler() { - let mut config = configure("build", &[], &[]); - config.download_rustc_commit = None; - let build = Build::new(config); - let builder = Builder::new(&build); - - let target_triple_1 = TargetSelection::from_user(TEST_TRIPLE_1); - - let compiler = Compiler::new(2, target_triple_1); - let expected = Compiler::new(1, target_triple_1); - let actual = tool::get_tool_rustc_compiler(&builder, compiler); - assert_eq!(expected, actual); - - let compiler = Compiler::new(1, target_triple_1); - let expected = Compiler::new(0, target_triple_1); - let actual = tool::get_tool_rustc_compiler(&builder, compiler); - assert_eq!(expected, actual); - - let mut config = configure("build", &[], &[]); - config.download_rustc_commit = Some("".to_owned()); - let build = Build::new(config); - let builder = Builder::new(&build); - - let compiler = Compiler::new(1, target_triple_1); - let expected = Compiler::new(1, target_triple_1); - let actual = tool::get_tool_rustc_compiler(&builder, compiler); - assert_eq!(expected, actual); -} - /// When bootstrap detects a step dependency cycle (which is a bug), its panic /// message should show the actual steps on the stack, not just several copies /// of `Any { .. }`. @@ -657,7 +567,7 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustdoc 0 + [build] rustdoc 1 "); } @@ -680,10 +590,10 @@ mod snapshot { [build] rustc 2 -> std 2 [build] rustc 1 -> std 1 [build] rustc 2 -> std 2 - [build] rustdoc 1 + [build] rustdoc 2 [build] llvm [build] rustc 1 -> rustc 2 - [build] rustdoc 1 + [build] rustdoc 2 "); } @@ -762,6 +672,100 @@ mod snapshot { "); } + #[test] + fn build_compiler_stage_3() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .stage(3) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> rustc 2 + [build] rustc 2 -> std 2 + [build] rustc 2 -> rustc 3 + "); + } + + #[test] + fn build_compiler_stage_3_cross() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .hosts(&[TEST_TRIPLE_1]) + .stage(3) + .render_steps(), @r" + [build] llvm + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> rustc 2 + [build] rustc 1 -> std 1 + [build] rustc 2 -> std 2 + [build] rustc 2 -> std 2 + [build] rustc 2 -> rustc 3 + "); + } + + #[test] + fn build_compiler_stage_3_full_bootstrap() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .stage(3) + .args(&["--set", "build.full-bootstrap=true"]) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> rustc 2 + [build] rustc 2 -> std 2 + [build] rustc 2 -> rustc 3 + "); + } + + #[test] + fn build_compiler_stage_3_cross_full_bootstrap() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .stage(3) + .hosts(&[TEST_TRIPLE_1]) + .args(&["--set", "build.full-bootstrap=true"]) + .render_steps(), @r" + [build] llvm + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> rustc 2 + [build] rustc 2 -> std 2 + [build] rustc 2 -> std 2 + [build] rustc 2 -> rustc 3 + "); + } + + #[test] + fn build_compiler_codegen_backend() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx + .config("build") + .args(&["--set", "rust.codegen-backends=['llvm', 'cranelift']"]) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> rustc_codegen_cranelift 1 + [build] rustc 1 -> std 1 + [build] rustdoc 1 + " + ); + } + #[test] fn build_compiler_tools() { let ctx = TestCtx::new(); @@ -780,7 +784,7 @@ mod snapshot { [build] rustc 1 -> LldWrapper 2 [build] rustc 1 -> LlvmBitcodeLinker 2 [build] rustc 2 -> std 2 - [build] rustdoc 1 + [build] rustdoc 2 " ); } @@ -809,11 +813,26 @@ mod snapshot { [build] rustc 1 -> rustc 2 [build] rustc 1 -> LldWrapper 2 [build] rustc 1 -> LlvmBitcodeLinker 2 - [build] rustdoc 1 + [build] rustdoc 2 " ); } + #[test] + fn build_compiler_lld_opt_in() { + with_lld_opt_in_targets(vec![host_target()], || { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("compiler") + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> LldWrapper 1 + "); + }); + } + #[test] fn build_library_no_explicit_stage() { let ctx = TestCtx::new(); @@ -914,6 +933,19 @@ mod snapshot { "); } + #[test] + fn build_error_index() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .path("error_index_generator") + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> error-index 1 + "); + } + #[test] fn build_bootstrap_tool_no_explicit_stage() { let ctx = TestCtx::new(); @@ -998,8 +1030,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustdoc 0 - [doc] std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] "); } @@ -1026,6 +1058,31 @@ mod snapshot { "); } + #[test] + fn build_cargo() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .paths(&["cargo"]) + .render_steps(), @"[build] rustc 0 -> cargo 1 "); + } + + #[test] + fn build_cargo_cross() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("build") + .paths(&["cargo"]) + .hosts(&[TEST_TRIPLE_1]) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> cargo 2 + "); + } + #[test] fn dist_default_stage() { let ctx = TestCtx::new(); @@ -1043,17 +1100,36 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 + [doc] rustc 1 -> standalone 2 + [build] rustc 1 -> rustc 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw [build] rustc 0 -> GenerateCopyright 1 [dist] rustc @@ -1079,34 +1155,56 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 0 -> LldWrapper 1 [build] rustc 0 -> WasmComponentLd 1 [build] rustc 0 -> LlvmBitcodeLinker 1 [build] rustc 1 -> std 1 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [build] rustdoc 1 + [doc] rustc 1 -> standalone 2 [build] rustc 1 -> rustc 2 [build] rustc 1 -> LldWrapper 2 [build] rustc 1 -> WasmComponentLd 2 [build] rustc 1 -> LlvmBitcodeLinker 2 - [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw + [build] rustc 1 -> rust-analyzer-proc-macro-srv 2 [build] rustc 0 -> GenerateCopyright 1 [dist] rustc [dist] rustc 1 -> std 1 [dist] src <> - [build] rustc 0 -> rustfmt 1 - [build] rustc 0 -> cargo-fmt 1 - [build] rustc 0 -> clippy-driver 1 - [build] rustc 0 -> cargo-clippy 1 - [build] rustc 0 -> miri 1 - [build] rustc 0 -> cargo-miri 1 + [build] rustc 1 -> cargo 2 + [build] rustc 1 -> rust-analyzer 2 + [build] rustc 1 -> rustfmt 2 + [build] rustc 1 -> cargo-fmt 2 + [build] rustc 1 -> clippy-driver 2 + [build] rustc 1 -> cargo-clippy 2 + [build] rustc 1 -> miri 2 + [build] rustc 1 -> cargo-miri 2 "); } @@ -1121,20 +1219,56 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 + [build] rustc 1 -> std 1 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [doc] rustc 1 -> standalone 2 + [doc] rustc 1 -> standalone 2 + [build] rustc 1 -> rustc 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rustdoc (book) + [doc] rust-by-example (book) + [doc] rust-by-example (book) [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs [dist] docs - [doc] std 2 crates=[] - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw [dist] mingw [build] rustc 0 -> GenerateCopyright 1 @@ -1158,25 +1292,46 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 - [build] rustc 0 -> LintDocs 1 + [doc] rustc 1 -> standalone 2 + [build] rustc 1 -> rustc 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [build] llvm [build] rustc 1 -> std 1 - [build] rustc 2 -> std 2 + [build] rustc 1 -> rustc 2 + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) + [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw [build] rustc 0 -> GenerateCopyright 1 [dist] rustc - [build] llvm - [build] rustc 1 -> rustc 2 - [build] rustdoc 1 + [build] rustdoc 2 [dist] rustc [dist] rustc 1 -> std 1 [dist] src <> @@ -1195,29 +1350,66 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> rustc 2 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 - [build] rustc 0 -> LintDocs 1 [build] rustc 1 -> std 1 - [build] rustc 2 -> std 2 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [doc] rustc 1 -> standalone 2 + [doc] rustc 1 -> standalone 2 + [build] rustc 1 -> rustc 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [build] llvm + [build] rustc 1 -> rustc 2 + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rustdoc (book) + [doc] rust-by-example (book) + [doc] rust-by-example (book) + [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] rustc (book) + [doc] cargo (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs [dist] docs - [doc] std 2 crates=[] - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw [dist] mingw [build] rustc 0 -> GenerateCopyright 1 [dist] rustc - [build] llvm - [build] rustc 1 -> rustc 2 - [build] rustdoc 1 + [build] rustdoc 2 [dist] rustc [dist] rustc 1 -> std 1 [dist] rustc 1 -> std 1 @@ -1237,16 +1429,33 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [build] rustdoc 1 [build] rustc 1 -> std 1 + [doc] rustc 1 -> standalone 2 [build] rustc 1 -> rustc 2 - [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw [build] rustc 2 -> std 2 [dist] rustc 2 -> std 2 @@ -1267,37 +1476,112 @@ mod snapshot { .render_steps(), @r" [build] rustc 0 -> UnstableBookGen 1 [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 0 -> WasmComponentLd 1 + [build] rustc 1 -> std 1 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [build] rustdoc 1 [build] rustc 1 -> std 1 + [doc] rustc 1 -> standalone 2 [build] rustc 1 -> rustc 2 [build] rustc 1 -> WasmComponentLd 2 - [build] rustdoc 1 - [doc] std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [build] rustc 2 -> std 2 - [build] rustc 1 -> std 1 - [build] rustc 2 -> std 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] llvm + [build] rustc 1 -> rustc 2 + [build] rustc 1 -> WasmComponentLd 2 + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 [build] rustc 0 -> RustInstaller 1 [dist] docs - [doc] std 2 crates=[] + [doc] rustc 2 -> std 2 crates=[] [dist] mingw - [build] llvm - [build] rustc 1 -> rustc 2 - [build] rustc 1 -> WasmComponentLd 2 - [build] rustdoc 1 + [build] rustdoc 2 + [build] rustc 1 -> rust-analyzer-proc-macro-srv 2 [build] rustc 0 -> GenerateCopyright 1 [dist] rustc [dist] rustc 1 -> std 1 [dist] src <> - [build] rustc 0 -> rustfmt 1 - [build] rustc 0 -> cargo-fmt 1 - [build] rustc 0 -> clippy-driver 1 - [build] rustc 0 -> cargo-clippy 1 - [build] rustc 0 -> miri 1 - [build] rustc 0 -> cargo-miri 1 + [build] rustc 1 -> cargo 2 + [build] rustc 1 -> rust-analyzer 2 + [build] rustc 1 -> rustfmt 2 + [build] rustc 1 -> cargo-fmt 2 + [build] rustc 1 -> clippy-driver 2 + [build] rustc 1 -> cargo-clippy 2 + [build] rustc 1 -> miri 2 + [build] rustc 1 -> cargo-miri 2 [build] rustc 1 -> LlvmBitcodeLinker 2 + [doc] rustc 2 -> std 2 crates=[] + "); + } + + // Enable dist cranelift tarball by default with `x dist` if cranelift is enabled in + // `rust.codegen-backends`. + #[test] + fn dist_cranelift_by_default() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx + .config("dist") + .args(&["--set", "rust.codegen-backends=['llvm', 'cranelift']"]) + .render_steps(), @r" + [build] rustc 0 -> UnstableBookGen 1 + [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> rustc_codegen_cranelift 1 + [build] rustc 1 -> std 1 + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [build] rustdoc 1 + [doc] rustc 1 -> standalone 2 + [build] rustc 1 -> rustc 2 + [build] rustc 1 -> rustc_codegen_cranelift 2 + [build] rustdoc 2 + [doc] rustc 2 -> std 2 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 1 -> error-index 2 + [doc] rustc 1 -> error-index 2 + [doc] nomicon (book) + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) + [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 1 -> releases 2 + [build] rustc 0 -> RustInstaller 1 + [dist] docs + [doc] rustc 2 -> std 2 crates=[] + [dist] mingw + [build] rustc 0 -> GenerateCopyright 1 + [dist] rustc + [dist] rustc 1 -> rustc_codegen_cranelift 2 + [dist] rustc 1 -> std 1 + [dist] src <> "); } @@ -1307,11 +1591,7 @@ mod snapshot { insta::assert_snapshot!( ctx.config("check") .path("compiler") - .render_steps(), @r" - [check] rustc 0 -> rustc 1 - [check] rustc 0 -> rustc_codegen_cranelift 1 - [check] rustc 0 -> rustc_codegen_gcc 1 - "); + .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); } #[test] @@ -1320,9 +1600,7 @@ mod snapshot { insta::assert_snapshot!( ctx.config("check") .path("rustc") - .render_steps(), @r" - [check] rustc 0 -> rustc 1 - "); + .render_steps(), @"[check] rustc 0 -> rustc 1 (1 crates)"); } #[test] @@ -1339,11 +1617,7 @@ mod snapshot { ctx.config("check") .path("compiler") .stage(1) - .render_steps(), @r" - [check] rustc 0 -> rustc 1 - [check] rustc 0 -> rustc_codegen_cranelift 1 - [check] rustc 0 -> rustc_codegen_gcc 1 - "); + .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); } #[test] @@ -1357,9 +1631,7 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [check] rustc 1 -> rustc 2 - [check] rustc 1 -> rustc_codegen_cranelift 2 - [check] rustc 1 -> rustc_codegen_gcc 2 + [check] rustc 1 -> rustc 2 (74 crates) "); } @@ -1374,7 +1646,8 @@ mod snapshot { [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 1 -> std 1 + [check] rustc 1 -> std 1 + [check] rustc 1 -> rustc 2 (74 crates) [check] rustc 1 -> rustc 2 [check] rustc 1 -> Rustdoc 2 [check] rustc 1 -> rustc_codegen_cranelift 2 @@ -1470,11 +1743,7 @@ mod snapshot { ctx.config("check") .paths(&["library", "compiler"]) .args(&args) - .render_steps(), @r" - [check] rustc 0 -> rustc 1 - [check] rustc 0 -> rustc_codegen_cranelift 1 - [check] rustc 0 -> rustc_codegen_gcc 1 - "); + .render_steps(), @"[check] rustc 0 -> rustc 1 (74 crates)"); } #[test] @@ -1558,7 +1827,6 @@ mod snapshot { .render_steps(), @r" [check] rustc 0 -> rustc 1 [check] rustc 0 -> rustc_codegen_cranelift 1 - [check] rustc 0 -> rustc_codegen_gcc 1 "); } @@ -1610,6 +1878,95 @@ mod snapshot { steps.assert_contains_fuzzy(StepMetadata::build("rustc", host)); } + #[test] + fn test_cargo_stage_1() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("test") + .path("cargo") + .render_steps(), @r" + [build] rustc 0 -> cargo 1 + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustdoc 1 + [build] rustdoc 0 + "); + } + + #[test] + fn test_cargo_stage_2() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("test") + .path("cargo") + .stage(2) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 1 -> cargo 2 + [build] rustc 1 -> rustc 2 + [build] rustc 2 -> std 2 + [build] rustdoc 2 + [build] rustdoc 1 + "); + } + + #[test] + fn test_cargotest() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("test") + .path("cargotest") + .render_steps(), @r" + [build] rustc 0 -> cargo 1 + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> CargoTest 1 + [build] rustdoc 1 + [test] cargotest 1 + "); + } + + #[test] + fn doc_all() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .render_steps(), @r" + [build] rustc 0 -> UnstableBookGen 1 + [build] rustc 0 -> Rustbook 1 + [doc] unstable-book (book) + [doc] book (book) + [doc] book/first-edition (book) + [doc] book/second-edition (book) + [doc] book/2018-edition (book) + [build] rustdoc 0 + [doc] rustc 0 -> standalone 1 + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustc 0 -> error-index 1 + [doc] rustc 0 -> error-index 1 + [doc] nomicon (book) + [build] rustc 1 -> std 1 + [doc] rustc 1 -> reference (book) 2 + [doc] rustdoc (book) + [doc] rust-by-example (book) + [build] rustc 0 -> LintDocs 1 + [doc] rustc (book) + [doc] cargo (book) + [doc] clippy (book) + [doc] embedded-book (book) + [doc] edition-guide (book) + [doc] style-guide (book) + [doc] rustc 0 -> releases 1 + "); + } + #[test] fn doc_library() { let ctx = TestCtx::new(); @@ -1619,8 +1976,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustdoc 0 - [doc] std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] "); } @@ -1633,8 +1990,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustdoc 0 - [doc] std 1 crates=[core] + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[core] "); } @@ -1648,26 +2005,11 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustdoc 0 - [doc] std 1 crates=[core] + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[core] "); } - #[test] - fn test_lld_opt_in() { - with_lld_opt_in_targets(vec![host_target()], || { - let ctx = TestCtx::new(); - insta::assert_snapshot!( - ctx.config("build") - .path("compiler") - .render_steps(), @r" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 0 -> LldWrapper 1 - "); - }); - } - #[test] fn doc_library_no_std_target() { let ctx = TestCtx::new(); @@ -1678,8 +2020,8 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 - [build] rustdoc 0 - [doc] std 1 crates=[alloc,core] + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[alloc,core] "); } @@ -1694,8 +2036,94 @@ mod snapshot { .render_steps(), @r" [build] llvm [build] rustc 0 -> rustc 1 + [build] rustdoc 1 + [doc] rustc 1 -> std 1 crates=[alloc,core] + "); + } + + #[test] + #[should_panic] + fn doc_compiler_stage_0() { + let ctx = TestCtx::new(); + ctx.config("doc").path("compiler").stage(0).run(); + } + + #[test] + fn doc_compiler_stage_1() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .path("compiler") + .stage(1) + .render_steps(), @r" [build] rustdoc 0 - [doc] std 1 crates=[alloc,core] + [build] llvm + [doc] rustc 0 -> rustc 1 + "); + } + + #[test] + fn doc_compiler_stage_2() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .path("compiler") + .stage(2) + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustdoc 1 + [doc] rustc 1 -> rustc 2 + "); + } + + #[test] + #[should_panic] + fn doc_compiletest_stage_0() { + let ctx = TestCtx::new(); + ctx.config("doc").path("src/tools/compiletest").stage(0).run(); + } + + #[test] + fn doc_compiletest_stage_1() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .path("src/tools/compiletest") + .stage(1) + .render_steps(), @r" + [build] rustdoc 0 + [doc] rustc 0 -> Compiletest 1 + "); + } + + #[test] + fn doc_compiletest_stage_2() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .path("src/tools/compiletest") + .stage(2) + .render_steps(), @r" + [build] rustdoc 0 + [doc] rustc 0 -> Compiletest 1 + "); + } + + // Reference should be auto-bumped to stage 2. + #[test] + fn doc_reference() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .path("reference") + .render_steps(), @r" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Rustbook 1 + [doc] rustc 1 -> reference (book) 2 "); } } diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 6055876c4757..d0647537e566 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -32,13 +32,18 @@ use tracing::{instrument, span}; use crate::core::build_steps::llvm; use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS; pub use crate::core::config::flags::Subcommand; -use crate::core::config::flags::{Color, Flags}; +use crate::core::config::flags::{Color, Flags, Warnings}; use crate::core::config::target_selection::TargetSelectionList; use crate::core::config::toml::TomlConfig; use crate::core::config::toml::build::{Build, Tool}; use crate::core::config::toml::change_id::ChangeId; +use crate::core::config::toml::dist::Dist; +use crate::core::config::toml::gcc::Gcc; +use crate::core::config::toml::install::Install; +use crate::core::config::toml::llvm::Llvm; use crate::core::config::toml::rust::{ - LldMode, RustOptimize, check_incompatible_options_for_ci_rustc, + LldMode, Rust, RustOptimize, check_incompatible_options_for_ci_rustc, + default_lld_opt_in_targets, parse_codegen_backends, }; use crate::core::config::toml::target::Target; use crate::core::config::{ @@ -89,7 +94,6 @@ pub struct Config { pub ccache: Option, /// Call Build::ninja() instead of this. pub ninja_in_file: bool, - pub verbose: usize, pub submodules: Option, pub compiler_docs: bool, pub library_docs_private_items: bool, @@ -442,23 +446,13 @@ impl Config { enable_bolt_settings: flags_enable_bolt_settings, skip_stage0_validation: flags_skip_stage0_validation, reproducible_artifact: flags_reproducible_artifact, - paths: mut flags_paths, + paths: flags_paths, set: flags_set, - free_args: mut flags_free_args, + free_args: flags_free_args, ci: flags_ci, skip_std_check_if_no_download_rustc: flags_skip_std_check_if_no_download_rustc, } = flags; - let mut config = Config::default_opts(); - let mut exec_ctx = ExecutionContext::new(); - exec_ctx.set_verbose(flags_verbose); - exec_ctx.set_fail_fast(flags_cmd.fail_fast()); - - config.exec_ctx = exec_ctx; - - // Set flags. - config.paths = std::mem::take(&mut flags_paths); - #[cfg(feature = "tracing")] span!( target: "CONFIG_HANDLING", @@ -469,14 +463,251 @@ impl Config { "flags.exclude" = ?flags_exclude ); - #[cfg(feature = "tracing")] - span!( - target: "CONFIG_HANDLING", - tracing::Level::TRACE, - "normalizing and combining `flag.skip`/`flag.exclude` paths", - "config.skip" = ?config.skip, + // First initialize the bare minimum that we need for further operation - source directory + // and execution context. + let mut config = Config::default_opts(); + let exec_ctx = ExecutionContext::new(flags_verbose, flags_cmd.fail_fast()); + + config.exec_ctx = exec_ctx; + + if let Some(src) = compute_src_directory(flags_src, &config.exec_ctx) { + config.src = src; + } + + // Now load the TOML config, as soon as possible + let (mut toml, toml_path) = load_toml_config(&config.src, flags_config, &get_toml); + config.config = toml_path.clone(); + + postprocess_toml( + &mut toml, + &config.src, + toml_path, + config.exec_ctx(), + &flags_set, + &get_toml, ); + // Now override TOML values with flags, to make sure that we won't later override flags with + // TOML values by accident instead, because flags have higher priority. + let Build { + description: build_description, + build: mut build_build, + host: build_host, + target: build_target, + build_dir: build_build_dir, + cargo: mut build_cargo, + rustc: mut build_rustc, + rustfmt: build_rustfmt, + cargo_clippy: build_cargo_clippy, + docs: build_docs, + compiler_docs: build_compiler_docs, + library_docs_private_items: build_library_docs_private_items, + docs_minification: build_docs_minification, + submodules: build_submodules, + gdb: build_gdb, + lldb: build_lldb, + nodejs: build_nodejs, + npm: build_npm, + python: build_python, + reuse: build_reuse, + locked_deps: build_locked_deps, + vendor: build_vendor, + full_bootstrap: build_full_bootstrap, + bootstrap_cache_path: build_bootstrap_cache_path, + extended: build_extended, + tools: build_tools, + tool: build_tool, + verbose: build_verbose, + sanitizers: build_sanitizers, + profiler: build_profiler, + cargo_native_static: build_cargo_native_static, + low_priority: build_low_priority, + configure_args: build_configure_args, + local_rebuild: build_local_rebuild, + print_step_timings: build_print_step_timings, + print_step_rusage: build_print_step_rusage, + check_stage: build_check_stage, + doc_stage: build_doc_stage, + build_stage: build_build_stage, + test_stage: build_test_stage, + install_stage: build_install_stage, + dist_stage: build_dist_stage, + bench_stage: build_bench_stage, + patch_binaries_for_nix: build_patch_binaries_for_nix, + // This field is only used by bootstrap.py + metrics: _, + android_ndk: build_android_ndk, + optimized_compiler_builtins: build_optimized_compiler_builtins, + jobs: mut build_jobs, + compiletest_diff_tool: build_compiletest_diff_tool, + compiletest_use_stage0_libtest: build_compiletest_use_stage0_libtest, + tidy_extra_checks: build_tidy_extra_checks, + ccache: build_ccache, + exclude: build_exclude, + compiletest_allow_stage0: build_compiletest_allow_stage0, + } = toml.build.unwrap_or_default(); + + let Install { + prefix: install_prefix, + sysconfdir: install_sysconfdir, + docdir: install_docdir, + bindir: install_bindir, + libdir: install_libdir, + mandir: install_mandir, + datadir: install_datadir, + } = toml.install.unwrap_or_default(); + + let Rust { + optimize: rust_optimize, + debug: rust_debug, + codegen_units: rust_codegen_units, + codegen_units_std: rust_codegen_units_std, + rustc_debug_assertions: rust_rustc_debug_assertions, + std_debug_assertions: rust_std_debug_assertions, + tools_debug_assertions: rust_tools_debug_assertions, + overflow_checks: rust_overflow_checks, + overflow_checks_std: rust_overflow_checks_std, + debug_logging: rust_debug_logging, + debuginfo_level: rust_debuginfo_level, + debuginfo_level_rustc: rust_debuginfo_level_rustc, + debuginfo_level_std: rust_debuginfo_level_std, + debuginfo_level_tools: rust_debuginfo_level_tools, + debuginfo_level_tests: rust_debuginfo_level_tests, + backtrace: rust_backtrace, + incremental: rust_incremental, + randomize_layout: rust_randomize_layout, + default_linker: rust_default_linker, + channel: rust_channel, + musl_root: rust_musl_root, + rpath: rust_rpath, + verbose_tests: rust_verbose_tests, + optimize_tests: rust_optimize_tests, + codegen_tests: rust_codegen_tests, + omit_git_hash: rust_omit_git_hash, + dist_src: rust_dist_src, + save_toolstates: rust_save_toolstates, + codegen_backends: rust_codegen_backends, + lld: rust_lld_enabled, + llvm_tools: rust_llvm_tools, + llvm_bitcode_linker: rust_llvm_bitcode_linker, + deny_warnings: rust_deny_warnings, + backtrace_on_ice: rust_backtrace_on_ice, + verify_llvm_ir: rust_verify_llvm_ir, + thin_lto_import_instr_limit: rust_thin_lto_import_instr_limit, + remap_debuginfo: rust_remap_debuginfo, + jemalloc: rust_jemalloc, + test_compare_mode: rust_test_compare_mode, + llvm_libunwind: rust_llvm_libunwind, + control_flow_guard: rust_control_flow_guard, + ehcont_guard: rust_ehcont_guard, + new_symbol_mangling: rust_new_symbol_mangling, + profile_generate: rust_profile_generate, + profile_use: rust_profile_use, + download_rustc: rust_download_rustc, + lto: rust_lto, + validate_mir_opts: rust_validate_mir_opts, + frame_pointers: rust_frame_pointers, + stack_protector: rust_stack_protector, + strip: rust_strip, + lld_mode: rust_lld_mode, + std_features: rust_std_features, + } = toml.rust.unwrap_or_default(); + + let Llvm { + optimize: llvm_optimize, + thin_lto: llvm_thin_lto, + release_debuginfo: llvm_release_debuginfo, + assertions: llvm_assertions, + tests: llvm_tests, + enzyme: llvm_enzyme, + plugins: llvm_plugin, + static_libstdcpp: llvm_static_libstdcpp, + libzstd: llvm_libzstd, + ninja: llvm_ninja, + targets: llvm_targets, + experimental_targets: llvm_experimental_targets, + link_jobs: llvm_link_jobs, + link_shared: llvm_link_shared, + version_suffix: llvm_version_suffix, + clang_cl: llvm_clang_cl, + cflags: llvm_cflags, + cxxflags: llvm_cxxflags, + ldflags: llvm_ldflags, + use_libcxx: llvm_use_libcxx, + use_linker: llvm_use_linker, + allow_old_toolchain: llvm_allow_old_toolchain, + offload: llvm_offload, + polly: llvm_polly, + clang: llvm_clang, + enable_warnings: llvm_enable_warnings, + download_ci_llvm: llvm_download_ci_llvm, + build_config: llvm_build_config, + } = toml.llvm.unwrap_or_default(); + + let Dist { + sign_folder: dist_sign_folder, + upload_addr: dist_upload_addr, + src_tarball: dist_src_tarball, + compression_formats: dist_compression_formats, + compression_profile: dist_compression_profile, + include_mingw_linker: dist_include_mingw_linker, + vendor: dist_vendor, + } = toml.dist.unwrap_or_default(); + + let Gcc { download_ci_gcc: gcc_download_ci_gcc } = toml.gcc.unwrap_or_default(); + + if cfg!(test) { + // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the + // same ones used to call the tests (if custom ones are not defined in the toml). If we + // don't do that, bootstrap will use its own detection logic to find a suitable rustc + // and Cargo, which doesn't work when the caller is specìfying a custom local rustc or + // Cargo in their bootstrap.toml. + build_rustc = build_rustc.take().or(std::env::var_os("RUSTC").map(|p| p.into())); + build_cargo = build_cargo.take().or(std::env::var_os("CARGO").map(|p| p.into())); + } + + build_jobs = flags_jobs.or(build_jobs); + build_build = flags_build.or(build_build); + + let build_dir = flags_build_dir.or(build_build_dir.map(PathBuf::from)); + let host = if let Some(TargetSelectionList(hosts)) = flags_host { + Some(hosts) + } else { + build_host + .map(|file_host| file_host.iter().map(|h| TargetSelection::from_user(h)).collect()) + }; + let target = if let Some(TargetSelectionList(targets)) = flags_target { + Some(targets) + } else { + build_target.map(|file_target| { + file_target.iter().map(|h| TargetSelection::from_user(h)).collect() + }) + }; + + if let Some(rustc) = &build_rustc + && !flags_skip_stage0_validation + { + check_stage0_version(rustc, "rustc", &config.src, config.exec_ctx()); + } + if let Some(cargo) = &build_cargo + && !flags_skip_stage0_validation + { + check_stage0_version(cargo, "cargo", &config.src, config.exec_ctx()); + } + + // Prefer CLI verbosity flags if set (`flags_verbose` > 0), otherwise take the value from + // TOML. + config + .exec_ctx + .set_verbosity(cmp::max(build_verbose.unwrap_or_default() as u8, flags_verbose)); + + let mut paths: Vec = flags_skip.into_iter().chain(flags_exclude).collect(); + if let Some(exclude) = build_exclude { + paths.extend(exclude); + } + + // Set config values based on flags. + config.paths = flags_paths; config.include_default_paths = flags_include_default_paths; config.rustc_error_format = flags_rustc_error_format; config.json_output = flags_json_output; @@ -489,7 +720,7 @@ impl Config { config.keep_stage = flags_keep_stage; config.keep_stage_std = flags_keep_stage_std; config.color = flags_color; - config.free_args = std::mem::take(&mut flags_free_args); + config.free_args = flags_free_args; config.llvm_profile_use = flags_llvm_profile_use; config.llvm_profile_generate = flags_llvm_profile_generate; config.enable_bolt_settings = flags_enable_bolt_settings; @@ -499,53 +730,6 @@ impl Config { // Infer the rest of the configuration. - if let Some(src) = flags_src { - config.src = src - } else { - // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary, - // running on a completely different machine from where it was compiled. - let mut cmd = helpers::git(None); - // NOTE: we cannot support running from outside the repository because the only other path we have available - // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally. - // We still support running outside the repository if we find we aren't in a git directory. - - // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path, - // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap - // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path. - cmd.arg("rev-parse").arg("--show-cdup"); - // Discard stderr because we expect this to fail when building from a tarball. - let output = cmd.allow_failure().run_capture_stdout(&config); - if output.is_success() { - let git_root_relative = output.stdout(); - // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes, - // and to resolve any relative components. - let git_root = env::current_dir() - .unwrap() - .join(PathBuf::from(git_root_relative.trim())) - .canonicalize() - .unwrap(); - let s = git_root.to_str().unwrap(); - - // Bootstrap is quite bad at handling /? in front of paths - let git_root = match s.strip_prefix("\\\\?\\") { - Some(p) => PathBuf::from(p), - None => git_root, - }; - // If this doesn't have at least `stage0`, we guessed wrong. This can happen when, - // for example, the build directory is inside of another unrelated git directory. - // In that case keep the original `CARGO_MANIFEST_DIR` handling. - // - // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside - // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1. - if git_root.join("src").join("stage0").exists() { - config.src = git_root; - } - } else { - // We're building from a tarball, not git sources. - // We don't support pre-downloaded bootstrap in this case. - } - } - if cfg!(test) { // Use the build directory of the original x.py invocation, so that we can set `initial_rustc` properly. config.out = Path::new( @@ -556,220 +740,11 @@ impl Config { .to_path_buf(); } + config.compiletest_allow_stage0 = build_compiletest_allow_stage0.unwrap_or(false); config.stage0_metadata = build_helper::stage0_parser::parse_stage0_file(); - // Locate the configuration file using the following priority (first match wins): - // 1. `--config ` (explicit flag) - // 2. `RUST_BOOTSTRAP_CONFIG` environment variable - // 3. `./bootstrap.toml` (local file) - // 4. `/bootstrap.toml` - // 5. `./config.toml` (fallback for backward compatibility) - // 6. `/config.toml` - let toml_path = flags_config - .clone() - .or_else(|| env::var_os("RUST_BOOTSTRAP_CONFIG").map(PathBuf::from)); - let using_default_path = toml_path.is_none(); - let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml")); - - if using_default_path && !toml_path.exists() { - toml_path = config.src.join(PathBuf::from("bootstrap.toml")); - if !toml_path.exists() { - toml_path = PathBuf::from("config.toml"); - if !toml_path.exists() { - toml_path = config.src.join(PathBuf::from("config.toml")); - } - } - } - - // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path, - // but not if `bootstrap.toml` hasn't been created. - let mut toml = if !using_default_path || toml_path.exists() { - config.config = Some(if cfg!(not(test)) { - toml_path = toml_path.canonicalize().unwrap(); - toml_path.clone() - } else { - toml_path.clone() - }); - get_toml(&toml_path).unwrap_or_else(|e| { - eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display()); - exit!(2); - }) - } else { - config.config = None; - TomlConfig::default() - }; - - if cfg!(test) { - // When configuring bootstrap for tests, make sure to set the rustc and Cargo to the - // same ones used to call the tests (if custom ones are not defined in the toml). If we - // don't do that, bootstrap will use its own detection logic to find a suitable rustc - // and Cargo, which doesn't work when the caller is specìfying a custom local rustc or - // Cargo in their bootstrap.toml. - let build = toml.build.get_or_insert_with(Default::default); - build.rustc = build.rustc.take().or(std::env::var_os("RUSTC").map(|p| p.into())); - build.cargo = build.cargo.take().or(std::env::var_os("CARGO").map(|p| p.into())); - } - - if config.git_info(false, &config.src).is_from_tarball() && toml.profile.is_none() { - toml.profile = Some("dist".into()); - } - - // Reverse the list to ensure the last added config extension remains the most dominant. - // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml". - // - // This must be handled before applying the `profile` since `include`s should always take - // precedence over `profile`s. - for include_path in toml.include.clone().unwrap_or_default().iter().rev() { - let include_path = toml_path.parent().unwrap().join(include_path); - - let included_toml = get_toml(&include_path).unwrap_or_else(|e| { - eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); - exit!(2); - }); - toml.merge( - Some(include_path), - &mut Default::default(), - included_toml, - ReplaceOpt::IgnoreDuplicate, - ); - } - - if let Some(include) = &toml.profile { - // Allows creating alias for profile names, allowing - // profiles to be renamed while maintaining back compatibility - // Keep in sync with `profile_aliases` in bootstrap.py - let profile_aliases = HashMap::from([("user", "dist")]); - let include = match profile_aliases.get(include.as_str()) { - Some(alias) => alias, - None => include.as_str(), - }; - let mut include_path = config.src.clone(); - include_path.push("src"); - include_path.push("bootstrap"); - include_path.push("defaults"); - include_path.push(format!("bootstrap.{include}.toml")); - let included_toml = get_toml(&include_path).unwrap_or_else(|e| { - eprintln!( - "ERROR: Failed to parse default config profile at '{}': {e}", - include_path.display() - ); - exit!(2); - }); - toml.merge( - Some(include_path), - &mut Default::default(), - included_toml, - ReplaceOpt::IgnoreDuplicate, - ); - } - - let mut override_toml = TomlConfig::default(); - for option in flags_set.iter() { - fn get_table(option: &str) -> Result { - toml::from_str(option).and_then(|table: toml::Value| TomlConfig::deserialize(table)) - } - - let mut err = match get_table(option) { - Ok(v) => { - override_toml.merge( - None, - &mut Default::default(), - v, - ReplaceOpt::ErrorOnDuplicate, - ); - continue; - } - Err(e) => e, - }; - // We want to be able to set string values without quotes, - // like in `configure.py`. Try adding quotes around the right hand side - if let Some((key, value)) = option.split_once('=') - && !value.contains('"') - { - match get_table(&format!(r#"{key}="{value}""#)) { - Ok(v) => { - override_toml.merge( - None, - &mut Default::default(), - v, - ReplaceOpt::ErrorOnDuplicate, - ); - continue; - } - Err(e) => err = e, - } - } - eprintln!("failed to parse override `{option}`: `{err}"); - exit!(2) - } - toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override); - config.change_id = toml.change_id.inner; - let Build { - description, - build, - host, - target, - build_dir, - cargo, - rustc, - rustfmt, - cargo_clippy, - docs, - compiler_docs, - library_docs_private_items, - docs_minification, - submodules, - gdb, - lldb, - nodejs, - npm, - python, - reuse, - locked_deps, - vendor, - full_bootstrap, - bootstrap_cache_path, - extended, - tools, - tool, - verbose, - sanitizers, - profiler, - cargo_native_static, - low_priority, - configure_args, - local_rebuild, - print_step_timings, - print_step_rusage, - check_stage, - doc_stage, - build_stage, - test_stage, - install_stage, - dist_stage, - bench_stage, - patch_binaries_for_nix, - // This field is only used by bootstrap.py - metrics: _, - android_ndk, - optimized_compiler_builtins, - jobs, - compiletest_diff_tool, - compiletest_allow_stage0, - compiletest_use_stage0_libtest, - tidy_extra_checks, - ccache, - exclude, - } = toml.build.unwrap_or_default(); - - let mut paths: Vec = flags_skip.into_iter().chain(flags_exclude).collect(); - - if let Some(exclude) = exclude { - paths.extend(exclude); - } - config.skip = paths .into_iter() .map(|p| { @@ -784,15 +759,20 @@ impl Config { }) .collect(); - config.jobs = Some(threads_from_config(flags_jobs.unwrap_or(jobs.unwrap_or(0)))); + #[cfg(feature = "tracing")] + span!( + target: "CONFIG_HANDLING", + tracing::Level::TRACE, + "normalizing and combining `flag.skip`/`flag.exclude` paths", + "config.skip" = ?config.skip, + ); - if let Some(flags_build) = flags_build { - config.host_target = TargetSelection::from_user(&flags_build); - } else if let Some(file_build) = build { - config.host_target = TargetSelection::from_user(&file_build); - }; + config.jobs = Some(threads_from_config(build_jobs.unwrap_or(0))); + if let Some(build) = build_build { + config.host_target = TargetSelection::from_user(&build); + } - set(&mut config.out, flags_build_dir.or_else(|| build_dir.map(PathBuf::from))); + set(&mut config.out, build_dir); // NOTE: Bootstrap spawns various commands with different working directories. // To avoid writing to random places on the file system, `config.out` needs to be an absolute path. if !config.out.is_absolute() { @@ -800,21 +780,13 @@ impl Config { config.out = absolute(&config.out).expect("can't make empty path absolute"); } - if cargo_clippy.is_some() && rustc.is_none() { + if build_cargo_clippy.is_some() && build_rustc.is_none() { println!( "WARNING: Using `build.cargo-clippy` without `build.rustc` usually fails due to toolchain conflict." ); } - config.patch_binaries_for_nix = patch_binaries_for_nix; - config.bootstrap_cache_path = bootstrap_cache_path; - config.llvm_assertions = - toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false)); - - config.initial_rustc = if let Some(rustc) = rustc { - if !flags_skip_stage0_validation { - config.check_stage0_version(&rustc, "rustc"); - } + config.initial_rustc = if let Some(rustc) = build_rustc { rustc } else { let dwn_ctx = DownloadContext::from(&config); @@ -836,12 +808,9 @@ impl Config { .trim() )); - config.initial_cargo_clippy = cargo_clippy; + config.initial_cargo_clippy = build_cargo_clippy; - config.initial_cargo = if let Some(cargo) = cargo { - if !flags_skip_stage0_validation { - config.check_stage0_version(&cargo, "cargo"); - } + config.initial_cargo = if let Some(cargo) = build_cargo { cargo } else { let dwn_ctx = DownloadContext::from(&config); @@ -856,62 +825,60 @@ impl Config { config.out = dir; } - config.hosts = if let Some(TargetSelectionList(arg_host)) = flags_host { - arg_host - } else if let Some(file_host) = host { - file_host.iter().map(|h| TargetSelection::from_user(h)).collect() - } else { - vec![config.host_target] - }; - config.targets = if let Some(TargetSelectionList(arg_target)) = flags_target { - arg_target - } else if let Some(file_target) = target { - file_target.iter().map(|h| TargetSelection::from_user(h)).collect() + config.hosts = if let Some(hosts) = host { hosts } else { vec![config.host_target] }; + config.targets = if let Some(targets) = target { + targets } else { // If target is *not* configured, then default to the host // toolchains. config.hosts.clone() }; - config.nodejs = nodejs.map(PathBuf::from); - config.npm = npm.map(PathBuf::from); - config.gdb = gdb.map(PathBuf::from); - config.lldb = lldb.map(PathBuf::from); - config.python = python.map(PathBuf::from); - config.reuse = reuse.map(PathBuf::from); - config.submodules = submodules; - config.android_ndk = android_ndk; - set(&mut config.low_priority, low_priority); - set(&mut config.compiler_docs, compiler_docs); - set(&mut config.library_docs_private_items, library_docs_private_items); - set(&mut config.docs_minification, docs_minification); - set(&mut config.docs, docs); - set(&mut config.locked_deps, locked_deps); - set(&mut config.full_bootstrap, full_bootstrap); - set(&mut config.extended, extended); - config.tools = tools; - set(&mut config.tool, tool); - set(&mut config.verbose, verbose); - set(&mut config.sanitizers, sanitizers); - set(&mut config.profiler, profiler); - set(&mut config.cargo_native_static, cargo_native_static); - set(&mut config.configure_args, configure_args); - set(&mut config.local_rebuild, local_rebuild); - set(&mut config.print_step_timings, print_step_timings); - set(&mut config.print_step_rusage, print_step_rusage); - - config.verbose = cmp::max(config.verbose, flags_verbose as usize); + config.nodejs = build_nodejs.map(PathBuf::from); + config.npm = build_npm.map(PathBuf::from); + config.gdb = build_gdb.map(PathBuf::from); + config.lldb = build_lldb.map(PathBuf::from); + config.python = build_python.map(PathBuf::from); + config.reuse = build_reuse.map(PathBuf::from); + config.submodules = build_submodules; + config.android_ndk = build_android_ndk; + config.bootstrap_cache_path = build_bootstrap_cache_path; + set(&mut config.low_priority, build_low_priority); + set(&mut config.compiler_docs, build_compiler_docs); + set(&mut config.library_docs_private_items, build_library_docs_private_items); + set(&mut config.docs_minification, build_docs_minification); + set(&mut config.docs, build_docs); + set(&mut config.locked_deps, build_locked_deps); + set(&mut config.full_bootstrap, build_full_bootstrap); + set(&mut config.extended, build_extended); + config.tools = build_tools; + set(&mut config.tool, build_tool); + set(&mut config.sanitizers, build_sanitizers); + set(&mut config.profiler, build_profiler); + set(&mut config.cargo_native_static, build_cargo_native_static); + set(&mut config.configure_args, build_configure_args); + set(&mut config.local_rebuild, build_local_rebuild); + set(&mut config.print_step_timings, build_print_step_timings); + set(&mut config.print_step_rusage, build_print_step_rusage); + config.patch_binaries_for_nix = build_patch_binaries_for_nix; // Verbose flag is a good default for `rust.verbose-tests`. config.verbose_tests = config.is_verbose(); - config.apply_install_config(toml.install); + config.prefix = install_prefix.map(PathBuf::from); + config.sysconfdir = install_sysconfdir.map(PathBuf::from); + config.datadir = install_datadir.map(PathBuf::from); + config.docdir = install_docdir.map(PathBuf::from); + set(&mut config.bindir, install_bindir.map(PathBuf::from)); + config.libdir = install_libdir.map(PathBuf::from); + config.mandir = install_mandir.map(PathBuf::from); + + config.llvm_assertions = llvm_assertions.unwrap_or(false); let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel"))); let ci_channel = file_content.trim_end(); - let toml_channel = toml.rust.as_ref().and_then(|r| r.channel.clone()); - let is_user_configured_rust_channel = match toml_channel { + let is_user_configured_rust_channel = match rust_channel { Some(channel) if channel == "auto-detect" => { config.channel = ci_channel.into(); true @@ -924,25 +891,29 @@ impl Config { }; let default = config.channel == "dev"; - config.omit_git_hash = toml.rust.as_ref().and_then(|r| r.omit_git_hash).unwrap_or(default); + config.omit_git_hash = rust_omit_git_hash.unwrap_or(default); - config.rust_info = config.git_info(config.omit_git_hash, &config.src); + config.rust_info = git_info(&config.exec_ctx, config.omit_git_hash, &config.src); config.cargo_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/cargo")); - config.rust_analyzer_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/rust-analyzer")); + git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/cargo")); + config.rust_analyzer_info = git_info( + &config.exec_ctx, + config.omit_git_hash, + &config.src.join("src/tools/rust-analyzer"), + ); config.clippy_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/clippy")); + git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/clippy")); config.miri_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/miri")); + git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/miri")); config.rustfmt_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/rustfmt")); + git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/rustfmt")); config.enzyme_info = - config.git_info(config.omit_git_hash, &config.src.join("src/tools/enzyme")); - config.in_tree_llvm_info = config.git_info(false, &config.src.join("src/llvm-project")); - config.in_tree_gcc_info = config.git_info(false, &config.src.join("src/gcc")); + git_info(&config.exec_ctx, config.omit_git_hash, &config.src.join("src/tools/enzyme")); + config.in_tree_llvm_info = + git_info(&config.exec_ctx, false, &config.src.join("src/llvm-project")); + config.in_tree_gcc_info = git_info(&config.exec_ctx, false, &config.src.join("src/gcc")); - config.vendor = vendor.unwrap_or( + config.vendor = build_vendor.unwrap_or( config.rust_info.is_from_tarball() && config.src.join("vendor").exists() && config.src.join(".cargo/config.toml").exists(), @@ -955,11 +926,237 @@ impl Config { config.rust_profile_use = flags_rust_profile_use; config.rust_profile_generate = flags_rust_profile_generate; - config.apply_target_config(toml.target); - config.apply_rust_config(toml.rust, flags_warnings); + // FIXME(#133381): alt rustc builds currently do *not* have rustc debug assertions + // enabled. We should not download a CI alt rustc if we need rustc to have debug + // assertions (e.g. for crashes test suite). This can be changed once something like + // [Enable debug assertions on alt + // builds](https://github.com/rust-lang/rust/pull/131077) lands. + // + // Note that `rust.debug = true` currently implies `rust.debug-assertions = true`! + // + // This relies also on the fact that the global default for `download-rustc` will be + // `false` if it's not explicitly set. + let debug_assertions_requested = matches!(rust_rustc_debug_assertions, Some(true)) + || (matches!(rust_debug, Some(true)) + && !matches!(rust_rustc_debug_assertions, Some(false))); + + if debug_assertions_requested + && let Some(ref opt) = rust_download_rustc + && opt.is_string_or_true() + { + eprintln!( + "WARN: currently no CI rustc builds have rustc debug assertions \ + enabled. Please either set `rust.debug-assertions` to `false` if you \ + want to use download CI rustc or set `rust.download-rustc` to `false`." + ); + } + + let dwn_ctx = DownloadContext::from(&config); + config.download_rustc_commit = + download_ci_rustc_commit(dwn_ctx, rust_download_rustc, config.llvm_assertions); + + if debug_assertions_requested && config.download_rustc_commit.is_some() { + eprintln!( + "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \ + rustc is not currently built with debug assertions." + ); + // We need to put this later down_ci_rustc_commit. + config.download_rustc_commit = None; + } + + if let Some(t) = toml.target { + for (triple, cfg) in t { + let mut target = Target::from_triple(&triple); + + if let Some(ref s) = cfg.llvm_config { + if config.download_rustc_commit.is_some() + && triple == *config.host_target.triple + { + panic!( + "setting llvm_config for the host is incompatible with download-rustc" + ); + } + target.llvm_config = Some(config.src.join(s)); + } + if let Some(patches) = cfg.llvm_has_rust_patches { + assert!( + config.submodules == Some(false) || cfg.llvm_config.is_some(), + "use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided" + ); + target.llvm_has_rust_patches = Some(patches); + } + if let Some(ref s) = cfg.llvm_filecheck { + target.llvm_filecheck = Some(config.src.join(s)); + } + target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| { + v.parse().unwrap_or_else(|_| { + panic!("failed to parse target.{triple}.llvm-libunwind") + }) + }); + if let Some(s) = cfg.no_std { + target.no_std = s; + } + target.cc = cfg.cc.map(PathBuf::from); + target.cxx = cfg.cxx.map(PathBuf::from); + target.ar = cfg.ar.map(PathBuf::from); + target.ranlib = cfg.ranlib.map(PathBuf::from); + target.linker = cfg.linker.map(PathBuf::from); + target.crt_static = cfg.crt_static; + target.musl_root = cfg.musl_root.map(PathBuf::from); + target.musl_libdir = cfg.musl_libdir.map(PathBuf::from); + target.wasi_root = cfg.wasi_root.map(PathBuf::from); + target.qemu_rootfs = cfg.qemu_rootfs.map(PathBuf::from); + target.runner = cfg.runner; + target.sanitizers = cfg.sanitizers; + target.profiler = cfg.profiler; + target.rpath = cfg.rpath; + target.optimized_compiler_builtins = cfg.optimized_compiler_builtins; + target.jemalloc = cfg.jemalloc; + if let Some(backends) = cfg.codegen_backends { + target.codegen_backends = + Some(parse_codegen_backends(backends, &format!("target.{triple}"))) + } + + target.split_debuginfo = cfg.split_debuginfo.as_ref().map(|v| { + v.parse().unwrap_or_else(|_| { + panic!("invalid value for target.{triple}.split-debuginfo") + }) + }); + + config.target_config.insert(TargetSelection::from_user(&triple), target); + } + } + + if rust_optimize.as_ref().is_some_and(|v| matches!(v, RustOptimize::Bool(false))) { + eprintln!( + "WARNING: setting `optimize` to `false` is known to cause errors and \ + should be considered unsupported. Refer to `bootstrap.example.toml` \ + for more details." + ); + } + + config.rust_new_symbol_mangling = rust_new_symbol_mangling; + set(&mut config.rust_optimize_tests, rust_optimize_tests); + set(&mut config.codegen_tests, rust_codegen_tests); + set(&mut config.rust_rpath, rust_rpath); + set(&mut config.rust_strip, rust_strip); + set(&mut config.rust_frame_pointers, rust_frame_pointers); + config.rust_stack_protector = rust_stack_protector; + set(&mut config.jemalloc, rust_jemalloc); + set(&mut config.test_compare_mode, rust_test_compare_mode); + set(&mut config.backtrace, rust_backtrace); + set(&mut config.rust_dist_src, rust_dist_src); + set(&mut config.verbose_tests, rust_verbose_tests); + // in the case "false" is set explicitly, do not overwrite the command line args + if let Some(true) = rust_incremental { + config.incremental = true; + } + set(&mut config.lld_mode, rust_lld_mode); + set(&mut config.llvm_bitcode_linker_enabled, rust_llvm_bitcode_linker); + + config.rust_randomize_layout = rust_randomize_layout.unwrap_or_default(); + config.llvm_tools_enabled = rust_llvm_tools.unwrap_or(true); + + config.llvm_enzyme = config.channel == "dev" || config.channel == "nightly"; + config.rustc_default_linker = rust_default_linker; + config.musl_root = rust_musl_root.map(PathBuf::from); + config.save_toolstates = rust_save_toolstates.map(PathBuf::from); + set( + &mut config.deny_warnings, + match flags_warnings { + Warnings::Deny => Some(true), + Warnings::Warn => Some(false), + Warnings::Default => rust_deny_warnings, + }, + ); + set(&mut config.backtrace_on_ice, rust_backtrace_on_ice); + set(&mut config.rust_verify_llvm_ir, rust_verify_llvm_ir); + config.rust_thin_lto_import_instr_limit = rust_thin_lto_import_instr_limit; + set(&mut config.rust_remap_debuginfo, rust_remap_debuginfo); + set(&mut config.control_flow_guard, rust_control_flow_guard); + set(&mut config.ehcont_guard, rust_ehcont_guard); + config.llvm_libunwind_default = + rust_llvm_libunwind.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); + set( + &mut config.rust_codegen_backends, + rust_codegen_backends.map(|backends| parse_codegen_backends(backends, "rust")), + ); + + config.rust_codegen_units = rust_codegen_units.map(threads_from_config); + config.rust_codegen_units_std = rust_codegen_units_std.map(threads_from_config); + + if config.rust_profile_use.is_none() { + config.rust_profile_use = rust_profile_use; + } + + if config.rust_profile_generate.is_none() { + config.rust_profile_generate = rust_profile_generate; + } + + config.rust_lto = + rust_lto.as_deref().map(|value| RustcLto::from_str(value).unwrap()).unwrap_or_default(); + config.rust_validate_mir_opts = rust_validate_mir_opts; + + config.rust_optimize = rust_optimize.unwrap_or(RustOptimize::Bool(true)); + + // We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will + // build our internal lld and use it as the default linker, by setting the `rust.lld` config + // to true by default: + // - on the `x86_64-unknown-linux-gnu` target + // - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that + // we're also able to build the corresponding lld + // - or when using an external llvm that's downloaded from CI, which also contains our prebuilt + // lld + // - otherwise, we'd be using an external llvm, and lld would not necessarily available and + // thus, disabled + // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g. + // when the config sets `rust.lld = false` + if default_lld_opt_in_targets().contains(&config.host_target.triple.to_string()) + && config.hosts == [config.host_target] + { + let no_llvm_config = config + .target_config + .get(&config.host_target) + .is_none_or(|target_config| target_config.llvm_config.is_none()); + let enable_lld = config.llvm_from_ci || no_llvm_config; + // Prefer the config setting in case an explicit opt-out is needed. + config.lld_enabled = rust_lld_enabled.unwrap_or(enable_lld); + } else { + set(&mut config.lld_enabled, rust_lld_enabled); + } + + let default_std_features = BTreeSet::from([String::from("panic-unwind")]); + config.rust_std_features = rust_std_features.unwrap_or(default_std_features); + + let default = rust_debug == Some(true); + config.rustc_debug_assertions = rust_rustc_debug_assertions.unwrap_or(default); + config.std_debug_assertions = + rust_std_debug_assertions.unwrap_or(config.rustc_debug_assertions); + config.tools_debug_assertions = + rust_tools_debug_assertions.unwrap_or(config.rustc_debug_assertions); + config.rust_overflow_checks = rust_overflow_checks.unwrap_or(default); + config.rust_overflow_checks_std = + rust_overflow_checks_std.unwrap_or(config.rust_overflow_checks); + + config.rust_debug_logging = rust_debug_logging.unwrap_or(config.rustc_debug_assertions); + + let with_defaults = |debuginfo_level_specific: Option<_>| { + debuginfo_level_specific.or(rust_debuginfo_level).unwrap_or( + if rust_debug == Some(true) { + DebuginfoLevel::Limited + } else { + DebuginfoLevel::None + }, + ) + }; + config.rust_debuginfo_level_rustc = with_defaults(rust_debuginfo_level_rustc); + config.rust_debuginfo_level_std = with_defaults(rust_debuginfo_level_std); + config.rust_debuginfo_level_tools = with_defaults(rust_debuginfo_level_tools); + config.rust_debuginfo_level_tests = + rust_debuginfo_level_tests.unwrap_or(DebuginfoLevel::None); config.reproducible_artifacts = flags_reproducible_artifact; - config.description = description; + config.description = build_description; // We need to override `rust.channel` if it's manually specified when using the CI rustc. // This is because if the compiler uses a different channel than the one specified in bootstrap.toml, @@ -971,17 +1168,98 @@ impl Config { "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel." ); + let dwn_ctx = DownloadContext::from(&config); let channel = - config.read_file_by_commit(Path::new("src/ci/channel"), commit).trim().to_owned(); + read_file_by_commit(dwn_ctx, Path::new("src/ci/channel"), commit).trim().to_owned(); config.channel = channel; } - config.apply_llvm_config(toml.llvm); + set(&mut config.ninja_in_file, llvm_ninja); + set(&mut config.llvm_optimize, llvm_optimize); + set(&mut config.llvm_thin_lto, llvm_thin_lto); + set(&mut config.llvm_release_debuginfo, llvm_release_debuginfo); + set(&mut config.llvm_static_stdcpp, llvm_static_libstdcpp); + set(&mut config.llvm_libzstd, llvm_libzstd); + if let Some(v) = llvm_link_shared { + config.llvm_link_shared.set(Some(v)); + } + config.llvm_targets.clone_from(&llvm_targets); + config.llvm_experimental_targets.clone_from(&llvm_experimental_targets); + config.llvm_link_jobs = llvm_link_jobs; + config.llvm_version_suffix.clone_from(&llvm_version_suffix); + config.llvm_clang_cl.clone_from(&llvm_clang_cl); + config.llvm_tests = llvm_tests.unwrap_or_default(); + config.llvm_enzyme = llvm_enzyme.unwrap_or_default(); + config.llvm_plugins = llvm_plugin.unwrap_or_default(); + + config.llvm_cflags.clone_from(&llvm_cflags); + config.llvm_cxxflags.clone_from(&llvm_cxxflags); + config.llvm_ldflags.clone_from(&llvm_ldflags); + set(&mut config.llvm_use_libcxx, llvm_use_libcxx); + config.llvm_use_linker.clone_from(&llvm_use_linker); + config.llvm_allow_old_toolchain = llvm_allow_old_toolchain.unwrap_or(false); + config.llvm_offload = llvm_offload.unwrap_or(false); + config.llvm_polly = llvm_polly.unwrap_or(false); + config.llvm_clang = llvm_clang.unwrap_or(false); + config.llvm_enable_warnings = llvm_enable_warnings.unwrap_or(false); + config.llvm_build_config = llvm_build_config.clone().unwrap_or(Default::default()); + + let dwn_ctx = DownloadContext::from(&config); + config.llvm_from_ci = + parse_download_ci_llvm(dwn_ctx, llvm_download_ci_llvm, config.llvm_assertions); + + if config.llvm_from_ci { + let warn = |option: &str| { + println!( + "WARNING: `{option}` will only be used on `compiler/rustc_llvm` build, not for the LLVM build." + ); + println!( + "HELP: To use `{option}` for LLVM builds, set `download-ci-llvm` option to false." + ); + }; + + if llvm_static_libstdcpp.is_some() { + warn("static-libstdcpp"); + } + + if llvm_link_shared.is_some() { + warn("link-shared"); + } + + // FIXME(#129153): instead of all the ad-hoc `download-ci-llvm` checks that follow, + // use the `builder-config` present in tarballs since #128822 to compare the local + // config to the ones used to build the LLVM artifacts on CI, and only notify users + // if they've chosen a different value. + + if llvm_libzstd.is_some() { + println!( + "WARNING: when using `download-ci-llvm`, the local `llvm.libzstd` option, \ + like almost all `llvm.*` options, will be ignored and set by the LLVM CI \ + artifacts builder config." + ); + println!( + "HELP: To use `llvm.libzstd` for LLVM/LLD builds, set `download-ci-llvm` option to false." + ); + } + } + + if !config.llvm_from_ci && config.llvm_thin_lto && llvm_link_shared.is_none() { + // If we're building with ThinLTO on, by default we want to link + // to LLVM shared, to avoid re-doing ThinLTO (which happens in + // the link step) with each stage. + config.llvm_link_shared.set(Some(true)); + } - config.apply_gcc_config(toml.gcc); + config.gcc_ci_mode = match gcc_download_ci_gcc { + Some(value) => match value { + true => GccCiMode::DownloadFromCi, + false => GccCiMode::BuildLocally, + }, + None => GccCiMode::default(), + }; - match ccache { + match build_ccache { Some(StringOrBool::String(ref s)) => config.ccache = Some(s.to_string()), Some(StringOrBool::Bool(true)) => { config.ccache = Some("ccache".to_string()); @@ -991,7 +1269,8 @@ impl Config { if config.llvm_from_ci { let triple = &config.host_target.triple; - let ci_llvm_bin = config.ci_llvm_root().join("bin"); + let dwn_ctx = DownloadContext::from(&config); + let ci_llvm_bin = ci_llvm_root(dwn_ctx).join("bin"); let build_target = config .target_config .entry(config.host_target) @@ -1005,9 +1284,18 @@ impl Config { Some(ci_llvm_bin.join(exe("FileCheck", config.host_target))); } - config.apply_dist_config(toml.dist); + config.dist_sign_folder = dist_sign_folder.map(PathBuf::from); + config.dist_upload_addr = dist_upload_addr; + config.dist_compression_formats = dist_compression_formats; + set(&mut config.dist_compression_profile, dist_compression_profile); + set(&mut config.rust_dist_src, dist_src_tarball); + set(&mut config.dist_include_mingw_linker, dist_include_mingw_linker); + config.dist_vendor = dist_vendor.unwrap_or_else(|| { + // If we're building from git or tarball sources, enable it by default. + config.rust_info.is_managed_git_subrepository() || config.rust_info.is_from_tarball() + }); - config.initial_rustfmt = if let Some(r) = rustfmt { + config.initial_rustfmt = if let Some(r) = build_rustfmt { Some(r) } else { let dwn_ctx = DownloadContext::from(&config); @@ -1023,46 +1311,46 @@ impl Config { ); } - if config.lld_enabled && config.is_system_llvm(config.host_target) { + let dwn_ctx = DownloadContext::from(&config); + if config.lld_enabled && is_system_llvm(dwn_ctx, config.host_target) { panic!("Cannot enable LLD with `rust.lld = true` when using external llvm-config."); } config.optimized_compiler_builtins = - optimized_compiler_builtins.unwrap_or(config.channel != "dev"); - - config.compiletest_diff_tool = compiletest_diff_tool; - - config.compiletest_allow_stage0 = compiletest_allow_stage0.unwrap_or(false); - config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true); - - config.tidy_extra_checks = tidy_extra_checks; + build_optimized_compiler_builtins.unwrap_or(config.channel != "dev"); + config.compiletest_diff_tool = build_compiletest_diff_tool; + config.compiletest_use_stage0_libtest = + build_compiletest_use_stage0_libtest.unwrap_or(true); + config.tidy_extra_checks = build_tidy_extra_checks; let download_rustc = config.download_rustc_commit.is_some(); config.explicit_stage_from_cli = flags_stage.is_some(); - config.explicit_stage_from_config = test_stage.is_some() - || build_stage.is_some() - || doc_stage.is_some() - || dist_stage.is_some() - || install_stage.is_some() - || check_stage.is_some() - || bench_stage.is_some(); + config.explicit_stage_from_config = build_test_stage.is_some() + || build_build_stage.is_some() + || build_doc_stage.is_some() + || build_dist_stage.is_some() + || build_install_stage.is_some() + || build_check_stage.is_some() + || build_bench_stage.is_some(); config.stage = match config.cmd { - Subcommand::Check { .. } => flags_stage.or(check_stage).unwrap_or(1), - Subcommand::Clippy { .. } | Subcommand::Fix => flags_stage.or(check_stage).unwrap_or(1), + Subcommand::Check { .. } => flags_stage.or(build_check_stage).unwrap_or(1), + Subcommand::Clippy { .. } | Subcommand::Fix => { + flags_stage.or(build_check_stage).unwrap_or(1) + } // `download-rustc` only has a speed-up for stage2 builds. Default to stage2 unless explicitly overridden. Subcommand::Doc { .. } => { - flags_stage.or(doc_stage).unwrap_or(if download_rustc { 2 } else { 1 }) + flags_stage.or(build_doc_stage).unwrap_or(if download_rustc { 2 } else { 1 }) } - Subcommand::Build => { - flags_stage.or(build_stage).unwrap_or(if download_rustc { 2 } else { 1 }) + Subcommand::Build { .. } => { + flags_stage.or(build_build_stage).unwrap_or(if download_rustc { 2 } else { 1 }) } Subcommand::Test { .. } | Subcommand::Miri { .. } => { - flags_stage.or(test_stage).unwrap_or(if download_rustc { 2 } else { 1 }) + flags_stage.or(build_test_stage).unwrap_or(if download_rustc { 2 } else { 1 }) } - Subcommand::Bench { .. } => flags_stage.or(bench_stage).unwrap_or(2), - Subcommand::Dist => flags_stage.or(dist_stage).unwrap_or(2), - Subcommand::Install => flags_stage.or(install_stage).unwrap_or(2), + Subcommand::Bench { .. } => flags_stage.or(build_bench_stage).unwrap_or(2), + Subcommand::Dist => flags_stage.or(build_dist_stage).unwrap_or(2), + Subcommand::Install => flags_stage.or(build_install_stage).unwrap_or(2), Subcommand::Perf { .. } => flags_stage.unwrap_or(1), // These are all bootstrap tools, which don't depend on the compiler. // The stage we pass shouldn't matter, but use 0 just in case. @@ -1075,12 +1363,20 @@ impl Config { // Now check that the selected stage makes sense, and if not, print a warning and end match (config.stage, &config.cmd) { - (0, Subcommand::Build) => { - eprintln!("WARNING: cannot build anything on stage 0. Use at least stage 1."); + (0, Subcommand::Build { .. }) => { + eprintln!("ERROR: cannot build anything on stage 0. Use at least stage 1."); exit!(1); } (0, Subcommand::Check { .. }) => { - eprintln!("WARNING: cannot check anything on stage 0. Use at least stage 1."); + eprintln!("ERROR: cannot check anything on stage 0. Use at least stage 1."); + exit!(1); + } + (0, Subcommand::Doc { .. }) => { + eprintln!("ERROR: cannot document anything on stage 0. Use at least stage 1."); + exit!(1); + } + (0, Subcommand::Clippy { .. }) => { + eprintln!("ERROR: cannot run clippy on stage 0. Use at least stage 1."); exit!(1); } _ => {} @@ -1100,7 +1396,7 @@ impl Config { Subcommand::Test { .. } | Subcommand::Miri { .. } | Subcommand::Doc { .. } - | Subcommand::Build + | Subcommand::Build { .. } | Subcommand::Bench { .. } | Subcommand::Dist | Subcommand::Install => { @@ -1159,14 +1455,8 @@ impl Config { /// Returns the content of the given file at a specific commit. pub(crate) fn read_file_by_commit(&self, file: &Path, commit: &str) -> String { - assert!( - self.rust_info.is_managed_git_subrepository(), - "`Config::read_file_by_commit` is not supported in non-git sources." - ); - - let mut git = helpers::git(Some(&self.src)); - git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap())); - git.run_capture_stdout(self).stdout() + let dwn_ctx = DownloadContext::from(self); + read_file_by_commit(dwn_ctx, file, commit) } /// Bootstrap embeds a version number into the name of shared libraries it uploads in CI. @@ -1237,8 +1527,8 @@ impl Config { /// The absolute path to the downloaded LLVM artifacts. pub(crate) fn ci_llvm_root(&self) -> PathBuf { - assert!(self.llvm_from_ci); - self.out.join(self.host_target).join("ci-llvm") + let dwn_ctx = DownloadContext::from(self); + ci_llvm_root(dwn_ctx) } /// Directory where the extracted `rustc-dev` component is stored. @@ -1401,304 +1691,14 @@ impl Config { ), )] pub(crate) fn update_submodule(&self, relative_path: &str) { - if self.rust_info.is_from_tarball() || !self.submodules() { - return; - } - - let absolute_path = self.src.join(relative_path); - - // NOTE: This check is required because `jj git clone` doesn't create directories for - // submodules, they are completely ignored. The code below assumes this directory exists, - // so create it here. - if !absolute_path.exists() { - t!(fs::create_dir_all(&absolute_path)); - } - - // NOTE: The check for the empty directory is here because when running x.py the first time, - // the submodule won't be checked out. Check it out now so we can build it. - if !self.git_info(false, &absolute_path).is_managed_git_subrepository() - && !helpers::dir_is_empty(&absolute_path) - { - return; - } - - // Submodule updating actually happens during in the dry run mode. We need to make sure that - // all the git commands below are actually executed, because some follow-up code - // in bootstrap might depend on the submodules being checked out. Furthermore, not all - // the command executions below work with an empty output (produced during dry run). - // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in - // dry run mode. - let submodule_git = || { - let mut cmd = helpers::git(Some(&absolute_path)); - cmd.run_in_dry_run(); - cmd - }; - - // Determine commit checked out in submodule. - let checked_out_hash = - submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(self).stdout(); - let checked_out_hash = checked_out_hash.trim_end(); - // Determine commit that the submodule *should* have. - let recorded = helpers::git(Some(&self.src)) - .run_in_dry_run() - .args(["ls-tree", "HEAD"]) - .arg(relative_path) - .run_capture_stdout(self) - .stdout(); - - let actual_hash = recorded - .split_whitespace() - .nth(2) - .unwrap_or_else(|| panic!("unexpected output `{recorded}`")); - - if actual_hash == checked_out_hash { - // already checked out - return; - } - - println!("Updating submodule {relative_path}"); - - helpers::git(Some(&self.src)) - .allow_failure() - .run_in_dry_run() - .args(["submodule", "-q", "sync"]) - .arg(relative_path) - .run(self); - - // Try passing `--progress` to start, then run git again without if that fails. - let update = |progress: bool| { - // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository, - // even though that has no relation to the upstream for the submodule. - let current_branch = helpers::git(Some(&self.src)) - .allow_failure() - .run_in_dry_run() - .args(["symbolic-ref", "--short", "HEAD"]) - .run_capture(self); - - let mut git = helpers::git(Some(&self.src)).allow_failure(); - git.run_in_dry_run(); - if current_branch.is_success() { - // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name. - // This syntax isn't accepted by `branch.{branch}`. Strip it. - let branch = current_branch.stdout(); - let branch = branch.trim(); - let branch = branch.strip_prefix("heads/").unwrap_or(branch); - git.arg("-c").arg(format!("branch.{branch}.remote=origin")); - } - git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]); - if progress { - git.arg("--progress"); - } - git.arg(relative_path); - git - }; - if !update(true).allow_failure().run(self) { - update(false).allow_failure().run(self); - } - - // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error). - // diff-index reports the modifications through the exit status - let has_local_modifications = - !submodule_git().allow_failure().args(["diff-index", "--quiet", "HEAD"]).run(self); - if has_local_modifications { - submodule_git().allow_failure().args(["stash", "push"]).run(self); - } - - submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(self); - submodule_git().allow_failure().args(["clean", "-qdfx"]).run(self); - - if has_local_modifications { - submodule_git().allow_failure().args(["stash", "pop"]).run(self); - } - } - - #[cfg(test)] - pub fn check_stage0_version(&self, _program_path: &Path, _component_name: &'static str) {} - - /// check rustc/cargo version is same or lower with 1 apart from the building one - #[cfg(not(test))] - pub fn check_stage0_version(&self, program_path: &Path, component_name: &'static str) { - use build_helper::util::fail; - - if self.dry_run() { - return; - } - - let stage0_output = - command(program_path).arg("--version").run_capture_stdout(self).stdout(); - let mut stage0_output = stage0_output.lines().next().unwrap().split(' '); - - let stage0_name = stage0_output.next().unwrap(); - if stage0_name != component_name { - fail(&format!( - "Expected to find {component_name} at {} but it claims to be {stage0_name}", - program_path.display() - )); - } - - let stage0_version = - semver::Version::parse(stage0_output.next().unwrap().split('-').next().unwrap().trim()) - .unwrap(); - let source_version = semver::Version::parse( - fs::read_to_string(self.src.join("src/version")).unwrap().trim(), - ) - .unwrap(); - if !(source_version == stage0_version - || (source_version.major == stage0_version.major - && (source_version.minor == stage0_version.minor - || source_version.minor == stage0_version.minor + 1))) - { - let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1); - fail(&format!( - "Unexpected {component_name} version: {stage0_version}, we should use {prev_version}/{source_version} to build source with {source_version}" - )); - } - } - - /// Returns the commit to download, or `None` if we shouldn't download CI artifacts. - pub fn download_ci_rustc_commit( - &self, - download_rustc: Option, - debug_assertions_requested: bool, - llvm_assertions: bool, - ) -> Option { - if !is_download_ci_available(&self.host_target.triple, llvm_assertions) { - return None; - } - - // If `download-rustc` is not set, default to rebuilding. - let if_unchanged = match download_rustc { - // Globally default `download-rustc` to `false`, because some contributors don't use - // profiles for reasons such as: - // - They need to seamlessly switch between compiler/library work. - // - They don't want to use compiler profile because they need to override too many - // things and it's easier to not use a profile. - None | Some(StringOrBool::Bool(false)) => return None, - Some(StringOrBool::Bool(true)) => false, - Some(StringOrBool::String(s)) if s == "if-unchanged" => { - if !self.rust_info.is_managed_git_subrepository() { - println!( - "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources." - ); - crate::exit!(1); - } - - true - } - Some(StringOrBool::String(other)) => { - panic!("unrecognized option for download-rustc: {other}") - } - }; - - let commit = if self.rust_info.is_managed_git_subrepository() { - // Look for a version to compare to based on the current commit. - // Only commits merged by bors will have CI artifacts. - let freshness = self.check_path_modifications(RUSTC_IF_UNCHANGED_ALLOWED_PATHS); - self.verbose(|| { - eprintln!("rustc freshness: {freshness:?}"); - }); - match freshness { - PathFreshness::LastModifiedUpstream { upstream } => upstream, - PathFreshness::HasLocalModifications { upstream } => { - if if_unchanged { - return None; - } - - if self.is_running_on_ci { - eprintln!("CI rustc commit matches with HEAD and we are in CI."); - eprintln!( - "`rustc.download-ci` functionality will be skipped as artifacts are not available." - ); - return None; - } - - upstream - } - PathFreshness::MissingUpstream => { - eprintln!("No upstream commit found"); - return None; - } - } - } else { - channel::read_commit_info_file(&self.src) - .map(|info| info.sha.trim().to_owned()) - .expect("git-commit-info is missing in the project root") - }; - - if debug_assertions_requested { - eprintln!( - "WARN: `rust.debug-assertions = true` will prevent downloading CI rustc as alt CI \ - rustc is not currently built with debug assertions." - ); - return None; - } - - Some(commit) - } - - pub fn parse_download_ci_llvm( - &self, - download_ci_llvm: Option, - asserts: bool, - ) -> bool { - // We don't ever want to use `true` on CI, as we should not - // download upstream artifacts if there are any local modifications. - let default = if self.is_running_on_ci { - StringOrBool::String("if-unchanged".to_string()) - } else { - StringOrBool::Bool(true) - }; - let download_ci_llvm = download_ci_llvm.unwrap_or(default); - - let if_unchanged = || { - if self.rust_info.is_from_tarball() { - // Git is needed for running "if-unchanged" logic. - println!("ERROR: 'if-unchanged' is only compatible with Git managed sources."); - crate::exit!(1); - } - - // Fetching the LLVM submodule is unnecessary for self-tests. - #[cfg(not(test))] - self.update_submodule("src/llvm-project"); - - // Check for untracked changes in `src/llvm-project` and other important places. - let has_changes = self.has_changes_from_upstream(LLVM_INVALIDATION_PATHS); - - // Return false if there are untracked changes, otherwise check if CI LLVM is available. - if has_changes { false } else { llvm::is_ci_llvm_available_for_target(self, asserts) } - }; - - match download_ci_llvm { - StringOrBool::Bool(b) => { - if !b && self.download_rustc_commit.is_some() { - panic!( - "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`." - ); - } - - if b && self.is_running_on_ci { - // On CI, we must always rebuild LLVM if there were any modifications to it - panic!( - "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead." - ); - } - - // If download-ci-llvm=true we also want to check that CI llvm is available - b && llvm::is_ci_llvm_available_for_target(self, asserts) - } - StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(), - StringOrBool::String(other) => { - panic!("unrecognized option for download-ci-llvm: {other:?}") - } - } + let dwn_ctx = DownloadContext::from(self); + update_submodule(dwn_ctx, relative_path); } /// Returns true if any of the `paths` have been modified locally. pub fn has_changes_from_upstream(&self, paths: &[&'static str]) -> bool { - match self.check_path_modifications(paths) { - PathFreshness::LastModifiedUpstream { .. } => false, - PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true, - } + let dwn_ctx = DownloadContext::from(self); + has_changes_from_upstream(dwn_ctx, paths) } /// Checks whether any of the given paths have been modified w.r.t. upstream. @@ -1719,10 +1719,6 @@ impl Config { .clone() } - pub fn ci_env(&self) -> CiEnv { - if self.is_running_on_ci { CiEnv::GitHubActions } else { CiEnv::None } - } - pub fn sanitizers_enabled(&self, target: TargetSelection) -> bool { self.target_config.get(&target).and_then(|t| t.sanitizers).unwrap_or(self.sanitizers) } @@ -1747,19 +1743,24 @@ impl Config { .unwrap_or(self.profiler) } - pub fn codegen_backends(&self, target: TargetSelection) -> &[CodegenBackendKind] { + /// Returns codegen backends that should be: + /// - Built and added to the sysroot when we build the compiler. + /// - Distributed when `x dist` is executed (if the codegen backend has a dist step). + pub fn enabled_codegen_backends(&self, target: TargetSelection) -> &[CodegenBackendKind] { self.target_config .get(&target) .and_then(|cfg| cfg.codegen_backends.as_deref()) .unwrap_or(&self.rust_codegen_backends) } - pub fn jemalloc(&self, target: TargetSelection) -> bool { - self.target_config.get(&target).and_then(|cfg| cfg.jemalloc).unwrap_or(self.jemalloc) + /// Returns the codegen backend that should be configured as the *default* codegen backend + /// for a rustc compiled by bootstrap. + pub fn default_codegen_backend(&self, target: TargetSelection) -> Option<&CodegenBackendKind> { + self.enabled_codegen_backends(target).first() } - pub fn default_codegen_backend(&self, target: TargetSelection) -> Option { - self.codegen_backends(target).first().cloned() + pub fn jemalloc(&self, target: TargetSelection) -> bool { + self.target_config.get(&target).and_then(|cfg| cfg.jemalloc).unwrap_or(self.jemalloc) } pub fn rpath_enabled(&self, target: TargetSelection) -> bool { @@ -1774,7 +1775,7 @@ impl Config { } pub fn llvm_enabled(&self, target: TargetSelection) -> bool { - self.codegen_backends(target).contains(&CodegenBackendKind::Llvm) + self.enabled_codegen_backends(target).contains(&CodegenBackendKind::Llvm) } pub fn llvm_libunwind(&self, target: TargetSelection) -> LlvmLibunwind { @@ -1806,15 +1807,8 @@ impl Config { /// /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set. pub fn is_system_llvm(&self, target: TargetSelection) -> bool { - match self.target_config.get(&target) { - Some(Target { llvm_config: Some(_), .. }) => { - let ci_llvm = self.llvm_from_ci && self.is_host_target(target); - !ci_llvm - } - // We're building from the in-tree src/llvm-project sources. - Some(Target { llvm_config: None, .. }) => false, - None => false, - } + let dwn_ctx = DownloadContext::from(self); + is_system_llvm(dwn_ctx, target) } /// Returns `true` if this is our custom, patched, version of LLVM. @@ -1846,3 +1840,624 @@ impl AsRef for Config { &self.exec_ctx } } + +fn compute_src_directory(src_dir: Option, exec_ctx: &ExecutionContext) -> Option { + if let Some(src) = src_dir { + return Some(src); + } else { + // Infer the source directory. This is non-trivial because we want to support a downloaded bootstrap binary, + // running on a completely different machine from where it was compiled. + let mut cmd = helpers::git(None); + // NOTE: we cannot support running from outside the repository because the only other path we have available + // is set at compile time, which can be wrong if bootstrap was downloaded rather than compiled locally. + // We still support running outside the repository if we find we aren't in a git directory. + + // NOTE: We get a relative path from git to work around an issue on MSYS/mingw. If we used an absolute path, + // and end up using MSYS's git rather than git-for-windows, we would get a unix-y MSYS path. But as bootstrap + // has already been (kinda-cross-)compiled to Windows land, we require a normal Windows path. + cmd.arg("rev-parse").arg("--show-cdup"); + // Discard stderr because we expect this to fail when building from a tarball. + let output = cmd.allow_failure().run_capture_stdout(exec_ctx); + if output.is_success() { + let git_root_relative = output.stdout(); + // We need to canonicalize this path to make sure it uses backslashes instead of forward slashes, + // and to resolve any relative components. + let git_root = env::current_dir() + .unwrap() + .join(PathBuf::from(git_root_relative.trim())) + .canonicalize() + .unwrap(); + let s = git_root.to_str().unwrap(); + + // Bootstrap is quite bad at handling /? in front of paths + let git_root = match s.strip_prefix("\\\\?\\") { + Some(p) => PathBuf::from(p), + None => git_root, + }; + // If this doesn't have at least `stage0`, we guessed wrong. This can happen when, + // for example, the build directory is inside of another unrelated git directory. + // In that case keep the original `CARGO_MANIFEST_DIR` handling. + // + // NOTE: this implies that downloadable bootstrap isn't supported when the build directory is outside + // the source directory. We could fix that by setting a variable from all three of python, ./x, and x.ps1. + if git_root.join("src").join("stage0").exists() { + return Some(git_root); + } + } else { + // We're building from a tarball, not git sources. + // We don't support pre-downloaded bootstrap in this case. + } + }; + None +} + +/// Loads bootstrap TOML config and returns the config together with a path from where +/// it was loaded. +/// `src` is the source root directory, and `config_path` is an optionally provided path to the +/// config. +fn load_toml_config( + src: &Path, + config_path: Option, + get_toml: &impl Fn(&Path) -> Result, +) -> (TomlConfig, Option) { + // Locate the configuration file using the following priority (first match wins): + // 1. `--config ` (explicit flag) + // 2. `RUST_BOOTSTRAP_CONFIG` environment variable + // 3. `./bootstrap.toml` (local file) + // 4. `/bootstrap.toml` + // 5. `./config.toml` (fallback for backward compatibility) + // 6. `/config.toml` + let toml_path = config_path.or_else(|| env::var_os("RUST_BOOTSTRAP_CONFIG").map(PathBuf::from)); + let using_default_path = toml_path.is_none(); + let mut toml_path = toml_path.unwrap_or_else(|| PathBuf::from("bootstrap.toml")); + + if using_default_path && !toml_path.exists() { + toml_path = src.join(PathBuf::from("bootstrap.toml")); + if !toml_path.exists() { + toml_path = PathBuf::from("config.toml"); + if !toml_path.exists() { + toml_path = src.join(PathBuf::from("config.toml")); + } + } + } + + // Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path, + // but not if `bootstrap.toml` hasn't been created. + if !using_default_path || toml_path.exists() { + let path = Some(if cfg!(not(test)) { + toml_path = toml_path.canonicalize().unwrap(); + toml_path.clone() + } else { + toml_path.clone() + }); + ( + get_toml(&toml_path).unwrap_or_else(|e| { + eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display()); + exit!(2); + }), + path, + ) + } else { + (TomlConfig::default(), None) + } +} + +fn postprocess_toml( + toml: &mut TomlConfig, + src_dir: &Path, + toml_path: Option, + exec_ctx: &ExecutionContext, + override_set: &[String], + get_toml: &impl Fn(&Path) -> Result, +) { + let git_info = GitInfo::new(false, src_dir, exec_ctx); + + if git_info.is_from_tarball() && toml.profile.is_none() { + toml.profile = Some("dist".into()); + } + + // Reverse the list to ensure the last added config extension remains the most dominant. + // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml". + // + // This must be handled before applying the `profile` since `include`s should always take + // precedence over `profile`s. + for include_path in toml.include.clone().unwrap_or_default().iter().rev() { + let include_path = toml_path + .as_ref() + .expect("include found in default TOML config") + .parent() + .unwrap() + .join(include_path); + + let included_toml = get_toml(&include_path).unwrap_or_else(|e| { + eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); + exit!(2); + }); + toml.merge( + Some(include_path), + &mut Default::default(), + included_toml, + ReplaceOpt::IgnoreDuplicate, + ); + } + + if let Some(include) = &toml.profile { + // Allows creating alias for profile names, allowing + // profiles to be renamed while maintaining back compatibility + // Keep in sync with `profile_aliases` in bootstrap.py + let profile_aliases = HashMap::from([("user", "dist")]); + let include = match profile_aliases.get(include.as_str()) { + Some(alias) => alias, + None => include.as_str(), + }; + let mut include_path = PathBuf::from(src_dir); + include_path.push("src"); + include_path.push("bootstrap"); + include_path.push("defaults"); + include_path.push(format!("bootstrap.{include}.toml")); + let included_toml = get_toml(&include_path).unwrap_or_else(|e| { + eprintln!( + "ERROR: Failed to parse default config profile at '{}': {e}", + include_path.display() + ); + exit!(2); + }); + toml.merge( + Some(include_path), + &mut Default::default(), + included_toml, + ReplaceOpt::IgnoreDuplicate, + ); + } + + let mut override_toml = TomlConfig::default(); + for option in override_set.iter() { + fn get_table(option: &str) -> Result { + toml::from_str(option).and_then(|table: toml::Value| TomlConfig::deserialize(table)) + } + + let mut err = match get_table(option) { + Ok(v) => { + override_toml.merge(None, &mut Default::default(), v, ReplaceOpt::ErrorOnDuplicate); + continue; + } + Err(e) => e, + }; + // We want to be able to set string values without quotes, + // like in `configure.py`. Try adding quotes around the right hand side + if let Some((key, value)) = option.split_once('=') + && !value.contains('"') + { + match get_table(&format!(r#"{key}="{value}""#)) { + Ok(v) => { + override_toml.merge( + None, + &mut Default::default(), + v, + ReplaceOpt::ErrorOnDuplicate, + ); + continue; + } + Err(e) => err = e, + } + } + eprintln!("failed to parse override `{option}`: `{err}"); + exit!(2) + } + toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override); +} + +#[cfg(test)] +pub fn check_stage0_version( + _program_path: &Path, + _component_name: &'static str, + _src_dir: &Path, + _exec_ctx: &ExecutionContext, +) { +} + +/// check rustc/cargo version is same or lower with 1 apart from the building one +#[cfg(not(test))] +pub fn check_stage0_version( + program_path: &Path, + component_name: &'static str, + src_dir: &Path, + exec_ctx: &ExecutionContext, +) { + use build_helper::util::fail; + + if exec_ctx.dry_run() { + return; + } + + let stage0_output = + command(program_path).arg("--version").run_capture_stdout(exec_ctx).stdout(); + let mut stage0_output = stage0_output.lines().next().unwrap().split(' '); + + let stage0_name = stage0_output.next().unwrap(); + if stage0_name != component_name { + fail(&format!( + "Expected to find {component_name} at {} but it claims to be {stage0_name}", + program_path.display() + )); + } + + let stage0_version = + semver::Version::parse(stage0_output.next().unwrap().split('-').next().unwrap().trim()) + .unwrap(); + let source_version = + semver::Version::parse(fs::read_to_string(src_dir.join("src/version")).unwrap().trim()) + .unwrap(); + if !(source_version == stage0_version + || (source_version.major == stage0_version.major + && (source_version.minor == stage0_version.minor + || source_version.minor == stage0_version.minor + 1))) + { + let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1); + fail(&format!( + "Unexpected {component_name} version: {stage0_version}, we should use {prev_version}/{source_version} to build source with {source_version}" + )); + } +} + +pub fn download_ci_rustc_commit<'a>( + dwn_ctx: impl AsRef>, + download_rustc: Option, + llvm_assertions: bool, +) -> Option { + let dwn_ctx = dwn_ctx.as_ref(); + + if !is_download_ci_available(&dwn_ctx.host_target.triple, llvm_assertions) { + return None; + } + + // If `download-rustc` is not set, default to rebuilding. + let if_unchanged = match download_rustc { + // Globally default `download-rustc` to `false`, because some contributors don't use + // profiles for reasons such as: + // - They need to seamlessly switch between compiler/library work. + // - They don't want to use compiler profile because they need to override too many + // things and it's easier to not use a profile. + None | Some(StringOrBool::Bool(false)) => return None, + Some(StringOrBool::Bool(true)) => false, + Some(StringOrBool::String(s)) if s == "if-unchanged" => { + if !dwn_ctx.rust_info.is_managed_git_subrepository() { + println!( + "ERROR: `download-rustc=if-unchanged` is only compatible with Git managed sources." + ); + crate::exit!(1); + } + + true + } + Some(StringOrBool::String(other)) => { + panic!("unrecognized option for download-rustc: {other}") + } + }; + + let commit = if dwn_ctx.rust_info.is_managed_git_subrepository() { + // Look for a version to compare to based on the current commit. + // Only commits merged by bors will have CI artifacts. + let freshness = check_path_modifications_(dwn_ctx, RUSTC_IF_UNCHANGED_ALLOWED_PATHS); + dwn_ctx.exec_ctx.verbose(|| { + eprintln!("rustc freshness: {freshness:?}"); + }); + match freshness { + PathFreshness::LastModifiedUpstream { upstream } => upstream, + PathFreshness::HasLocalModifications { upstream } => { + if if_unchanged { + return None; + } + + if dwn_ctx.is_running_on_ci { + eprintln!("CI rustc commit matches with HEAD and we are in CI."); + eprintln!( + "`rustc.download-ci` functionality will be skipped as artifacts are not available." + ); + return None; + } + + upstream + } + PathFreshness::MissingUpstream => { + eprintln!("No upstream commit found"); + return None; + } + } + } else { + channel::read_commit_info_file(dwn_ctx.src) + .map(|info| info.sha.trim().to_owned()) + .expect("git-commit-info is missing in the project root") + }; + + Some(commit) +} + +pub fn check_path_modifications_<'a>( + dwn_ctx: impl AsRef>, + paths: &[&'static str], +) -> PathFreshness { + let dwn_ctx = dwn_ctx.as_ref(); + // Checking path modifications through git can be relatively expensive (>100ms). + // We do not assume that the sources would change during bootstrap's execution, + // so we can cache the results here. + // Note that we do not use a static variable for the cache, because it would cause problems + // in tests that create separate `Config` instsances. + dwn_ctx + .path_modification_cache + .lock() + .unwrap() + .entry(paths.to_vec()) + .or_insert_with(|| { + check_path_modifications( + dwn_ctx.src, + &git_config(dwn_ctx.stage0_metadata), + paths, + CiEnv::current(), + ) + .unwrap() + }) + .clone() +} + +pub fn git_config(stage0_metadata: &build_helper::stage0_parser::Stage0) -> GitConfig<'_> { + GitConfig { + nightly_branch: &stage0_metadata.config.nightly_branch, + git_merge_commit_email: &stage0_metadata.config.git_merge_commit_email, + } +} + +pub fn parse_download_ci_llvm<'a>( + dwn_ctx: impl AsRef>, + download_ci_llvm: Option, + asserts: bool, +) -> bool { + let dwn_ctx = dwn_ctx.as_ref(); + + // We don't ever want to use `true` on CI, as we should not + // download upstream artifacts if there are any local modifications. + let default = if dwn_ctx.is_running_on_ci { + StringOrBool::String("if-unchanged".to_string()) + } else { + StringOrBool::Bool(true) + }; + let download_ci_llvm = download_ci_llvm.unwrap_or(default); + + let if_unchanged = || { + if dwn_ctx.rust_info.is_from_tarball() { + // Git is needed for running "if-unchanged" logic. + println!("ERROR: 'if-unchanged' is only compatible with Git managed sources."); + crate::exit!(1); + } + + // Fetching the LLVM submodule is unnecessary for self-tests. + #[cfg(not(test))] + update_submodule(dwn_ctx, "src/llvm-project"); + + // Check for untracked changes in `src/llvm-project` and other important places. + let has_changes = has_changes_from_upstream(dwn_ctx, LLVM_INVALIDATION_PATHS); + + // Return false if there are untracked changes, otherwise check if CI LLVM is available. + if has_changes { + false + } else { + llvm::is_ci_llvm_available_for_target(&dwn_ctx.host_target, asserts) + } + }; + + match download_ci_llvm { + StringOrBool::Bool(b) => { + if !b && dwn_ctx.download_rustc_commit.is_some() { + panic!( + "`llvm.download-ci-llvm` cannot be set to `false` if `rust.download-rustc` is set to `true` or `if-unchanged`." + ); + } + + if b && dwn_ctx.is_running_on_ci { + // On CI, we must always rebuild LLVM if there were any modifications to it + panic!( + "`llvm.download-ci-llvm` cannot be set to `true` on CI. Use `if-unchanged` instead." + ); + } + + // If download-ci-llvm=true we also want to check that CI llvm is available + b && llvm::is_ci_llvm_available_for_target(&dwn_ctx.host_target, asserts) + } + StringOrBool::String(s) if s == "if-unchanged" => if_unchanged(), + StringOrBool::String(other) => { + panic!("unrecognized option for download-ci-llvm: {other:?}") + } + } +} + +pub fn has_changes_from_upstream<'a>( + dwn_ctx: impl AsRef>, + paths: &[&'static str], +) -> bool { + let dwn_ctx = dwn_ctx.as_ref(); + match check_path_modifications_(dwn_ctx, paths) { + PathFreshness::LastModifiedUpstream { .. } => false, + PathFreshness::HasLocalModifications { .. } | PathFreshness::MissingUpstream => true, + } +} + +#[cfg_attr( + feature = "tracing", + instrument( + level = "trace", + name = "Config::update_submodule", + skip_all, + fields(relative_path = ?relative_path), + ), +)] +pub(crate) fn update_submodule<'a>(dwn_ctx: impl AsRef>, relative_path: &str) { + let dwn_ctx = dwn_ctx.as_ref(); + if dwn_ctx.rust_info.is_from_tarball() || !submodules_(dwn_ctx.submodules, dwn_ctx.rust_info) { + return; + } + + let absolute_path = dwn_ctx.src.join(relative_path); + + // NOTE: This check is required because `jj git clone` doesn't create directories for + // submodules, they are completely ignored. The code below assumes this directory exists, + // so create it here. + if !absolute_path.exists() { + t!(fs::create_dir_all(&absolute_path)); + } + + // NOTE: The check for the empty directory is here because when running x.py the first time, + // the submodule won't be checked out. Check it out now so we can build it. + if !git_info(dwn_ctx.exec_ctx, false, &absolute_path).is_managed_git_subrepository() + && !helpers::dir_is_empty(&absolute_path) + { + return; + } + + // Submodule updating actually happens during in the dry run mode. We need to make sure that + // all the git commands below are actually executed, because some follow-up code + // in bootstrap might depend on the submodules being checked out. Furthermore, not all + // the command executions below work with an empty output (produced during dry run). + // Therefore, all commands below are marked with `run_in_dry_run()`, so that they also run in + // dry run mode. + let submodule_git = || { + let mut cmd = helpers::git(Some(&absolute_path)); + cmd.run_in_dry_run(); + cmd + }; + + // Determine commit checked out in submodule. + let checked_out_hash = + submodule_git().args(["rev-parse", "HEAD"]).run_capture_stdout(dwn_ctx.exec_ctx).stdout(); + let checked_out_hash = checked_out_hash.trim_end(); + // Determine commit that the submodule *should* have. + let recorded = helpers::git(Some(dwn_ctx.src)) + .run_in_dry_run() + .args(["ls-tree", "HEAD"]) + .arg(relative_path) + .run_capture_stdout(dwn_ctx.exec_ctx) + .stdout(); + + let actual_hash = recorded + .split_whitespace() + .nth(2) + .unwrap_or_else(|| panic!("unexpected output `{recorded}`")); + + if actual_hash == checked_out_hash { + // already checked out + return; + } + + println!("Updating submodule {relative_path}"); + + helpers::git(Some(dwn_ctx.src)) + .allow_failure() + .run_in_dry_run() + .args(["submodule", "-q", "sync"]) + .arg(relative_path) + .run(dwn_ctx.exec_ctx); + + // Try passing `--progress` to start, then run git again without if that fails. + let update = |progress: bool| { + // Git is buggy and will try to fetch submodules from the tracking branch for *this* repository, + // even though that has no relation to the upstream for the submodule. + let current_branch = helpers::git(Some(dwn_ctx.src)) + .allow_failure() + .run_in_dry_run() + .args(["symbolic-ref", "--short", "HEAD"]) + .run_capture(dwn_ctx.exec_ctx); + + let mut git = helpers::git(Some(dwn_ctx.src)).allow_failure(); + git.run_in_dry_run(); + if current_branch.is_success() { + // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name. + // This syntax isn't accepted by `branch.{branch}`. Strip it. + let branch = current_branch.stdout(); + let branch = branch.trim(); + let branch = branch.strip_prefix("heads/").unwrap_or(branch); + git.arg("-c").arg(format!("branch.{branch}.remote=origin")); + } + git.args(["submodule", "update", "--init", "--recursive", "--depth=1"]); + if progress { + git.arg("--progress"); + } + git.arg(relative_path); + git + }; + if !update(true).allow_failure().run(dwn_ctx.exec_ctx) { + update(false).allow_failure().run(dwn_ctx.exec_ctx); + } + + // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error). + // diff-index reports the modifications through the exit status + let has_local_modifications = !submodule_git() + .allow_failure() + .args(["diff-index", "--quiet", "HEAD"]) + .run(dwn_ctx.exec_ctx); + if has_local_modifications { + submodule_git().allow_failure().args(["stash", "push"]).run(dwn_ctx.exec_ctx); + } + + submodule_git().allow_failure().args(["reset", "-q", "--hard"]).run(dwn_ctx.exec_ctx); + submodule_git().allow_failure().args(["clean", "-qdfx"]).run(dwn_ctx.exec_ctx); + + if has_local_modifications { + submodule_git().allow_failure().args(["stash", "pop"]).run(dwn_ctx.exec_ctx); + } +} + +pub fn git_info(exec_ctx: &ExecutionContext, omit_git_hash: bool, dir: &Path) -> GitInfo { + GitInfo::new(omit_git_hash, dir, exec_ctx) +} + +pub fn submodules_(submodules: &Option, rust_info: &channel::GitInfo) -> bool { + // If not specified in config, the default is to only manage + // submodules if we're currently inside a git repository. + submodules.unwrap_or(rust_info.is_managed_git_subrepository()) +} + +/// Returns `true` if this is an external version of LLVM not managed by bootstrap. +/// In particular, we expect llvm sources to be available when this is false. +/// +/// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set. +pub fn is_system_llvm<'a>( + dwn_ctx: impl AsRef>, + target: TargetSelection, +) -> bool { + let dwn_ctx = dwn_ctx.as_ref(); + match dwn_ctx.target_config.get(&target) { + Some(Target { llvm_config: Some(_), .. }) => { + let ci_llvm = dwn_ctx.llvm_from_ci && is_host_target(&dwn_ctx.host_target, &target); + !ci_llvm + } + // We're building from the in-tree src/llvm-project sources. + Some(Target { llvm_config: None, .. }) => false, + None => false, + } +} + +pub fn is_host_target(host_target: &TargetSelection, target: &TargetSelection) -> bool { + host_target == target +} + +pub(crate) fn ci_llvm_root<'a>(dwn_ctx: impl AsRef>) -> PathBuf { + let dwn_ctx = dwn_ctx.as_ref(); + assert!(dwn_ctx.llvm_from_ci); + dwn_ctx.out.join(dwn_ctx.host_target).join("ci-llvm") +} + +/// Returns the content of the given file at a specific commit. +pub(crate) fn read_file_by_commit<'a>( + dwn_ctx: impl AsRef>, + file: &Path, + commit: &str, +) -> String { + let dwn_ctx = dwn_ctx.as_ref(); + assert!( + dwn_ctx.rust_info.is_managed_git_subrepository(), + "`Config::read_file_by_commit` is not supported in non-git sources." + ); + + let mut git = helpers::git(Some(dwn_ctx.src)); + git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap())); + git.run_capture_stdout(dwn_ctx.exec_ctx).stdout() +} diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 31a427f9ffaa..c01b71b92606 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -15,7 +15,7 @@ use crate::core::build_steps::setup::Profile; use crate::core::builder::{Builder, Kind}; use crate::core::config::Config; use crate::core::config::target_selection::{TargetSelectionList, target_selection_list}; -use crate::{Build, DocTests}; +use crate::{Build, CodegenBackendKind, DocTests}; #[derive(Copy, Clone, Default, Debug, ValueEnum)] pub enum Color { @@ -241,7 +241,7 @@ fn normalize_args(args: &[String]) -> Vec { it.collect() } -#[derive(Debug, Clone, Default, clap::Subcommand)] +#[derive(Debug, Clone, clap::Subcommand)] pub enum Subcommand { #[command(aliases = ["b"], long_about = "\n Arguments: @@ -256,8 +256,11 @@ pub enum Subcommand { ./x.py build --stage 0 ./x.py build ")] /// Compile either the compiler or libraries - #[default] - Build, + Build { + #[arg(long)] + /// Pass `--timings` to Cargo to get crate build timings + timings: bool, + }, #[command(aliases = ["c"], long_about = "\n Arguments: This subcommand accepts a number of paths to directories to the crates @@ -269,6 +272,9 @@ pub enum Subcommand { #[arg(long)] /// Check all targets all_targets: bool, + #[arg(long)] + /// Pass `--timings` to Cargo to get crate build timings + timings: bool, }, /// Run Clippy (uses rustup/cargo-installed clippy binary) #[command(long_about = "\n @@ -413,6 +419,9 @@ pub enum Subcommand { #[arg(long)] /// don't capture stdout/stderr of tests no_capture: bool, + #[arg(long)] + /// Use a different codegen backend when running tests. + test_codegen_backend: Option, }, /// Build and run some test suites *in Miri* Miri { @@ -494,11 +503,17 @@ Arguments: Perf(PerfArgs), } +impl Default for Subcommand { + fn default() -> Self { + Subcommand::Build { timings: false } + } +} + impl Subcommand { pub fn kind(&self) -> Kind { match self { Subcommand::Bench { .. } => Kind::Bench, - Subcommand::Build => Kind::Build, + Subcommand::Build { .. } => Kind::Build, Subcommand::Check { .. } => Kind::Check, Subcommand::Clippy { .. } => Kind::Clippy, Subcommand::Doc { .. } => Kind::Doc, @@ -626,6 +641,13 @@ impl Subcommand { } } + pub fn timings(&self) -> bool { + match *self { + Subcommand::Build { timings, .. } | Subcommand::Check { timings, .. } => timings, + _ => false, + } + } + pub fn vendor_versioned_dirs(&self) -> bool { match *self { Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs, @@ -639,6 +661,13 @@ impl Subcommand { _ => vec![], } } + + pub fn test_codegen_backend(&self) -> Option<&CodegenBackendKind> { + match self { + Subcommand::Test { test_codegen_backend, .. } => test_codegen_backend.as_ref(), + _ => None, + } + } } /// Returns the shell completion for a given shell, if the result differs from the current diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs index 285d20917e7d..8c5f90372514 100644 --- a/src/bootstrap/src/core/config/mod.rs +++ b/src/bootstrap/src/core/config/mod.rs @@ -324,7 +324,7 @@ impl FromStr for LlvmLibunwind { } } -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub enum SplitDebuginfo { Packed, Unpacked, diff --git a/src/bootstrap/src/core/config/target_selection.rs b/src/bootstrap/src/core/config/target_selection.rs index ebd3fe7a8c68..40b63a7f9c75 100644 --- a/src/bootstrap/src/core/config/target_selection.rs +++ b/src/bootstrap/src/core/config/target_selection.rs @@ -14,7 +14,7 @@ pub struct TargetSelection { } /// Newtype over `Vec` so we can implement custom parsing logic -#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)] pub struct TargetSelectionList(pub Vec); pub fn target_selection_list(s: &str) -> Result { diff --git a/src/bootstrap/src/core/config/toml/dist.rs b/src/bootstrap/src/core/config/toml/dist.rs index b1429ef18617..934d64d88991 100644 --- a/src/bootstrap/src/core/config/toml/dist.rs +++ b/src/bootstrap/src/core/config/toml/dist.rs @@ -7,11 +7,12 @@ use serde::{Deserialize, Deserializer}; +use crate::core::config::Merge; use crate::core::config::toml::ReplaceOpt; -use crate::core::config::{Merge, set}; -use crate::{Config, HashSet, PathBuf, define_config, exit}; +use crate::{HashSet, PathBuf, define_config, exit}; define_config! { + #[derive(Default)] struct Dist { sign_folder: Option = "sign-folder", upload_addr: Option = "upload-addr", @@ -22,31 +23,3 @@ define_config! { vendor: Option = "vendor", } } - -impl Config { - /// Applies distribution-related configuration from the `Dist` struct - /// to the global `Config` structure. - pub fn apply_dist_config(&mut self, toml_dist: Option) { - if let Some(dist) = toml_dist { - let Dist { - sign_folder, - upload_addr, - src_tarball, - compression_formats, - compression_profile, - include_mingw_linker, - vendor, - } = dist; - self.dist_sign_folder = sign_folder.map(PathBuf::from); - self.dist_upload_addr = upload_addr; - self.dist_compression_formats = compression_formats; - set(&mut self.dist_compression_profile, compression_profile); - set(&mut self.rust_dist_src, src_tarball); - set(&mut self.dist_include_mingw_linker, include_mingw_linker); - self.dist_vendor = vendor.unwrap_or_else(|| { - // If we're building from git or tarball sources, enable it by default. - self.rust_info.is_managed_git_subrepository() || self.rust_info.is_from_tarball() - }); - } - } -} diff --git a/src/bootstrap/src/core/config/toml/gcc.rs b/src/bootstrap/src/core/config/toml/gcc.rs index bb817c2aaef8..9ea697edf159 100644 --- a/src/bootstrap/src/core/config/toml/gcc.rs +++ b/src/bootstrap/src/core/config/toml/gcc.rs @@ -6,29 +6,14 @@ use serde::{Deserialize, Deserializer}; +use crate::core::config::Merge; use crate::core::config::toml::ReplaceOpt; -use crate::core::config::{GccCiMode, Merge}; -use crate::{Config, HashSet, PathBuf, define_config, exit}; +use crate::{HashSet, PathBuf, define_config, exit}; define_config! { /// TOML representation of how the GCC build is configured. + #[derive(Default)] struct Gcc { download_ci_gcc: Option = "download-ci-gcc", } } - -impl Config { - /// Applies GCC-related configuration from the `TomlGcc` struct to the - /// global `Config` structure. - pub fn apply_gcc_config(&mut self, toml_gcc: Option) { - if let Some(gcc) = toml_gcc { - self.gcc_ci_mode = match gcc.download_ci_gcc { - Some(value) => match value { - true => GccCiMode::DownloadFromCi, - false => GccCiMode::BuildLocally, - }, - None => GccCiMode::default(), - }; - } - } -} diff --git a/src/bootstrap/src/core/config/toml/install.rs b/src/bootstrap/src/core/config/toml/install.rs index 6b9ab87a0b66..60fa958bd82f 100644 --- a/src/bootstrap/src/core/config/toml/install.rs +++ b/src/bootstrap/src/core/config/toml/install.rs @@ -8,12 +8,13 @@ use serde::{Deserialize, Deserializer}; +use crate::core::config::Merge; use crate::core::config::toml::ReplaceOpt; -use crate::core::config::{Merge, set}; -use crate::{Config, HashSet, PathBuf, define_config, exit}; +use crate::{HashSet, PathBuf, define_config, exit}; define_config! { /// TOML representation of various global install decisions. + #[derive(Default)] struct Install { prefix: Option = "prefix", sysconfdir: Option = "sysconfdir", @@ -24,20 +25,3 @@ define_config! { datadir: Option = "datadir", } } - -impl Config { - /// Applies installation-related configuration from the `Install` struct - /// to the global `Config` structure. - pub fn apply_install_config(&mut self, toml_install: Option) { - if let Some(install) = toml_install { - let Install { prefix, sysconfdir, docdir, bindir, libdir, mandir, datadir } = install; - self.prefix = prefix.map(PathBuf::from); - self.sysconfdir = sysconfdir.map(PathBuf::from); - self.datadir = datadir.map(PathBuf::from); - self.docdir = docdir.map(PathBuf::from); - set(&mut self.bindir, bindir.map(PathBuf::from)); - self.libdir = libdir.map(PathBuf::from); - self.mandir = mandir.map(PathBuf::from); - } - } -} diff --git a/src/bootstrap/src/core/config/toml/llvm.rs b/src/bootstrap/src/core/config/toml/llvm.rs index 1f0cecd145c7..9751837a8879 100644 --- a/src/bootstrap/src/core/config/toml/llvm.rs +++ b/src/bootstrap/src/core/config/toml/llvm.rs @@ -3,12 +3,13 @@ use serde::{Deserialize, Deserializer}; +use crate::core::config::StringOrBool; use crate::core::config::toml::{Merge, ReplaceOpt, TomlConfig}; -use crate::core::config::{StringOrBool, set}; -use crate::{Config, HashMap, HashSet, PathBuf, define_config, exit}; +use crate::{HashMap, HashSet, PathBuf, define_config, exit}; define_config! { /// TOML representation of how the LLVM build is configured. + #[derive(Default)] struct Llvm { optimize: Option = "optimize", thin_lto: Option = "thin-lto", @@ -144,127 +145,3 @@ pub fn check_incompatible_options_for_ci_llvm( Ok(()) } - -impl Config { - pub fn apply_llvm_config(&mut self, toml_llvm: Option) { - let mut llvm_tests = None; - let mut llvm_enzyme = None; - let mut llvm_offload = None; - let mut llvm_plugins = None; - - if let Some(llvm) = toml_llvm { - let Llvm { - optimize: optimize_toml, - thin_lto, - release_debuginfo, - assertions: _, - tests, - enzyme, - plugins, - static_libstdcpp, - libzstd, - ninja, - targets, - experimental_targets, - link_jobs, - link_shared, - version_suffix, - clang_cl, - cflags, - cxxflags, - ldflags, - use_libcxx, - use_linker, - allow_old_toolchain, - offload, - polly, - clang, - enable_warnings, - download_ci_llvm, - build_config, - } = llvm; - - set(&mut self.ninja_in_file, ninja); - llvm_tests = tests; - llvm_enzyme = enzyme; - llvm_offload = offload; - llvm_plugins = plugins; - set(&mut self.llvm_optimize, optimize_toml); - set(&mut self.llvm_thin_lto, thin_lto); - set(&mut self.llvm_release_debuginfo, release_debuginfo); - set(&mut self.llvm_static_stdcpp, static_libstdcpp); - set(&mut self.llvm_libzstd, libzstd); - if let Some(v) = link_shared { - self.llvm_link_shared.set(Some(v)); - } - self.llvm_targets.clone_from(&targets); - self.llvm_experimental_targets.clone_from(&experimental_targets); - self.llvm_link_jobs = link_jobs; - self.llvm_version_suffix.clone_from(&version_suffix); - self.llvm_clang_cl.clone_from(&clang_cl); - - self.llvm_cflags.clone_from(&cflags); - self.llvm_cxxflags.clone_from(&cxxflags); - self.llvm_ldflags.clone_from(&ldflags); - set(&mut self.llvm_use_libcxx, use_libcxx); - self.llvm_use_linker.clone_from(&use_linker); - self.llvm_allow_old_toolchain = allow_old_toolchain.unwrap_or(false); - self.llvm_offload = offload.unwrap_or(false); - self.llvm_polly = polly.unwrap_or(false); - self.llvm_clang = clang.unwrap_or(false); - self.llvm_enable_warnings = enable_warnings.unwrap_or(false); - self.llvm_build_config = build_config.clone().unwrap_or(Default::default()); - - self.llvm_from_ci = self.parse_download_ci_llvm(download_ci_llvm, self.llvm_assertions); - - if self.llvm_from_ci { - let warn = |option: &str| { - println!( - "WARNING: `{option}` will only be used on `compiler/rustc_llvm` build, not for the LLVM build." - ); - println!( - "HELP: To use `{option}` for LLVM builds, set `download-ci-llvm` option to false." - ); - }; - - if static_libstdcpp.is_some() { - warn("static-libstdcpp"); - } - - if link_shared.is_some() { - warn("link-shared"); - } - - // FIXME(#129153): instead of all the ad-hoc `download-ci-llvm` checks that follow, - // use the `builder-config` present in tarballs since #128822 to compare the local - // config to the ones used to build the LLVM artifacts on CI, and only notify users - // if they've chosen a different value. - - if libzstd.is_some() { - println!( - "WARNING: when using `download-ci-llvm`, the local `llvm.libzstd` option, \ - like almost all `llvm.*` options, will be ignored and set by the LLVM CI \ - artifacts builder config." - ); - println!( - "HELP: To use `llvm.libzstd` for LLVM/LLD builds, set `download-ci-llvm` option to false." - ); - } - } - - if !self.llvm_from_ci && self.llvm_thin_lto && link_shared.is_none() { - // If we're building with ThinLTO on, by default we want to link - // to LLVM shared, to avoid re-doing ThinLTO (which happens in - // the link step) with each stage. - self.llvm_link_shared.set(Some(true)); - } - } else { - self.llvm_from_ci = self.parse_download_ci_llvm(None, false); - } - - self.llvm_tests = llvm_tests.unwrap_or(false); - self.llvm_enzyme = llvm_enzyme.unwrap_or(false); - self.llvm_offload = llvm_offload.unwrap_or(false); - self.llvm_plugins = llvm_plugins.unwrap_or(false); - } -} diff --git a/src/bootstrap/src/core/config/toml/mod.rs b/src/bootstrap/src/core/config/toml/mod.rs index 01eb243159ce..7af22432ef8c 100644 --- a/src/bootstrap/src/core/config/toml/mod.rs +++ b/src/bootstrap/src/core/config/toml/mod.rs @@ -5,7 +5,7 @@ //! these raw TOML configurations from various sources (the main `bootstrap.toml`, //! included files, profile defaults, and command-line overrides). This processed //! TOML data then serves as an intermediate representation, which is further -//! transformed and applied to the final [`Config`] struct. +//! transformed and applied to the final `Config` struct. use serde::Deserialize; use serde_derive::Deserialize; diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 03da993a17dd..3dab8d1d96d5 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -1,22 +1,15 @@ //! This module defines the `Rust` struct, which represents the `[rust]` table //! in the `bootstrap.toml` configuration file. -use std::str::FromStr; - use serde::{Deserialize, Deserializer}; -use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX; use crate::core::config::toml::TomlConfig; -use crate::core::config::{ - DebuginfoLevel, Merge, ReplaceOpt, RustcLto, StringOrBool, set, threads_from_config, -}; -use crate::flags::Warnings; -use crate::{ - BTreeSet, CodegenBackendKind, Config, HashSet, PathBuf, TargetSelection, define_config, exit, -}; +use crate::core::config::{DebuginfoLevel, Merge, ReplaceOpt, StringOrBool}; +use crate::{BTreeSet, CodegenBackendKind, HashSet, PathBuf, TargetSelection, define_config, exit}; define_config! { /// TOML representation of how the Rust build is configured. + #[derive(Default)] struct Rust { optimize: Option = "optimize", debug: Option = "debug", @@ -397,6 +390,8 @@ pub(crate) fn parse_codegen_backends( backends: Vec, section: &str, ) -> Vec { + const CODEGEN_BACKEND_PREFIX: &str = "rustc_codegen_"; + let mut found_backends = vec![]; for backend in &backends { if let Some(stripped) = backend.strip_prefix(CODEGEN_BACKEND_PREFIX) { @@ -424,7 +419,7 @@ pub(crate) fn parse_codegen_backends( } #[cfg(not(test))] -fn default_lld_opt_in_targets() -> Vec { +pub fn default_lld_opt_in_targets() -> Vec { vec!["x86_64-unknown-linux-gnu".to_string()] } @@ -434,7 +429,7 @@ thread_local! { } #[cfg(test)] -fn default_lld_opt_in_targets() -> Vec { +pub fn default_lld_opt_in_targets() -> Vec { TEST_LLD_OPT_IN_TARGETS.with(|cell| cell.borrow().clone()).unwrap_or_default() } @@ -447,250 +442,3 @@ pub fn with_lld_opt_in_targets(targets: Vec, f: impl FnOnce() -> R) - result }) } - -impl Config { - pub fn apply_rust_config(&mut self, toml_rust: Option, warnings: Warnings) { - let mut debug = None; - let mut rustc_debug_assertions = None; - let mut std_debug_assertions = None; - let mut tools_debug_assertions = None; - let mut overflow_checks = None; - let mut overflow_checks_std = None; - let mut debug_logging = None; - let mut debuginfo_level = None; - let mut debuginfo_level_rustc = None; - let mut debuginfo_level_std = None; - let mut debuginfo_level_tools = None; - let mut debuginfo_level_tests = None; - let mut optimize = None; - let mut lld_enabled = None; - let mut std_features = None; - - if let Some(rust) = toml_rust { - let Rust { - optimize: optimize_toml, - debug: debug_toml, - codegen_units, - codegen_units_std, - rustc_debug_assertions: rustc_debug_assertions_toml, - std_debug_assertions: std_debug_assertions_toml, - tools_debug_assertions: tools_debug_assertions_toml, - overflow_checks: overflow_checks_toml, - overflow_checks_std: overflow_checks_std_toml, - debug_logging: debug_logging_toml, - debuginfo_level: debuginfo_level_toml, - debuginfo_level_rustc: debuginfo_level_rustc_toml, - debuginfo_level_std: debuginfo_level_std_toml, - debuginfo_level_tools: debuginfo_level_tools_toml, - debuginfo_level_tests: debuginfo_level_tests_toml, - backtrace, - incremental, - randomize_layout, - default_linker, - channel: _, // already handled above - musl_root, - rpath, - verbose_tests, - optimize_tests, - codegen_tests, - omit_git_hash: _, // already handled above - dist_src, - save_toolstates, - codegen_backends, - lld: lld_enabled_toml, - llvm_tools, - llvm_bitcode_linker, - deny_warnings, - backtrace_on_ice, - verify_llvm_ir, - thin_lto_import_instr_limit, - remap_debuginfo, - jemalloc, - test_compare_mode, - llvm_libunwind, - control_flow_guard, - ehcont_guard, - new_symbol_mangling, - profile_generate, - profile_use, - download_rustc, - lto, - validate_mir_opts, - frame_pointers, - stack_protector, - strip, - lld_mode, - std_features: std_features_toml, - } = rust; - - // FIXME(#133381): alt rustc builds currently do *not* have rustc debug assertions - // enabled. We should not download a CI alt rustc if we need rustc to have debug - // assertions (e.g. for crashes test suite). This can be changed once something like - // [Enable debug assertions on alt - // builds](https://github.com/rust-lang/rust/pull/131077) lands. - // - // Note that `rust.debug = true` currently implies `rust.debug-assertions = true`! - // - // This relies also on the fact that the global default for `download-rustc` will be - // `false` if it's not explicitly set. - let debug_assertions_requested = matches!(rustc_debug_assertions_toml, Some(true)) - || (matches!(debug_toml, Some(true)) - && !matches!(rustc_debug_assertions_toml, Some(false))); - - if debug_assertions_requested - && let Some(ref opt) = download_rustc - && opt.is_string_or_true() - { - eprintln!( - "WARN: currently no CI rustc builds have rustc debug assertions \ - enabled. Please either set `rust.debug-assertions` to `false` if you \ - want to use download CI rustc or set `rust.download-rustc` to `false`." - ); - } - - self.download_rustc_commit = self.download_ci_rustc_commit( - download_rustc, - debug_assertions_requested, - self.llvm_assertions, - ); - - debug = debug_toml; - rustc_debug_assertions = rustc_debug_assertions_toml; - std_debug_assertions = std_debug_assertions_toml; - tools_debug_assertions = tools_debug_assertions_toml; - overflow_checks = overflow_checks_toml; - overflow_checks_std = overflow_checks_std_toml; - debug_logging = debug_logging_toml; - debuginfo_level = debuginfo_level_toml; - debuginfo_level_rustc = debuginfo_level_rustc_toml; - debuginfo_level_std = debuginfo_level_std_toml; - debuginfo_level_tools = debuginfo_level_tools_toml; - debuginfo_level_tests = debuginfo_level_tests_toml; - lld_enabled = lld_enabled_toml; - std_features = std_features_toml; - - if optimize_toml.as_ref().is_some_and(|v| matches!(v, RustOptimize::Bool(false))) { - eprintln!( - "WARNING: setting `optimize` to `false` is known to cause errors and \ - should be considered unsupported. Refer to `bootstrap.example.toml` \ - for more details." - ); - } - - optimize = optimize_toml; - self.rust_new_symbol_mangling = new_symbol_mangling; - set(&mut self.rust_optimize_tests, optimize_tests); - set(&mut self.codegen_tests, codegen_tests); - set(&mut self.rust_rpath, rpath); - set(&mut self.rust_strip, strip); - set(&mut self.rust_frame_pointers, frame_pointers); - self.rust_stack_protector = stack_protector; - set(&mut self.jemalloc, jemalloc); - set(&mut self.test_compare_mode, test_compare_mode); - set(&mut self.backtrace, backtrace); - set(&mut self.rust_dist_src, dist_src); - set(&mut self.verbose_tests, verbose_tests); - // in the case "false" is set explicitly, do not overwrite the command line args - if let Some(true) = incremental { - self.incremental = true; - } - set(&mut self.lld_mode, lld_mode); - set(&mut self.llvm_bitcode_linker_enabled, llvm_bitcode_linker); - - self.rust_randomize_layout = randomize_layout.unwrap_or_default(); - self.llvm_tools_enabled = llvm_tools.unwrap_or(true); - - self.llvm_enzyme = self.channel == "dev" || self.channel == "nightly"; - self.rustc_default_linker = default_linker; - self.musl_root = musl_root.map(PathBuf::from); - self.save_toolstates = save_toolstates.map(PathBuf::from); - set( - &mut self.deny_warnings, - match warnings { - Warnings::Deny => Some(true), - Warnings::Warn => Some(false), - Warnings::Default => deny_warnings, - }, - ); - set(&mut self.backtrace_on_ice, backtrace_on_ice); - set(&mut self.rust_verify_llvm_ir, verify_llvm_ir); - self.rust_thin_lto_import_instr_limit = thin_lto_import_instr_limit; - set(&mut self.rust_remap_debuginfo, remap_debuginfo); - set(&mut self.control_flow_guard, control_flow_guard); - set(&mut self.ehcont_guard, ehcont_guard); - self.llvm_libunwind_default = - llvm_libunwind.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); - set( - &mut self.rust_codegen_backends, - codegen_backends.map(|backends| parse_codegen_backends(backends, "rust")), - ); - - self.rust_codegen_units = codegen_units.map(threads_from_config); - self.rust_codegen_units_std = codegen_units_std.map(threads_from_config); - - if self.rust_profile_use.is_none() { - self.rust_profile_use = profile_use; - } - - if self.rust_profile_generate.is_none() { - self.rust_profile_generate = profile_generate; - } - - self.rust_lto = - lto.as_deref().map(|value| RustcLto::from_str(value).unwrap()).unwrap_or_default(); - self.rust_validate_mir_opts = validate_mir_opts; - } - - self.rust_optimize = optimize.unwrap_or(RustOptimize::Bool(true)); - - // We make `x86_64-unknown-linux-gnu` use the self-contained linker by default, so we will - // build our internal lld and use it as the default linker, by setting the `rust.lld` config - // to true by default: - // - on the `x86_64-unknown-linux-gnu` target - // - when building our in-tree llvm (i.e. the target has not set an `llvm-config`), so that - // we're also able to build the corresponding lld - // - or when using an external llvm that's downloaded from CI, which also contains our prebuilt - // lld - // - otherwise, we'd be using an external llvm, and lld would not necessarily available and - // thus, disabled - // - similarly, lld will not be built nor used by default when explicitly asked not to, e.g. - // when the config sets `rust.lld = false` - if default_lld_opt_in_targets().contains(&self.host_target.triple.to_string()) - && self.hosts == [self.host_target] - { - let no_llvm_config = self - .target_config - .get(&self.host_target) - .is_none_or(|target_config| target_config.llvm_config.is_none()); - let enable_lld = self.llvm_from_ci || no_llvm_config; - // Prefer the config setting in case an explicit opt-out is needed. - self.lld_enabled = lld_enabled.unwrap_or(enable_lld); - } else { - set(&mut self.lld_enabled, lld_enabled); - } - - let default_std_features = BTreeSet::from([String::from("panic-unwind")]); - self.rust_std_features = std_features.unwrap_or(default_std_features); - - let default = debug == Some(true); - self.rustc_debug_assertions = rustc_debug_assertions.unwrap_or(default); - self.std_debug_assertions = std_debug_assertions.unwrap_or(self.rustc_debug_assertions); - self.tools_debug_assertions = tools_debug_assertions.unwrap_or(self.rustc_debug_assertions); - self.rust_overflow_checks = overflow_checks.unwrap_or(default); - self.rust_overflow_checks_std = overflow_checks_std.unwrap_or(self.rust_overflow_checks); - - self.rust_debug_logging = debug_logging.unwrap_or(self.rustc_debug_assertions); - - let with_defaults = |debuginfo_level_specific: Option<_>| { - debuginfo_level_specific.or(debuginfo_level).unwrap_or(if debug == Some(true) { - DebuginfoLevel::Limited - } else { - DebuginfoLevel::None - }) - }; - self.rust_debuginfo_level_rustc = with_defaults(debuginfo_level_rustc); - self.rust_debuginfo_level_std = with_defaults(debuginfo_level_std); - self.rust_debuginfo_level_tools = with_defaults(debuginfo_level_tools); - self.rust_debuginfo_level_tests = debuginfo_level_tests.unwrap_or(DebuginfoLevel::None); - } -} diff --git a/src/bootstrap/src/core/config/toml/target.rs b/src/bootstrap/src/core/config/toml/target.rs index 9dedadff3a19..2c06fd083a81 100644 --- a/src/bootstrap/src/core/config/toml/target.rs +++ b/src/bootstrap/src/core/config/toml/target.rs @@ -7,18 +7,12 @@ //! * [`TomlTarget`]: This struct directly mirrors the `[target.]` sections in your //! `bootstrap.toml`. It's used for deserializing raw TOML data for a specific target. //! * [`Target`]: This struct represents the processed and validated configuration for a -//! build target, which is is stored in the main [`Config`] structure. -//! * [`Config::apply_target_config`]: This method processes the `TomlTarget` data and -//! applies it to the global [`Config`], ensuring proper path resolution, validation, -//! and integration with other build settings. - -use std::collections::HashMap; +//! build target, which is is stored in the main `Config` structure. use serde::{Deserialize, Deserializer}; -use crate::core::config::toml::rust::parse_codegen_backends; use crate::core::config::{LlvmLibunwind, Merge, ReplaceOpt, SplitDebuginfo, StringOrBool}; -use crate::{CodegenBackendKind, Config, HashSet, PathBuf, TargetSelection, define_config, exit}; +use crate::{CodegenBackendKind, HashSet, PathBuf, define_config, exit}; define_config! { /// TOML representation of how each build target is configured. @@ -93,68 +87,3 @@ impl Target { target } } - -impl Config { - pub fn apply_target_config(&mut self, toml_target: Option>) { - if let Some(t) = toml_target { - for (triple, cfg) in t { - let mut target = Target::from_triple(&triple); - - if let Some(ref s) = cfg.llvm_config { - if self.download_rustc_commit.is_some() && triple == *self.host_target.triple { - panic!( - "setting llvm_config for the host is incompatible with download-rustc" - ); - } - target.llvm_config = Some(self.src.join(s)); - } - if let Some(patches) = cfg.llvm_has_rust_patches { - assert!( - self.submodules == Some(false) || cfg.llvm_config.is_some(), - "use of `llvm-has-rust-patches` is restricted to cases where either submodules are disabled or llvm-config been provided" - ); - target.llvm_has_rust_patches = Some(patches); - } - if let Some(ref s) = cfg.llvm_filecheck { - target.llvm_filecheck = Some(self.src.join(s)); - } - target.llvm_libunwind = cfg.llvm_libunwind.as_ref().map(|v| { - v.parse().unwrap_or_else(|_| { - panic!("failed to parse target.{triple}.llvm-libunwind") - }) - }); - if let Some(s) = cfg.no_std { - target.no_std = s; - } - target.cc = cfg.cc.map(PathBuf::from); - target.cxx = cfg.cxx.map(PathBuf::from); - target.ar = cfg.ar.map(PathBuf::from); - target.ranlib = cfg.ranlib.map(PathBuf::from); - target.linker = cfg.linker.map(PathBuf::from); - target.crt_static = cfg.crt_static; - target.musl_root = cfg.musl_root.map(PathBuf::from); - target.musl_libdir = cfg.musl_libdir.map(PathBuf::from); - target.wasi_root = cfg.wasi_root.map(PathBuf::from); - target.qemu_rootfs = cfg.qemu_rootfs.map(PathBuf::from); - target.runner = cfg.runner; - target.sanitizers = cfg.sanitizers; - target.profiler = cfg.profiler; - target.rpath = cfg.rpath; - target.optimized_compiler_builtins = cfg.optimized_compiler_builtins; - target.jemalloc = cfg.jemalloc; - if let Some(backends) = cfg.codegen_backends { - target.codegen_backends = - Some(parse_codegen_backends(backends, &format!("target.{triple}"))) - } - - target.split_debuginfo = cfg.split_debuginfo.as_ref().map(|v| { - v.parse().unwrap_or_else(|_| { - panic!("invalid value for target.{triple}.split-debuginfo") - }) - }); - - self.target_config.insert(TargetSelection::from_user(&triple), target); - } - } - } -} diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index 7ec6c62a07d0..5ded44cef144 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -1,14 +1,17 @@ +use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::fs::{self, File}; use std::io::{BufRead, BufReader, BufWriter, ErrorKind, Write}; use std::path::{Path, PathBuf}; -use std::sync::OnceLock; +use std::sync::{Arc, Mutex, OnceLock}; +use build_helper::git::PathFreshness; use xz2::bufread::XzDecoder; -use crate::core::config::{BUILDER_CONFIG_FILENAME, TargetSelection}; +use crate::core::config::{BUILDER_CONFIG_FILENAME, Target, TargetSelection}; use crate::utils::build_stamp::BuildStamp; +use crate::utils::channel; use crate::utils::exec::{ExecutionContext, command}; use crate::utils::helpers::{exe, hex_encode, move_file}; use crate::{Config, t}; @@ -398,14 +401,21 @@ impl Config { /// Only should be used for pre config initialization downloads. pub(crate) struct DownloadContext<'a> { - host_target: TargetSelection, - out: &'a Path, - patch_binaries_for_nix: Option, - exec_ctx: &'a ExecutionContext, - stage0_metadata: &'a build_helper::stage0_parser::Stage0, - llvm_assertions: bool, - bootstrap_cache_path: &'a Option, - is_running_on_ci: bool, + pub path_modification_cache: Arc, PathFreshness>>>, + pub src: &'a Path, + pub rust_info: &'a channel::GitInfo, + pub submodules: &'a Option, + pub download_rustc_commit: &'a Option, + pub host_target: TargetSelection, + pub llvm_from_ci: bool, + pub target_config: &'a HashMap, + pub out: &'a Path, + pub patch_binaries_for_nix: Option, + pub exec_ctx: &'a ExecutionContext, + pub stage0_metadata: &'a build_helper::stage0_parser::Stage0, + pub llvm_assertions: bool, + pub bootstrap_cache_path: &'a Option, + pub is_running_on_ci: bool, } impl<'a> AsRef> for DownloadContext<'a> { @@ -417,7 +427,14 @@ impl<'a> AsRef> for DownloadContext<'a> { impl<'a> From<&'a Config> for DownloadContext<'a> { fn from(value: &'a Config) -> Self { DownloadContext { + path_modification_cache: value.path_modification_cache.clone(), + src: &value.src, host_target: value.host_target, + rust_info: &value.rust_info, + download_rustc_commit: &value.download_rustc_commit, + submodules: &value.submodules, + llvm_from_ci: value.llvm_from_ci, + target_config: &value.target_config, out: &value.out, patch_binaries_for_nix: value.patch_binaries_for_nix, exec_ctx: &value.exec_ctx, diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 15e04f591296..bd02131b7fe7 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -33,7 +33,9 @@ pub struct Finder { // // Targets can be removed from this list once they are present in the stage0 compiler (usually by updating the beta compiler of the bootstrap). const STAGE0_MISSING_TARGETS: &[&str] = &[ + "armv7a-vex-v5", // just a dummy comment so the list doesn't get onelined + "aarch64_be-unknown-none-softfloat", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 011b52df97bb..ec7edbf75312 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -37,14 +37,14 @@ use crate::core::builder; use crate::core::builder::Kind; use crate::core::config::{DryRun, LldMode, LlvmLibunwind, TargetSelection, flags}; use crate::utils::exec::{BootstrapCommand, command}; -use crate::utils::helpers::{ - self, dir_is_empty, exe, libdir, set_file_times, split_debuginfo, symlink_dir, -}; +use crate::utils::helpers::{self, dir_is_empty, exe, libdir, set_file_times, split_debuginfo}; mod core; mod utils; pub use core::builder::PathSet; +#[cfg(feature = "tracing")] +pub use core::builder::STEP_SPAN_TARGET; pub use core::config::flags::{Flags, Subcommand}; pub use core::config::{ChangeId, Config}; @@ -53,7 +53,9 @@ use tracing::{instrument, span}; pub use utils::change_tracker::{ CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes, }; -pub use utils::helpers::PanicTracker; +pub use utils::helpers::{PanicTracker, symlink_dir}; +#[cfg(feature = "tracing")] +pub use utils::tracing::setup_tracing; use crate::core::build_steps::vendor::VENDOR_DIR; @@ -163,6 +165,20 @@ impl CodegenBackendKind { } } +impl std::str::FromStr for CodegenBackendKind { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "" => Err("Invalid empty backend name"), + "gcc" => Ok(Self::Gcc), + "llvm" => Ok(Self::Llvm), + "cranelift" => Ok(Self::Cranelift), + _ => Ok(Self::Custom(s.to_string())), + } + } +} + #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum DocTests { /// Run normal tests and doc tests (default). @@ -188,7 +204,6 @@ pub enum GitRepo { /// although most functions are implemented as free functions rather than /// methods specifically on this structure itself (to make it easier to /// organize). -#[derive(Clone)] pub struct Build { /// User-specified configuration from `bootstrap.toml`. config: Config, @@ -244,6 +259,9 @@ pub struct Build { #[cfg(feature = "build-metrics")] metrics: crate::utils::metrics::BuildMetrics, + + #[cfg(feature = "tracing")] + step_graph: std::cell::RefCell, } #[derive(Debug, Clone)] @@ -275,7 +293,7 @@ pub enum DependencyType { /// /// These entries currently correspond to the various output directories of the /// build system, with each mod generating output in a different directory. -#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] pub enum Mode { /// Build the standard library, placing output in the "stageN-std" directory. Std, @@ -353,7 +371,7 @@ pub enum RemapScheme { NonCompiler, } -#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] pub enum CLang { C, Cxx, @@ -408,6 +426,25 @@ forward! { download_rustc() -> bool, } +/// A mostly temporary helper struct before we can migrate everything in bootstrap to use +/// the concept of a build compiler. +struct HostAndStage { + host: TargetSelection, + stage: u32, +} + +impl From<(TargetSelection, u32)> for HostAndStage { + fn from((host, stage): (TargetSelection, u32)) -> Self { + Self { host, stage } + } +} + +impl From for HostAndStage { + fn from(compiler: Compiler) -> Self { + Self { host: compiler.host, stage: compiler.stage } + } +} + impl Build { /// Creates a new set of build configuration from the `flags` on the command /// line and the filesystem `config`. @@ -515,7 +552,7 @@ impl Build { local_rebuild: config.local_rebuild, fail_fast: config.cmd.fail_fast(), doc_tests: config.cmd.doc_tests(), - verbosity: config.verbose, + verbosity: config.exec_ctx.verbosity as usize, host_target: config.host_target, hosts: config.hosts.clone(), @@ -547,6 +584,9 @@ impl Build { #[cfg(feature = "build-metrics")] metrics: crate::utils::metrics::BuildMetrics::init(), + + #[cfg(feature = "tracing")] + step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()), }; // If local-rust is the same major.minor as the current version, then force a @@ -854,14 +894,17 @@ impl Build { if self.config.rust_optimize.is_release() { "release" } else { "debug" } } - fn tools_dir(&self, compiler: Compiler) -> PathBuf { - let out = self.out.join(compiler.host).join(format!("stage{}-tools-bin", compiler.stage)); + fn tools_dir(&self, build_compiler: Compiler) -> PathBuf { + let out = self + .out + .join(build_compiler.host) + .join(format!("stage{}-tools-bin", build_compiler.stage + 1)); t!(fs::create_dir_all(&out)); out } /// Returns the root directory for all output generated in a particular - /// stage when running with a particular host compiler. + /// stage when being built with a particular build compiler. /// /// The mode indicates what the root directory is for. fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf { @@ -871,15 +914,17 @@ impl Build { (None, "bootstrap-tools") } fn staged_tool(build_compiler: Compiler) -> (Option, &'static str) { - (Some(build_compiler.stage), "tools") + (Some(build_compiler.stage + 1), "tools") } let (stage, suffix) = match mode { + // Std is special, stage N std is built with stage N rustc Mode::Std => (Some(build_compiler.stage), "std"), - Mode::Rustc => (Some(build_compiler.stage), "rustc"), - Mode::Codegen => (Some(build_compiler.stage), "codegen"), + // The rest of things are built with stage N-1 rustc + Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"), + Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"), Mode::ToolBootstrap => bootstrap_tool(), - Mode::ToolStd | Mode::ToolRustc => (Some(build_compiler.stage), "tools"), + Mode::ToolStd | Mode::ToolRustc => (Some(build_compiler.stage + 1), "tools"), Mode::ToolTarget => { // If we're not cross-compiling (the common case), share the target directory with // bootstrap tools to reuse the build cache. @@ -902,8 +947,8 @@ impl Build { /// Returns the root output directory for all Cargo output in a given stage, /// running a particular compiler, whether or not we're building the /// standard library, and targeting the specified architecture. - fn cargo_out(&self, compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf { - self.stage_out(compiler, mode).join(target).join(self.cargo_dir()) + fn cargo_out(&self, build_compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf { + self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir()) } /// Root output directory of LLVM for `target` @@ -1062,56 +1107,14 @@ impl Build { } } - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_clippy( - &self, - what: impl Display, - target: impl Into>, - ) -> Option { - self.msg(Kind::Clippy, self.config.stage, what, self.config.host_target, target) - } - - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_check( - &self, - what: impl Display, - target: impl Into>, - custom_stage: Option, - ) -> Option { - self.msg( - Kind::Check, - custom_stage.unwrap_or(self.config.stage), - what, - self.config.host_target, - target, - ) - } - - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_doc( - &self, - compiler: Compiler, - what: impl Display, - target: impl Into> + Copy, - ) -> Option { - self.msg(Kind::Doc, compiler.stage, what, compiler.host, target.into()) - } - - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_build( - &self, - compiler: Compiler, - what: impl Display, - target: impl Into>, - ) -> Option { - self.msg(Kind::Build, compiler.stage, what, compiler.host, target) - } - - /// Return a `Group` guard for a [`Step`] that is built for each `--stage`. + /// Return a `Group` guard for a [`Step`] that: + /// - Performs `action` + /// - On `what` + /// - Where `what` possibly corresponds to a `mode` + /// - `action` is performed using the given build compiler (`host_and_stage`). + /// - Since some steps do not use the concept of a build compiler yet, it is also possible + /// to pass the host and stage explicitly. + /// - With a given `target`. /// /// [`Step`]: crate::core::builder::Step #[must_use = "Groups should not be dropped until the Step finishes running"] @@ -1119,19 +1122,36 @@ impl Build { fn msg( &self, action: impl Into, - stage: u32, what: impl Display, - host: impl Into>, + mode: impl Into>, + host_and_stage: impl Into, target: impl Into>, ) -> Option { + let host_and_stage = host_and_stage.into(); + let actual_stage = match mode.into() { + // Std has the same stage as the compiler that builds it + Some(Mode::Std) => host_and_stage.stage, + // Other things have stage corresponding to their build compiler + 1 + Some( + Mode::Rustc + | Mode::Codegen + | Mode::ToolBootstrap + | Mode::ToolTarget + | Mode::ToolStd + | Mode::ToolRustc, + ) + | None => host_and_stage.stage + 1, + }; + let action = action.into().description(); - let msg = |fmt| format!("{action} stage{stage} {what}{fmt}"); + let msg = |fmt| format!("{action} stage{actual_stage} {what}{fmt}"); let msg = if let Some(target) = target.into() { - let host = host.into().unwrap(); + let build_stage = host_and_stage.stage; + let host = host_and_stage.host; if host == target { - msg(format_args!(" ({target})")) + msg(format_args!(" (stage{build_stage} -> stage{actual_stage}, {target})")) } else { - msg(format_args!(" ({host} -> {target})")) + msg(format_args!(" (stage{build_stage}:{host} -> stage{actual_stage}:{target})")) } } else { msg(format_args!("")) @@ -1155,26 +1175,6 @@ impl Build { self.group(&msg) } - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_sysroot_tool( - &self, - action: impl Into, - stage: u32, - what: impl Display, - host: TargetSelection, - target: TargetSelection, - ) -> Option { - let action = action.into().description(); - let msg = |fmt| format!("{action} {what} {fmt}"); - let msg = if host == target { - msg(format_args!("(stage{stage} -> stage{}, {target})", stage + 1)) - } else { - msg(format_args!("(stage{stage}:{host} -> stage{}:{target})", stage + 1)) - }; - self.group(&msg) - } - #[track_caller] fn group(&self, msg: &str) -> Option { match self.config.get_dry_run() { @@ -1757,6 +1757,7 @@ impl Build { /// /// If `src` is a symlink, `src` will be resolved to the actual path /// and copied to `dst` instead of the symlink itself. + #[track_caller] pub fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) { self.copy_link_internal(src, dst, true); } @@ -1765,6 +1766,7 @@ impl Build { /// Attempts to use hard links if possible, falling back to copying. /// You can neither rely on this being a copy nor it being a link, /// so do not write to dst. + #[track_caller] pub fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) { self.copy_link_internal(src, dst, false); @@ -1779,6 +1781,7 @@ impl Build { } } + #[track_caller] fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) { if self.config.dry_run() { return; @@ -1787,6 +1790,10 @@ impl Build { if src == dst { return; } + + #[cfg(feature = "tracing")] + let _span = trace_io!("file-copy-link", ?src, ?dst); + if let Err(e) = fs::remove_file(dst) && cfg!(windows) && e.kind() != io::ErrorKind::NotFound @@ -1829,6 +1836,7 @@ impl Build { /// Links the `src` directory recursively to `dst`. Both are assumed to exist /// when this function is called. /// Will attempt to use hard links if possible and fall back to copying. + #[track_caller] pub fn cp_link_r(&self, src: &Path, dst: &Path) { if self.config.dry_run() { return; @@ -1851,12 +1859,14 @@ impl Build { /// Will attempt to use hard links if possible and fall back to copying. /// Unwanted files or directories can be skipped /// by returning `false` from the filter function. + #[track_caller] pub fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) { // Immediately recurse with an empty relative path self.cp_link_filtered_recurse(src, dst, Path::new(""), filter) } // Inner function does the actual work + #[track_caller] fn cp_link_filtered_recurse( &self, src: &Path, @@ -1876,7 +1886,6 @@ impl Build { self.create_dir(&dst); self.cp_link_filtered_recurse(&path, &dst, &relative, filter); } else { - let _ = fs::remove_file(&dst); self.copy_link(&path, &dst, FileType::Regular); } } @@ -1918,10 +1927,15 @@ impl Build { t!(fs::read_to_string(path)) } + #[track_caller] fn create_dir(&self, dir: &Path) { if self.config.dry_run() { return; } + + #[cfg(feature = "tracing")] + let _span = trace_io!("dir-create", ?dir); + t!(fs::create_dir_all(dir)) } @@ -1929,6 +1943,10 @@ impl Build { if self.config.dry_run() { return; } + + #[cfg(feature = "tracing")] + let _span = trace_io!("dir-remove", ?dir); + t!(fs::remove_dir_all(dir)) } @@ -2021,8 +2039,13 @@ to download LLVM rather than building it. &self.config.exec_ctx } - pub fn report_summary(&self, start_time: Instant) { - self.config.exec_ctx.profiler().report_summary(start_time); + pub fn report_summary(&self, path: &Path, start_time: Instant) { + self.config.exec_ctx.profiler().report_summary(path, start_time); + } + + #[cfg(feature = "tracing")] + pub fn report_step_graph(self, directory: &Path) { + self.step_graph.into_inner().store_to_dot_files(directory); } } diff --git a/src/bootstrap/src/utils/build_stamp.rs b/src/bootstrap/src/utils/build_stamp.rs index bd4eb790ae50..6c79385190e8 100644 --- a/src/bootstrap/src/utils/build_stamp.rs +++ b/src/bootstrap/src/utils/build_stamp.rs @@ -146,13 +146,13 @@ pub fn libstd_stamp( } /// Cargo's output path for librustc in a given stage, compiled by a particular -/// compiler for the specified target. +/// `build_compiler` for the specified target. pub fn librustc_stamp( builder: &Builder<'_>, - compiler: Compiler, + build_compiler: Compiler, target: TargetSelection, ) -> BuildStamp { - BuildStamp::new(&builder.cargo_out(compiler, Mode::Rustc, target)).with_prefix("librustc") + BuildStamp::new(&builder.cargo_out(build_compiler, Mode::Rustc, target)).with_prefix("librustc") } /// Computes a hash representing the state of a repository/submodule and additional input. diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index d3331b81587e..4fb5891ed181 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -491,4 +491,29 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Warning, summary: "Added `build.compiletest-allow-stage0` flag instead of `COMPILETEST_FORCE_STAGE0` env var, and reject running `compiletest` self tests against stage 0 rustc unless explicitly allowed.", }, + ChangeInfo { + change_id: 145011, + severity: ChangeSeverity::Warning, + summary: "It is no longer possible to `x doc` with stage 0. All doc commands have to be on stage 1+.", + }, + ChangeInfo { + change_id: 145295, + severity: ChangeSeverity::Warning, + summary: "The names of stageN directories in the build directory have been consolidated with the new (post-stage-0-redesign) staging scheme. Some tools and binaries might be located in a different build directory than before.", + }, + ChangeInfo { + change_id: 145131, + severity: ChangeSeverity::Warning, + summary: "It is no longer possible to `x clippy` with stage 0. All clippy commands have to be on stage 1+.", + }, + ChangeInfo { + change_id: 145256, + severity: ChangeSeverity::Info, + summary: "Added `--test-codegen-backend` CLI option for tests", + }, + ChangeInfo { + change_id: 145379, + severity: ChangeSeverity::Info, + summary: "Build/check now supports forwarding `--timings` flag to cargo.", + }, ]; diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs index 209ff3939731..9a536f75ab74 100644 --- a/src/bootstrap/src/utils/exec.rs +++ b/src/bootstrap/src/utils/exec.rs @@ -15,7 +15,6 @@ use std::hash::Hash; use std::io::{BufWriter, Write}; use std::panic::Location; use std::path::Path; -use std::process; use std::process::{ Child, ChildStderr, ChildStdout, Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio, }; @@ -26,10 +25,8 @@ use build_helper::ci::CiEnv; use build_helper::drop_bomb::DropBomb; use build_helper::exit; -use crate::PathBuf; use crate::core::config::DryRun; -#[cfg(feature = "tracing")] -use crate::trace_cmd; +use crate::{PathBuf, t}; /// What should be done when the command fails. #[derive(Debug, Copy, Clone)] @@ -77,14 +74,32 @@ pub struct CommandFingerprint { } impl CommandFingerprint { + #[cfg(feature = "tracing")] + pub(crate) fn program_name(&self) -> String { + Path::new(&self.program) + .file_name() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()) + } + /// Helper method to format both Command and BootstrapCommand as a short execution line, /// without all the other details (e.g. environment variables). - pub fn format_short_cmd(&self) -> String { - let program = Path::new(&self.program); - let mut line = vec![program.file_name().unwrap().to_str().unwrap().to_owned()]; - line.extend(self.args.iter().map(|arg| arg.to_string_lossy().into_owned())); - line.extend(self.cwd.iter().map(|p| p.to_string_lossy().into_owned())); - line.join(" ") + pub(crate) fn format_short_cmd(&self) -> String { + use std::fmt::Write; + + let mut cmd = self.program.to_string_lossy().to_string(); + for arg in &self.args { + let arg = arg.to_string_lossy(); + if arg.contains(' ') { + write!(cmd, " '{arg}'").unwrap(); + } else { + write!(cmd, " {arg}").unwrap(); + } + } + if let Some(cwd) = &self.cwd { + write!(cmd, " [workdir={}]", cwd.to_string_lossy()).unwrap(); + } + cmd } } @@ -111,17 +126,9 @@ impl CommandProfiler { entry.traces.push(ExecutionTrace::CacheHit); } - pub fn report_summary(&self, start_time: Instant) { - let pid = process::id(); - let filename = format!("bootstrap-profile-{pid}.txt"); - - let file = match File::create(&filename) { - Ok(f) => f, - Err(e) => { - eprintln!("Failed to create profiler output file: {e}"); - return; - } - }; + /// Report summary of executed commands file at the specified `path`. + pub fn report_summary(&self, path: &Path, start_time: Instant) { + let file = t!(File::create(path)); let mut writer = BufWriter::new(file); let stats = self.stats.lock().unwrap(); @@ -211,8 +218,6 @@ impl CommandProfiler { writeln!(writer, "Total cache hits: {total_cache_hits}").unwrap(); writeln!(writer, "Estimated time saved due to cache hits: {total_saved_duration:.2?}") .unwrap(); - - println!("Command profiler report saved to {filename}"); } } @@ -434,8 +439,8 @@ impl From for BootstrapCommand { enum CommandStatus { /// The command has started and finished with some status. Finished(ExitStatus), - /// It was not even possible to start the command. - DidNotStart, + /// It was not even possible to start the command or wait for it to finish. + DidNotStartOrFinish, } /// Create a new BootstrapCommand. This is a helper function to make command creation @@ -456,9 +461,9 @@ pub struct CommandOutput { impl CommandOutput { #[must_use] - pub fn did_not_start(stdout: OutputMode, stderr: OutputMode) -> Self { + pub fn not_finished(stdout: OutputMode, stderr: OutputMode) -> Self { Self { - status: CommandStatus::DidNotStart, + status: CommandStatus::DidNotStartOrFinish, stdout: match stdout { OutputMode::Print => None, OutputMode::Capture => Some(vec![]), @@ -489,7 +494,7 @@ impl CommandOutput { pub fn is_success(&self) -> bool { match self.status { CommandStatus::Finished(status) => status.success(), - CommandStatus::DidNotStart => false, + CommandStatus::DidNotStartOrFinish => false, } } @@ -501,7 +506,7 @@ impl CommandOutput { pub fn status(&self) -> Option { match self.status { CommandStatus::Finished(status) => Some(status), - CommandStatus::DidNotStart => None, + CommandStatus::DidNotStartOrFinish => None, } } @@ -550,7 +555,7 @@ impl Default for CommandOutput { #[derive(Clone, Default)] pub struct ExecutionContext { dry_run: DryRun, - verbose: u8, + pub verbosity: u8, pub fail_fast: bool, delayed_failures: Arc>>, command_cache: Arc, @@ -603,8 +608,8 @@ impl CommandCache { } impl ExecutionContext { - pub fn new() -> Self { - ExecutionContext::default() + pub fn new(verbosity: u8, fail_fast: bool) -> Self { + Self { verbosity, fail_fast, ..Default::default() } } pub fn dry_run(&self) -> bool { @@ -629,7 +634,7 @@ impl ExecutionContext { } pub fn is_verbose(&self) -> bool { - self.verbose > 0 + self.verbosity > 0 } pub fn fail_fast(&self) -> bool { @@ -640,8 +645,8 @@ impl ExecutionContext { self.dry_run = value; } - pub fn set_verbose(&mut self, value: u8) { - self.verbose = value; + pub fn set_verbosity(&mut self, value: u8) { + self.verbosity = value; } pub fn set_fail_fast(&mut self, value: bool) { @@ -676,9 +681,6 @@ impl ExecutionContext { ) -> DeferredCommand<'a> { let fingerprint = command.fingerprint(); - #[cfg(feature = "tracing")] - let span_guard = trace_cmd!(command); - if let Some(cached_output) = self.command_cache.get(&fingerprint) { command.mark_as_executed(); self.verbose(|| println!("Cache hit: {command:?}")); @@ -686,6 +688,9 @@ impl ExecutionContext { return DeferredCommand { state: CommandState::Cached(cached_output) }; } + #[cfg(feature = "tracing")] + let span_guard = crate::utils::tracing::trace_cmd(command); + let created_at = command.get_created_location(); let executed_at = std::panic::Location::caller(); @@ -745,25 +750,11 @@ impl ExecutionContext { self.start(command, stdout, stderr).wait_for_output(self) } - fn fail(&self, message: &str, output: CommandOutput) -> ! { - if self.is_verbose() { - println!("{message}"); - } else { - let (stdout, stderr) = (output.stdout_if_present(), output.stderr_if_present()); - // If the command captures output, the user would not see any indication that - // it has failed. In this case, print a more verbose error, since to provide more - // context. - if stdout.is_some() || stderr.is_some() { - if let Some(stdout) = output.stdout_if_present().take_if(|s| !s.trim().is_empty()) { - println!("STDOUT:\n{stdout}\n"); - } - if let Some(stderr) = output.stderr_if_present().take_if(|s| !s.trim().is_empty()) { - println!("STDERR:\n{stderr}\n"); - } - println!("Command has failed. Rerun with -v to see more details."); - } else { - println!("Command has failed. Rerun with -v to see more details."); - } + fn fail(&self, message: &str) -> ! { + println!("{message}"); + + if !self.is_verbose() { + println!("Command has failed. Rerun with -v to see more details."); } exit!(1); } @@ -783,7 +774,7 @@ impl ExecutionContext { } #[cfg(feature = "tracing")] - let span_guard = trace_cmd!(command); + let span_guard = crate::utils::tracing::trace_cmd(command); let start_time = Instant::now(); let fingerprint = command.fingerprint(); @@ -856,7 +847,7 @@ impl<'a> DeferredCommand<'a> { && command.should_cache { exec_ctx.command_cache.insert(fingerprint.clone(), output.clone()); - exec_ctx.profiler.record_execution(fingerprint.clone(), start_time); + exec_ctx.profiler.record_execution(fingerprint, start_time); } output @@ -872,6 +863,8 @@ impl<'a> DeferredCommand<'a> { executed_at: &'a std::panic::Location<'a>, exec_ctx: &ExecutionContext, ) -> CommandOutput { + use std::fmt::Write; + command.mark_as_executed(); let process = match process.take() { @@ -881,79 +874,82 @@ impl<'a> DeferredCommand<'a> { let created_at = command.get_created_location(); - let mut message = String::new(); + #[allow(clippy::enum_variant_names)] + enum FailureReason { + FailedAtRuntime(ExitStatus), + FailedToFinish(std::io::Error), + FailedToStart(std::io::Error), + } - let output = match process { + let (output, fail_reason) = match process { Ok(child) => match child.wait_with_output() { - Ok(result) if result.status.success() => { + Ok(output) if output.status.success() => { // Successful execution - CommandOutput::from_output(result, stdout, stderr) + (CommandOutput::from_output(output, stdout, stderr), None) } - Ok(result) => { - // Command ran but failed - use std::fmt::Write; - - writeln!( - message, - r#" -Command {command:?} did not execute successfully. -Expected success, got {} -Created at: {created_at} -Executed at: {executed_at}"#, - result.status, + Ok(output) => { + // Command started, but then it failed + let status = output.status; + ( + CommandOutput::from_output(output, stdout, stderr), + Some(FailureReason::FailedAtRuntime(status)), ) - .unwrap(); - - let output = CommandOutput::from_output(result, stdout, stderr); - - if stdout.captures() { - writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap(); - } - if stderr.captures() { - writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap(); - } - - output } Err(e) => { // Failed to wait for output - use std::fmt::Write; - - writeln!( - message, - "\n\nCommand {command:?} did not execute successfully.\ - \nIt was not possible to execute the command: {e:?}" + ( + CommandOutput::not_finished(stdout, stderr), + Some(FailureReason::FailedToFinish(e)), ) - .unwrap(); - - CommandOutput::did_not_start(stdout, stderr) } }, Err(e) => { // Failed to spawn the command - use std::fmt::Write; - - writeln!( - message, - "\n\nCommand {command:?} did not execute successfully.\ - \nIt was not possible to execute the command: {e:?}" - ) - .unwrap(); - - CommandOutput::did_not_start(stdout, stderr) + (CommandOutput::not_finished(stdout, stderr), Some(FailureReason::FailedToStart(e))) } }; - if !output.is_success() { + if let Some(fail_reason) = fail_reason { + let mut error_message = String::new(); + let command_str = if exec_ctx.is_verbose() { + format!("{command:?}") + } else { + command.fingerprint().format_short_cmd() + }; + let action = match fail_reason { + FailureReason::FailedAtRuntime(e) => { + format!("failed with exit code {}", e.code().unwrap_or(1)) + } + FailureReason::FailedToFinish(e) => { + format!("failed to finish: {e:?}") + } + FailureReason::FailedToStart(e) => { + format!("failed to start: {e:?}") + } + }; + writeln!( + error_message, + r#"Command `{command_str}` {action} +Created at: {created_at} +Executed at: {executed_at}"#, + ) + .unwrap(); + if stdout.captures() { + writeln!(error_message, "\n--- STDOUT vvv\n{}", output.stdout().trim()).unwrap(); + } + if stderr.captures() { + writeln!(error_message, "\n--- STDERR vvv\n{}", output.stderr().trim()).unwrap(); + } + match command.failure_behavior { BehaviorOnFailure::DelayFail => { if exec_ctx.fail_fast { - exec_ctx.fail(&message, output); + exec_ctx.fail(&error_message); } - exec_ctx.add_to_delay_failure(message); + exec_ctx.add_to_delay_failure(error_message); } BehaviorOnFailure::Exit => { - exec_ctx.fail(&message, output); + exec_ctx.fail(&error_message); } BehaviorOnFailure::Ignore => { // If failures are allowed, either the error has been printed already diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index eb00ed566c2d..451482717b6c 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -404,9 +404,8 @@ pub fn linker_args( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, - stage: u32, ) -> Vec { - let mut args = linker_flags(builder, target, lld_threads, stage); + let mut args = linker_flags(builder, target, lld_threads); if let Some(linker) = builder.linker(target) { args.push(format!("-Clinker={}", linker.display())); @@ -421,29 +420,16 @@ pub fn linker_flags( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, - stage: u32, ) -> Vec { let mut args = vec![]; if !builder.is_lld_direct_linker(target) && builder.config.lld_mode.is_used() { match builder.config.lld_mode { LldMode::External => { - // cfg(bootstrap) - remove the stage 0 check after updating the bootstrap compiler: - // `-Clinker-features` has been stabilized. - if stage == 0 { - args.push("-Zlinker-features=+lld".to_string()); - } else { - args.push("-Clinker-features=+lld".to_string()); - } + args.push("-Clinker-features=+lld".to_string()); args.push("-Zunstable-options".to_string()); } LldMode::SelfContained => { - // cfg(bootstrap) - remove the stage 0 check after updating the bootstrap compiler: - // `-Clinker-features` has been stabilized. - if stage == 0 { - args.push("-Zlinker-features=+lld".to_string()); - } else { - args.push("-Clinker-features=+lld".to_string()); - } + args.push("-Clinker-features=+lld".to_string()); args.push("-Clink-self-contained=+linker".to_string()); args.push("-Zunstable-options".to_string()); } @@ -465,9 +451,8 @@ pub fn add_rustdoc_cargo_linker_args( builder: &Builder<'_>, target: TargetSelection, lld_threads: LldThreads, - stage: u32, ) { - let args = linker_args(builder, target, lld_threads, stage); + let args = linker_args(builder, target, lld_threads); let mut flags = cmd .get_envs() .find_map(|(k, v)| if k == OsStr::new("RUSTDOCFLAGS") { v } else { None }) diff --git a/src/bootstrap/src/utils/mod.rs b/src/bootstrap/src/utils/mod.rs index 169fcec303e9..97d8d274e8fb 100644 --- a/src/bootstrap/src/utils/mod.rs +++ b/src/bootstrap/src/utils/mod.rs @@ -19,5 +19,8 @@ pub(crate) mod tracing; #[cfg(feature = "build-metrics")] pub(crate) mod metrics; +#[cfg(feature = "tracing")] +pub(crate) mod step_graph; + #[cfg(test)] pub(crate) mod tests; diff --git a/src/bootstrap/src/utils/shared_helpers.rs b/src/bootstrap/src/utils/shared_helpers.rs index 9428e221f412..d620cc4bbb6b 100644 --- a/src/bootstrap/src/utils/shared_helpers.rs +++ b/src/bootstrap/src/utils/shared_helpers.rs @@ -81,10 +81,11 @@ pub fn parse_rustc_verbose() -> usize { } /// Parses the value of the "RUSTC_STAGE" environment variable and returns it as a `String`. +/// This is the stage of the *build compiler*, which we are wrapping using a rustc/rustdoc wrapper. /// /// If "RUSTC_STAGE" was not set, the program will be terminated with 101. -pub fn parse_rustc_stage() -> String { - env::var("RUSTC_STAGE").unwrap_or_else(|_| { +pub fn parse_rustc_stage() -> u32 { + env::var("RUSTC_STAGE").ok().and_then(|v| v.parse().ok()).unwrap_or_else(|| { // Don't panic here; it's reasonable to try and run these shims directly. Give a helpful error instead. eprintln!("rustc shim: FATAL: RUSTC_STAGE was not set"); eprintln!("rustc shim: NOTE: use `x.py build -vvv` to see all environment variables set by bootstrap"); diff --git a/src/bootstrap/src/utils/step_graph.rs b/src/bootstrap/src/utils/step_graph.rs new file mode 100644 index 000000000000..cffe0dbb3e3a --- /dev/null +++ b/src/bootstrap/src/utils/step_graph.rs @@ -0,0 +1,178 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::io::BufWriter; +use std::path::Path; + +use crate::core::builder::{AnyDebug, Step, pretty_step_name}; +use crate::t; + +/// Records the executed steps and their dependencies in a directed graph, +/// which can then be rendered into a DOT file for visualization. +/// +/// The graph visualizes the first execution of a step with a solid edge, +/// and cached executions of steps with a dashed edge. +/// If you only want to see first executions, you can modify the code in `DotGraph` to +/// always set `cached: false`. +#[derive(Default)] +pub struct StepGraph { + /// We essentially store one graph per dry run mode. + graphs: HashMap, +} + +impl StepGraph { + pub fn register_step_execution( + &mut self, + step: &S, + parent: Option<&Box>, + dry_run: bool, + ) { + let key = get_graph_key(dry_run); + let graph = self.graphs.entry(key.to_string()).or_insert_with(|| DotGraph::default()); + + // The debug output of the step sort of serves as the unique identifier of it. + // We use it to access the node ID of parents to generate edges. + // We could probably also use addresses on the heap from the `Box`, but this seems less + // magical. + let node_key = render_step(step); + + let label = if let Some(metadata) = step.metadata() { + format!( + "{}{} [{}]", + metadata.get_name(), + metadata.get_stage().map(|s| format!(" stage {s}")).unwrap_or_default(), + metadata.get_target() + ) + } else { + pretty_step_name::() + }; + + let node = Node { label, tooltip: node_key.clone() }; + let node_handle = graph.add_node(node_key, node); + + if let Some(parent) = parent { + let parent_key = render_step(parent); + if let Some(src_node_handle) = graph.get_handle_by_key(&parent_key) { + graph.add_edge(src_node_handle, node_handle); + } + } + } + + pub fn register_cached_step( + &mut self, + step: &S, + parent: &Box, + dry_run: bool, + ) { + let key = get_graph_key(dry_run); + let graph = self.graphs.get_mut(key).unwrap(); + + let node_key = render_step(step); + let parent_key = render_step(parent); + + if let Some(src_node_handle) = graph.get_handle_by_key(&parent_key) { + if let Some(dst_node_handle) = graph.get_handle_by_key(&node_key) { + graph.add_cached_edge(src_node_handle, dst_node_handle); + } + } + } + + pub fn store_to_dot_files(self, directory: &Path) { + for (key, graph) in self.graphs.into_iter() { + let filename = directory.join(format!("step-graph{key}.dot")); + t!(graph.render(&filename)); + } + } +} + +fn get_graph_key(dry_run: bool) -> &'static str { + if dry_run { ".dryrun" } else { "" } +} + +struct Node { + label: String, + tooltip: String, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +struct NodeHandle(usize); + +/// Represents a dependency between two bootstrap steps. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] +struct Edge { + src: NodeHandle, + dst: NodeHandle, + // Was the corresponding execution of a step cached, or was the step actually executed? + cached: bool, +} + +// We could use a library for this, but they either: +// - require lifetimes, which gets annoying (dot_writer) +// - don't support tooltips (dot_graph) +// - have a lot of dependencies (graphviz_rust) +// - only have SVG export (layout-rs) +// - use a builder pattern that is very annoying to use here (tabbycat) +#[derive(Default)] +struct DotGraph { + nodes: Vec, + /// The `NodeHandle` represents an index within `self.nodes` + edges: HashSet, + key_to_index: HashMap, +} + +impl DotGraph { + fn add_node(&mut self, key: String, node: Node) -> NodeHandle { + let handle = NodeHandle(self.nodes.len()); + self.nodes.push(node); + self.key_to_index.insert(key, handle); + handle + } + + fn add_edge(&mut self, src: NodeHandle, dst: NodeHandle) { + self.edges.insert(Edge { src, dst, cached: false }); + } + + fn add_cached_edge(&mut self, src: NodeHandle, dst: NodeHandle) { + // There's no point in rendering both cached and uncached edge + let uncached = Edge { src, dst, cached: false }; + if !self.edges.contains(&uncached) { + self.edges.insert(Edge { src, dst, cached: true }); + } + } + + fn get_handle_by_key(&self, key: &str) -> Option { + self.key_to_index.get(key).copied() + } + + fn render(&self, path: &Path) -> std::io::Result<()> { + use std::io::Write; + + let mut file = BufWriter::new(std::fs::File::create(path)?); + writeln!(file, "digraph bootstrap_steps {{")?; + for (index, node) in self.nodes.iter().enumerate() { + writeln!( + file, + r#"{index} [label="{}", tooltip="{}"]"#, + escape(&node.label), + escape(&node.tooltip) + )?; + } + + let mut edges: Vec<&Edge> = self.edges.iter().collect(); + edges.sort(); + for edge in edges { + let style = if edge.cached { "dashed" } else { "solid" }; + writeln!(file, r#"{} -> {} [style="{style}"]"#, edge.src.0, edge.dst.0)?; + } + + writeln!(file, "}}") + } +} + +fn render_step(step: &dyn Debug) -> String { + format!("{step:?}") +} + +/// Normalizes the string so that it can be rendered into a DOT file. +fn escape(input: &str) -> String { + input.replace("\"", "\\\"") +} diff --git a/src/bootstrap/src/utils/tracing.rs b/src/bootstrap/src/utils/tracing.rs index 109407bc5f23..472781ffa73a 100644 --- a/src/bootstrap/src/utils/tracing.rs +++ b/src/bootstrap/src/utils/tracing.rs @@ -48,17 +48,408 @@ macro_rules! error { } } +#[cfg(feature = "tracing")] +pub const IO_SPAN_TARGET: &str = "IO"; + +/// Create a tracing span around an I/O operation, if tracing is enabled. +/// Note that at least one tracing value field has to be passed to this macro, otherwise it will not +/// compile. #[macro_export] -macro_rules! trace_cmd { - ($cmd:expr) => { +macro_rules! trace_io { + ($name:expr, $($args:tt)*) => { + ::tracing::trace_span!( + target: $crate::utils::tracing::IO_SPAN_TARGET, + $name, + $($args)*, + location = $crate::utils::tracing::format_location(*::std::panic::Location::caller()) + ).entered() + } +} + +#[cfg(feature = "tracing")] +pub fn format_location(location: std::panic::Location<'static>) -> String { + format!("{}:{}", location.file(), location.line()) +} + +#[cfg(feature = "tracing")] +const COMMAND_SPAN_TARGET: &str = "COMMAND"; + +#[cfg(feature = "tracing")] +pub fn trace_cmd(command: &crate::BootstrapCommand) -> tracing::span::EnteredSpan { + let fingerprint = command.fingerprint(); + let location = command.get_created_location(); + let location = format_location(location); + + tracing::span!( + target: COMMAND_SPAN_TARGET, + tracing::Level::TRACE, + "cmd", + cmd_name = fingerprint.program_name().to_string(), + cmd = fingerprint.format_short_cmd(), + full_cmd = ?command, + location + ) + .entered() +} + +// # Note on `tracing` usage in bootstrap +// +// Due to the conditional compilation via the `tracing` cargo feature, this means that `tracing` +// usages in bootstrap need to be also gated behind the `tracing` feature: +// +// - `tracing` macros with log levels (`trace!`, `debug!`, `warn!`, `info`, `error`) should not be +// used *directly*. You should use the wrapped `tracing` macros which gate the actual invocations +// behind `feature = "tracing"`. +// - `tracing`'s `#[instrument(..)]` macro will need to be gated like `#![cfg_attr(feature = +// "tracing", instrument(..))]`. +#[cfg(feature = "tracing")] +mod inner { + use std::fmt::Debug; + use std::fs::File; + use std::io::Write; + use std::path::{Path, PathBuf}; + use std::sync::atomic::Ordering; + + use chrono::{DateTime, Utc}; + use tracing::field::{Field, Visit}; + use tracing::{Event, Id, Level, Subscriber}; + use tracing_subscriber::layer::{Context, SubscriberExt}; + use tracing_subscriber::registry::{LookupSpan, SpanRef}; + use tracing_subscriber::{EnvFilter, Layer}; + + use super::{COMMAND_SPAN_TARGET, IO_SPAN_TARGET}; + use crate::STEP_SPAN_TARGET; + + pub fn setup_tracing(env_name: &str) -> TracingGuard { + let filter = EnvFilter::from_env(env_name); + + let registry = tracing_subscriber::registry().with(filter).with(TracingPrinter::default()); + + // When we're creating this layer, we do not yet know the location of the tracing output + // directory, because it is stored in the output directory determined after Config is parsed, + // but we already want to make tracing calls during (and before) config parsing. + // So we store the output into a temporary file, and then move it to the tracing directory + // before bootstrap ends. + let tempdir = tempfile::TempDir::new().expect("Cannot create temporary directory"); + let chrome_tracing_path = tempdir.path().join("bootstrap-trace.json"); + let file = std::io::BufWriter::new(File::create(&chrome_tracing_path).unwrap()); + + let chrome_layer = tracing_chrome::ChromeLayerBuilder::new() + .writer(file) + .include_args(true) + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(e) => e.metadata().name().to_string(), + tracing_chrome::EventOrSpan::Span(s) => { + if s.metadata().target() == STEP_SPAN_TARGET + && let Some(extension) = s.extensions().get::() + { + extension.0.clone() + } else if s.metadata().target() == COMMAND_SPAN_TARGET + && let Some(extension) = s.extensions().get::() + { + extension.0.clone() + } else { + s.metadata().name().to_string() + } + } + })); + let (chrome_layer, guard) = chrome_layer.build(); + + tracing::subscriber::set_global_default(registry.with(chrome_layer)).unwrap(); + TracingGuard { guard, _tempdir: tempdir, chrome_tracing_path } + } + + pub struct TracingGuard { + guard: tracing_chrome::FlushGuard, + _tempdir: tempfile::TempDir, + chrome_tracing_path: std::path::PathBuf, + } + + impl TracingGuard { + pub fn copy_to_dir(self, dir: &std::path::Path) { + drop(self.guard); + std::fs::rename(&self.chrome_tracing_path, dir.join("chrome-trace.json")).unwrap(); + } + } + + /// Visitor that extracts both known and unknown field values from events and spans. + #[derive(Default)] + struct FieldValues { + /// Main event message + message: Option, + /// Name of a recorded psna + step_name: Option, + /// Short name of an executed command + cmd_name: Option, + /// The rest of arbitrary event/span fields + fields: Vec<(&'static str, String)>, + } + + impl Visit for FieldValues { + /// Record fields if possible using `record_str`, to avoid rendering simple strings with + /// their `Debug` representation, which adds extra quotes. + fn record_str(&mut self, field: &Field, value: &str) { + match field.name() { + "step_name" => { + self.step_name = Some(value.to_string()); + } + "cmd_name" => { + self.cmd_name = Some(value.to_string()); + } + name => { + self.fields.push((name, value.to_string())); + } + } + } + + fn record_debug(&mut self, field: &Field, value: &dyn Debug) { + let formatted = format!("{value:?}"); + match field.name() { + "message" => { + self.message = Some(formatted); + } + name => { + self.fields.push((name, formatted)); + } + } + } + } + + #[derive(Copy, Clone)] + enum SpanAction { + Enter, + } + + /// Holds the name of a step span, stored in `tracing_subscriber`'s extensions. + struct StepNameExtension(String); + + /// Holds the name of a command span, stored in `tracing_subscriber`'s extensions. + struct CommandNameExtension(String); + + #[derive(Default)] + struct TracingPrinter { + indent: std::sync::atomic::AtomicU32, + span_values: std::sync::Mutex>, + } + + impl TracingPrinter { + fn format_header( + &self, + writer: &mut W, + time: DateTime, + level: &Level, + ) -> std::io::Result<()> { + // Use a fixed-width timestamp without date, that shouldn't be very important + let timestamp = time.format("%H:%M:%S.%3f"); + write!(writer, "{timestamp} ")?; + // Make sure that levels are aligned to the same number of characters, in order not to + // break the layout + write!(writer, "{level:>5} ")?; + write!(writer, "{}", " ".repeat(self.indent.load(Ordering::Relaxed) as usize)) + } + + fn write_event(&self, writer: &mut W, event: &Event<'_>) -> std::io::Result<()> { + let now = Utc::now(); + + self.format_header(writer, now, event.metadata().level())?; + + let mut field_values = FieldValues::default(); + event.record(&mut field_values); + + if let Some(msg) = &field_values.message { + write!(writer, "{msg}")?; + } + + if !field_values.fields.is_empty() { + if field_values.message.is_some() { + write!(writer, " ")?; + } + write!(writer, "[")?; + for (index, (name, value)) in field_values.fields.iter().enumerate() { + write!(writer, "{name} = {value}")?; + if index < field_values.fields.len() - 1 { + write!(writer, ", ")?; + } + } + write!(writer, "]")?; + } + write_location(writer, event.metadata())?; + writeln!(writer)?; + Ok(()) + } + + fn write_span( + &self, + writer: &mut W, + span: SpanRef<'_, S>, + field_values: Option<&FieldValues>, + action: SpanAction, + ) -> std::io::Result<()> + where + S: for<'lookup> LookupSpan<'lookup>, { - ::tracing::span!( - target: "COMMAND", - ::tracing::Level::TRACE, - "executing command", - cmd = $cmd.fingerprint().format_short_cmd(), - full_cmd = ?$cmd - ).entered() + let now = Utc::now(); + + self.format_header(writer, now, span.metadata().level())?; + match action { + SpanAction::Enter => { + write!(writer, "> ")?; + } + } + + fn write_fields<'a, I: IntoIterator, W: Write>( + writer: &mut W, + iter: I, + ) -> std::io::Result<()> { + let items = iter.into_iter().collect::>(); + if !items.is_empty() { + write!(writer, " [")?; + for (index, (name, value)) in items.iter().enumerate() { + write!(writer, "{name} = {value}")?; + if index < items.len() - 1 { + write!(writer, ", ")?; + } + } + write!(writer, "]")?; + } + Ok(()) + } + + // Write fields while treating the "location" field specially, and assuming that it + // contains the source file location relevant to the span. + let write_with_location = |writer: &mut W| -> std::io::Result<()> { + if let Some(values) = field_values { + write_fields( + writer, + values.fields.iter().filter(|(name, _)| *name != "location"), + )?; + let location = + &values.fields.iter().find(|(name, _)| *name == "location").unwrap().1; + let (filename, line) = location.rsplit_once(':').unwrap(); + let filename = shorten_filename(filename); + write!(writer, " ({filename}:{line})",)?; + } + Ok(()) + }; + + // We handle steps specially. We instrument them dynamically in `Builder::ensure`, + // and we want to have custom name for each step span. But tracing doesn't allow setting + // dynamic span names. So we detect step spans here and override their name. + match span.metadata().target() { + // Executed step + STEP_SPAN_TARGET => { + let name = + field_values.and_then(|v| v.step_name.as_deref()).unwrap_or(span.name()); + write!(writer, "{name}")?; + + // There should be only one more field called `args` + if let Some(values) = field_values { + let field = &values.fields[0]; + write!(writer, " {{{}}}", field.1)?; + } + write_location(writer, span.metadata())?; + } + // Executed command + COMMAND_SPAN_TARGET => { + write!(writer, "{}", span.name())?; + write_with_location(writer)?; + } + IO_SPAN_TARGET => { + write!(writer, "{}", span.name())?; + write_with_location(writer)?; + } + // Other span + _ => { + write!(writer, "{}", span.name())?; + if let Some(values) = field_values { + write_fields(writer, values.fields.iter())?; + } + write_location(writer, span.metadata())?; + } + } + + writeln!(writer)?; + Ok(()) + } + } + + fn write_location( + writer: &mut W, + metadata: &'static tracing::Metadata<'static>, + ) -> std::io::Result<()> { + if let Some(filename) = metadata.file() { + let filename = shorten_filename(filename); + + write!(writer, " ({filename}")?; + if let Some(line) = metadata.line() { + write!(writer, ":{line}")?; + } + write!(writer, ")")?; + } + Ok(()) + } + + /// Keep only the module name and file name to make it shorter + fn shorten_filename(filename: &str) -> String { + Path::new(filename) + .components() + // Take last two path components + .rev() + .take(2) + .collect::>() + .into_iter() + .rev() + .collect::() + .display() + .to_string() + } + + impl Layer for TracingPrinter + where + S: Subscriber, + S: for<'lookup> LookupSpan<'lookup>, + { + fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + // Record value of span fields + // Note that we do not implement changing values of span fields after they are created. + // For that we would also need to implement the `on_record` method + let mut field_values = FieldValues::default(); + attrs.record(&mut field_values); + + // We need to propagate the actual name of the span to the Chrome layer below, because + // it cannot access field values. We do that through extensions. + if attrs.metadata().target() == STEP_SPAN_TARGET + && let Some(step_name) = field_values.step_name.clone() + { + ctx.span(id).unwrap().extensions_mut().insert(StepNameExtension(step_name)); + } else if attrs.metadata().target() == COMMAND_SPAN_TARGET + && let Some(cmd_name) = field_values.cmd_name.clone() + { + ctx.span(id).unwrap().extensions_mut().insert(CommandNameExtension(cmd_name)); + } + self.span_values.lock().unwrap().insert(id.clone(), field_values); } - }; + + fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { + let mut writer = std::io::stderr().lock(); + self.write_event(&mut writer, event).unwrap(); + } + + fn on_enter(&self, id: &Id, ctx: Context<'_, S>) { + if let Some(span) = ctx.span(id) { + let mut writer = std::io::stderr().lock(); + let values = self.span_values.lock().unwrap(); + let values = values.get(id); + self.write_span(&mut writer, span, values, SpanAction::Enter).unwrap(); + } + self.indent.fetch_add(1, Ordering::Relaxed); + } + + fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) { + self.indent.fetch_sub(1, Ordering::Relaxed); + } + } } + +#[cfg(feature = "tracing")] +pub use inner::setup_tracing; diff --git a/src/build_helper/src/util.rs b/src/build_helper/src/util.rs index a8355f774e9d..1bdbb7515e25 100644 --- a/src/build_helper/src/util.rs +++ b/src/build_helper/src/util.rs @@ -3,6 +3,8 @@ use std::io::{BufRead, BufReader}; use std::path::Path; use std::process::Command; +use crate::ci::CiEnv; + /// Invokes `build_helper::util::detail_exit` with `cfg!(test)` /// /// This is a macro instead of a function so that it uses `cfg(test)` in the *calling* crate, not in build helper. @@ -20,6 +22,15 @@ pub fn detail_exit(code: i32, is_test: bool) -> ! { if is_test { panic!("status code: {code}"); } else { + // If we're in CI, print the current bootstrap invocation command, to make it easier to + // figure out what exactly has failed. + if CiEnv::is_ci() { + // Skip the first argument, as it will be some absolute path to the bootstrap binary. + let bootstrap_args = + std::env::args().skip(1).map(|a| a.to_string()).collect::>().join(" "); + eprintln!("Bootstrap failed while executing `{bootstrap_args}`"); + } + // otherwise, exit with provided status code std::process::exit(code); } diff --git a/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile index 2b8a3f829c60..e726329753f2 100644 --- a/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile +++ b/src/ci/docker/host-aarch64/dist-aarch64-linux/Dockerfile @@ -96,7 +96,7 @@ ENV RUST_CONFIGURE_ARGS \ --set rust.codegen-units=1 ENV SCRIPT python3 ../x.py build --set rust.debug=true opt-dist && \ - ./build/$HOSTS/stage0-tools-bin/opt-dist linux-ci -- python3 ../x.py dist \ + ./build/$HOSTS/stage1-tools-bin/opt-dist linux-ci -- python3 ../x.py dist \ --host $HOSTS --target $HOSTS --include-default-paths build-manifest bootstrap ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=clang diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh index 924bdbc76150..4c95d4a401e5 100755 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh @@ -4,7 +4,7 @@ set -eux python3 ../x.py build --set rust.debug=true opt-dist -./build/$HOSTS/stage0-tools-bin/opt-dist linux-ci -- python3 ../x.py dist \ +./build/$HOSTS/stage1-tools-bin/opt-dist linux-ci -- python3 ../x.py dist \ --host $HOSTS --target $HOSTS \ --include-default-paths \ build-manifest bootstrap diff --git a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile index f82e19bcbb47..8073b8efb46f 100644 --- a/src/ci/docker/host-x86_64/pr-check-2/Dockerfile +++ b/src/ci/docker/host-x86_64/pr-check-2/Dockerfile @@ -28,16 +28,16 @@ RUN sh /scripts/sccache.sh ENV SCRIPT \ python3 ../x.py check && \ - python3 ../x.py clippy ci && \ + python3 ../x.py clippy ci --stage 2 && \ python3 ../x.py test --stage 1 core alloc std test proc_macro && \ python3 ../x.py test --stage 1 src/tools/compiletest && \ - python3 ../x.py doc --stage 0 bootstrap && \ + python3 ../x.py doc bootstrap && \ # Build both public and internal documentation. - RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 compiler && \ - RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library && \ + RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc compiler --stage 1 && \ + RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc library --stage 1 && \ mkdir -p /checkout/obj/staging/doc && \ cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \ - RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 1 library/test && \ + RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc library/test --stage 1 && \ # The BOOTSTRAP_TRACING flag is added to verify whether the # bootstrap process compiles successfully with this flag enabled. BOOTSTRAP_TRACING=1 python3 ../x.py --help diff --git a/src/ci/docker/host-x86_64/tidy/Dockerfile b/src/ci/docker/host-x86_64/tidy/Dockerfile index ee1ae5410ee8..c8558689d3ba 100644 --- a/src/ci/docker/host-x86_64/tidy/Dockerfile +++ b/src/ci/docker/host-x86_64/tidy/Dockerfile @@ -45,4 +45,4 @@ RUN bash -c 'npm install -g eslint@$(cat /tmp/eslint.version)' # NOTE: intentionally uses python2 for x.py so we can test it still works. # validate-toolstate only runs in our CI, so it's ok for it to only support python3. ENV SCRIPT TIDY_PRINT_DIFF=1 python2.7 ../x.py test --stage 0 \ - src/tools/tidy tidyselftest --extra-checks=py,cpp,js + src/tools/tidy tidyselftest --extra-checks=py,cpp,js,spellcheck diff --git a/src/ci/docker/host-x86_64/tidy/eslint.version b/src/ci/docker/host-x86_64/tidy/eslint.version index 1acea15afd69..42890ac0095a 100644 --- a/src/ci/docker/host-x86_64/tidy/eslint.version +++ b/src/ci/docker/host-x86_64/tidy/eslint.version @@ -1 +1 @@ -8.6.0 \ No newline at end of file +8.57.1 diff --git a/src/ci/docker/scripts/build-clang.sh b/src/ci/docker/scripts/build-clang.sh index 536991cc06b7..905c40773042 100755 --- a/src/ci/docker/scripts/build-clang.sh +++ b/src/ci/docker/scripts/build-clang.sh @@ -5,7 +5,7 @@ set -ex source shared.sh # Try to keep the LLVM version here in sync with src/ci/scripts/install-clang.sh -LLVM=llvmorg-20.1.0-rc2 +LLVM=llvmorg-21.1.0-rc2 mkdir llvm-project cd llvm-project @@ -44,8 +44,10 @@ hide_output \ -DLLVM_INCLUDE_BENCHMARKS=OFF \ -DLLVM_INCLUDE_TESTS=OFF \ -DLLVM_INCLUDE_EXAMPLES=OFF \ - -DLLVM_ENABLE_PROJECTS="clang;lld;compiler-rt;bolt" \ + -DLLVM_ENABLE_PROJECTS="clang;lld;bolt" \ + -DLLVM_ENABLE_RUNTIMES="compiler-rt" \ -DLLVM_BINUTILS_INCDIR="/rustroot/lib/gcc/$GCC_PLUGIN_TARGET/$GCC_VERSION/plugin/include/" \ + -DRUNTIMES_CMAKE_ARGS="-DCMAKE_CXX_FLAGS=\"--gcc-toolchain=/rustroot\"" \ -DC_INCLUDE_DIRS="$INC" hide_output make -j$(nproc) diff --git a/src/ci/docker/scripts/freebsd-toolchain.sh b/src/ci/docker/scripts/freebsd-toolchain.sh index 0d02636db919..56b3c5cd82e7 100755 --- a/src/ci/docker/scripts/freebsd-toolchain.sh +++ b/src/ci/docker/scripts/freebsd-toolchain.sh @@ -28,7 +28,9 @@ exit 1 # First up, build binutils mkdir binutils cd binutils -curl https://ftp.gnu.org/gnu/binutils/binutils-${binutils_version}.tar.bz2 | tar xjf - +# Originally downloaded from: +# https://sourceware.org/pub/binutils/releases/binutils-${binutils_version}.tar.bz2 +curl https://ci-mirrors.rust-lang.org/rustc/binutils-${binutils_version}.tar.bz2 | tar xjf - mkdir binutils-build cd binutils-build hide_output ../binutils-${binutils_version}/configure \ diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 48c570bfa11a..409d2cba8210 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -23,16 +23,11 @@ runners: <<: *base-job - &job-macos - os: macos-13 - <<: *base-job - - - &job-macos-m1 os: macos-14 <<: *base-job - &job-windows os: windows-2025 - free_disk: true <<: *base-job - &job-windows-8c @@ -68,17 +63,6 @@ runners: <<: *base-job envs: - env-x86_64-apple-tests: &env-x86_64-apple-tests - SCRIPT: ./x.py check compiletest --set build.compiletest-use-stage0-libtest=true && ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc -- --exact - RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc - # Ensure that host tooling is tested on our minimum supported macOS version. - MACOSX_DEPLOYMENT_TARGET: 10.12 - MACOSX_STD_DEPLOYMENT_TARGET: 10.12 - SELECT_XCODE: /Applications/Xcode_15.2.app - NO_LLVM_ASSERTIONS: 1 - NO_DEBUG_ASSERTIONS: 1 - NO_OVERFLOW_CHECKS: 1 - production: &production DEPLOY_BUCKET: rust-lang-ci2 @@ -455,8 +439,19 @@ auto: - name: dist-x86_64-apple env: - SCRIPT: ./x.py dist bootstrap --include-default-paths --host=x86_64-apple-darwin --target=x86_64-apple-darwin - RUST_CONFIGURE_ARGS: --enable-full-tools --enable-sanitizers --enable-profiler --set rust.jemalloc --set rust.lto=thin --set rust.codegen-units=1 + SCRIPT: >- + ./x.py dist bootstrap + --include-default-paths + --host=x86_64-apple-darwin + --target=x86_64-apple-darwin + RUST_CONFIGURE_ARGS: >- + --enable-full-tools + --enable-sanitizers + --enable-profiler + --disable-docs + --set rust.jemalloc + --set rust.lto=thin + --set rust.codegen-units=1 # Ensure that host tooling is built to support our minimum support macOS version. MACOSX_DEPLOYMENT_TARGET: 10.12 MACOSX_STD_DEPLOYMENT_TARGET: 10.12 @@ -482,17 +477,6 @@ auto: NO_LLVM_ASSERTIONS: 1 NO_DEBUG_ASSERTIONS: 1 NO_OVERFLOW_CHECKS: 1 - <<: *job-macos-m1 - - - name: x86_64-apple-1 - env: - <<: *env-x86_64-apple-tests - <<: *job-macos - - - name: x86_64-apple-2 - env: - SCRIPT: ./x.py --stage 2 test tests/ui tests/rustdoc - <<: *env-x86_64-apple-tests <<: *job-macos - name: dist-aarch64-apple @@ -517,7 +501,7 @@ auto: NO_OVERFLOW_CHECKS: 1 DIST_REQUIRE_ALL_TOOLS: 1 CODEGEN_BACKENDS: llvm,cranelift - <<: *job-macos-m1 + <<: *job-macos - name: aarch64-apple env: @@ -537,7 +521,7 @@ auto: NO_LLVM_ASSERTIONS: 1 NO_DEBUG_ASSERTIONS: 1 NO_OVERFLOW_CHECKS: 1 - <<: *job-macos-m1 + <<: *job-macos ###################### # Windows Builders # @@ -656,7 +640,7 @@ auto: --enable-full-tools --enable-profiler --set rust.codegen-units=1 - SCRIPT: python x.py build --set rust.debug=true opt-dist && PGO_HOST=x86_64-pc-windows-msvc ./build/x86_64-pc-windows-msvc/stage0-tools-bin/opt-dist windows-ci -- python x.py dist bootstrap --include-default-paths + SCRIPT: python x.py build --set rust.debug=true opt-dist && PGO_HOST=x86_64-pc-windows-msvc ./build/x86_64-pc-windows-msvc/stage1-tools-bin/opt-dist windows-ci -- python x.py dist bootstrap --include-default-paths DIST_REQUIRE_ALL_TOOLS: 1 CODEGEN_BACKENDS: llvm,cranelift <<: *job-windows-8c diff --git a/src/ci/run.sh b/src/ci/run.sh index f58a067041dd..c9d81f1ff510 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -205,6 +205,9 @@ if [ "$ENABLE_GCC_CODEGEN" = "1" ]; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-new-symbol-mangling" fi +# If bootstrap fails, we want to see its backtrace +export RUST_BACKTRACE=1 + # Print the date from the local machine and the date from an external source to # check for clock drifts. An HTTP URL is used instead of HTTPS since on Azure # Pipelines it happened that the certificates were marked as expired. diff --git a/src/ci/scripts/free-disk-space-windows.ps1 b/src/ci/scripts/free-disk-space-windows.ps1 deleted file mode 100644 index 8a4677bd2ab4..000000000000 --- a/src/ci/scripts/free-disk-space-windows.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -# Free disk space on Windows GitHub action runners. - -$ErrorActionPreference = 'Stop' - -Get-Volume | Out-String | Write-Output - -$available = $(Get-Volume C).SizeRemaining - -$dirs = 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm', -'C:\rtools45', 'C:\ghcup', 'C:\Program Files (x86)\Android', -'C:\Program Files\Google\Chrome', 'C:\Program Files (x86)\Microsoft\Edge', -'C:\Program Files\Mozilla Firefox', 'C:\Program Files\MySQL', 'C:\Julia', -'C:\Program Files\MongoDB', 'C:\Program Files\Azure Cosmos DB Emulator', -'C:\Program Files\PostgreSQL', 'C:\Program Files\Unity Hub', -'C:\Strawberry', 'C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk' - -foreach ($dir in $dirs) { - Start-ThreadJob -InputObject $dir { - Remove-Item -Recurse -Force -LiteralPath $input - } | Out-Null -} - -foreach ($job in Get-Job) { - Wait-Job $job | Out-Null - if ($job.Error) { - Write-Output "::warning file=$PSCommandPath::$($job.Error)" - } - Remove-Job $job -} - -Get-Volume | Out-String | Write-Output - -$saved = ($(Get-Volume C).SizeRemaining - $available) / 1gb -$savedRounded = [math]::Round($saved, 3) -Write-Output "total space saved: $savedRounded GB" diff --git a/src/ci/scripts/free-disk-space.sh b/src/ci/scripts/free-disk-space.sh deleted file mode 100755 index 062ad801cd8d..000000000000 --- a/src/ci/scripts/free-disk-space.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -euo pipefail - -script_dir=$(dirname "$0") - -if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - pwsh $script_dir/free-disk-space-windows.ps1 -else - $script_dir/free-disk-space-linux.sh -fi diff --git a/src/doc/book b/src/doc/book index b2d1a0821e12..3e9dc46aa563 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit b2d1a0821e12a676b496d61891b8e3d374a8e832 +Subproject commit 3e9dc46aa563ca0c53ec826c41b05f10c5915925 diff --git a/src/doc/index.md b/src/doc/index.md index 8ad5b427b552..892057a8f4db 100644 --- a/src/doc/index.md +++ b/src/doc/index.md @@ -172,9 +172,9 @@ unsafe Rust. It's also sometimes called "the 'nomicon." [The Unstable Book](unstable-book/index.html) has documentation for unstable features. -### The `rustc` Contribution Guide +### The `rustc` Development Guide -[The `rustc` Guide](https://rustc-dev-guide.rust-lang.org/) +[The `rustc-dev-guide`](https://rustc-dev-guide.rust-lang.org/) documents how the compiler works and how to contribute to it. This is useful if you want to build or modify the Rust compiler from source (e.g. to target something non-standard). diff --git a/src/doc/reference b/src/doc/reference index 1f45bd41fa6c..59b8af811886 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 1f45bd41fa6c17b7c048ed6bfe5f168c4311206a +Subproject commit 59b8af811886313577615c2cf0e045f01faed88b diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index e386be5f44af..adc1f3b9012a 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit e386be5f44af711854207c11fdd61bb576270b04 +Subproject commit adc1f3b9012ad3255eea2054ca30596a953d053d diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index daf5223cbd4a..6eabb999fb01 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -17,7 +17,6 @@ jobs: MDBOOK_VERSION: 0.4.48 MDBOOK_LINKCHECK2_VERSION: 0.9.1 MDBOOK_MERMAID_VERSION: 0.12.6 - MDBOOK_TOC_VERSION: 0.11.2 MDBOOK_OUTPUT__LINKCHECK__FOLLOW_WEB_LINKS: ${{ github.event_name != 'pull_request' }} DEPLOY_DIR: book/html BASE_SHA: ${{ github.event.pull_request.base.sha }} @@ -34,7 +33,7 @@ jobs: with: path: | ~/.cargo/bin - key: ${{ runner.os }}-${{ env.MDBOOK_VERSION }}--${{ env.MDBOOK_LINKCHECK2_VERSION }}--${{ env.MDBOOK_TOC_VERSION }}--${{ env.MDBOOK_MERMAID_VERSION }} + key: ${{ runner.os }}-${{ env.MDBOOK_VERSION }}--${{ env.MDBOOK_LINKCHECK2_VERSION }}--${{ env.MDBOOK_MERMAID_VERSION }} - name: Restore cached Linkcheck if: github.event_name == 'schedule' @@ -57,7 +56,6 @@ jobs: run: | cargo install mdbook --version ${{ env.MDBOOK_VERSION }} cargo install mdbook-linkcheck2 --version ${{ env.MDBOOK_LINKCHECK2_VERSION }} - cargo install mdbook-toc --version ${{ env.MDBOOK_TOC_VERSION }} cargo install mdbook-mermaid --version ${{ env.MDBOOK_MERMAID_VERSION }} - name: Check build diff --git a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml index ad570ee4595d..04d6469aeaa4 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml @@ -11,10 +11,11 @@ jobs: if: github.repository == 'rust-lang/rustc-dev-guide' uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main with: + github-app-id: ${{ vars.APP_CLIENT_ID }} zulip-stream-id: 196385 zulip-bot-email: "rustc-dev-guide-gha-notif-bot@rust-lang.zulipchat.com" pr-base-branch: master branch-name: rustc-pull secrets: zulip-api-token: ${{ secrets.ZULIP_API_TOKEN }} - token: ${{ secrets.GITHUB_TOKEN }} + github-app-secret: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/src/doc/rustc-dev-guide/README.md b/src/doc/rustc-dev-guide/README.md index 5932da467ab2..1ad895aeda2e 100644 --- a/src/doc/rustc-dev-guide/README.md +++ b/src/doc/rustc-dev-guide/README.md @@ -43,7 +43,7 @@ rustdocs][rustdocs]. To build a local static HTML site, install [`mdbook`](https://github.com/rust-lang/mdBook) with: ``` -cargo install mdbook mdbook-linkcheck2 mdbook-toc mdbook-mermaid +cargo install mdbook mdbook-linkcheck2 mdbook-mermaid ``` and execute the following command in the root of the repository: @@ -67,8 +67,8 @@ ENABLE_LINKCHECK=1 mdbook serve ### Table of Contents -We use `mdbook-toc` to auto-generate TOCs for long sections. You can invoke the preprocessor by -including the `` marker at the place where you want the TOC. +Each page has a TOC that is automatically generated by `pagetoc.js`. +There is an associated `pagetoc.css`, for styling. ## Synchronizing josh subtree with rustc diff --git a/src/doc/rustc-dev-guide/book.toml b/src/doc/rustc-dev-guide/book.toml index b84b1e7548a8..daf237ed9081 100644 --- a/src/doc/rustc-dev-guide/book.toml +++ b/src/doc/rustc-dev-guide/book.toml @@ -6,17 +6,18 @@ description = "A guide to developing the Rust compiler (rustc)" [build] create-missing = false -[preprocessor.toc] -command = "mdbook-toc" -renderer = ["html"] - [preprocessor.mermaid] command = "mdbook-mermaid" [output.html] git-repository-url = "https://github.com/rust-lang/rustc-dev-guide" edit-url-template = "https://github.com/rust-lang/rustc-dev-guide/edit/master/{path}" -additional-js = ["mermaid.min.js", "mermaid-init.js"] +additional-js = [ + "mermaid.min.js", + "mermaid-init.js", + "pagetoc.js", +] +additional-css = ["pagetoc.css"] [output.html.search] use-boolean-and = true diff --git a/src/doc/rustc-dev-guide/pagetoc.css b/src/doc/rustc-dev-guide/pagetoc.css new file mode 100644 index 000000000000..fa709194f375 --- /dev/null +++ b/src/doc/rustc-dev-guide/pagetoc.css @@ -0,0 +1,84 @@ +/* Inspired by https://github.com/JorelAli/mdBook-pagetoc/tree/98ee241 (under WTFPL) */ + +:root { + --toc-width: 270px; + --center-content-toc-shift: calc(-1 * var(--toc-width) / 2); +} + +.nav-chapters { + /* adjust width of buttons that bring to the previous or the next page */ + min-width: 50px; +} + +@media only screen { + @media (max-width: 1179px) { + .sidebar-hidden #sidetoc { + display: none; + } + } + + @media (max-width: 1439px) { + .sidebar-visible #sidetoc { + display: none; + } + } + + @media (1180px <= width <= 1439px) { + .sidebar-hidden main { + position: relative; + left: var(--center-content-toc-shift); + } + } + + @media (1440px <= width <= 1700px) { + .sidebar-visible main { + position: relative; + left: var(--center-content-toc-shift); + } + } + + #sidetoc { + margin-left: calc(100% + 20px); + } + #pagetoc { + position: fixed; + /* adjust TOC width */ + width: var(--toc-width); + height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); + overflow: auto; + } + #pagetoc a { + border-left: 1px solid var(--sidebar-bg); + color: var(--fg); + display: block; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 10px; + text-align: left; + text-decoration: none; + } + #pagetoc a:hover, + #pagetoc a.active { + background: var(--sidebar-bg); + color: var(--sidebar-active) !important; + } + #pagetoc .active { + background: var(--sidebar-bg); + color: var(--sidebar-active); + } + #pagetoc .pagetoc-H2 { + padding-left: 20px; + } + #pagetoc .pagetoc-H3 { + padding-left: 40px; + } + #pagetoc .pagetoc-H4 { + padding-left: 60px; + } +} + +@media print { + #sidetoc { + display: none; + } +} diff --git a/src/doc/rustc-dev-guide/pagetoc.js b/src/doc/rustc-dev-guide/pagetoc.js new file mode 100644 index 000000000000..927a5b10749b --- /dev/null +++ b/src/doc/rustc-dev-guide/pagetoc.js @@ -0,0 +1,104 @@ +// Inspired by https://github.com/JorelAli/mdBook-pagetoc/tree/98ee241 (under WTFPL) + +let activeHref = location.href; +function updatePageToc(elem = undefined) { + let selectedPageTocElem = elem; + const pagetoc = document.getElementById("pagetoc"); + + function getRect(element) { + return element.getBoundingClientRect(); + } + + function overflowTop(container, element) { + return getRect(container).top - getRect(element).top; + } + + function overflowBottom(container, element) { + return getRect(container).bottom - getRect(element).bottom; + } + + // We've not selected a heading to highlight, and the URL needs updating + // so we need to find a heading based on the URL + if (selectedPageTocElem === undefined && location.href !== activeHref) { + activeHref = location.href; + for (const pageTocElement of pagetoc.children) { + if (pageTocElement.href === activeHref) { + selectedPageTocElem = pageTocElement; + } + } + } + + // We still don't have a selected heading, let's try and find the most + // suitable heading based on the scroll position + if (selectedPageTocElem === undefined) { + const margin = window.innerHeight / 3; + + const headers = document.getElementsByClassName("header"); + for (let i = 0; i < headers.length; i++) { + const header = headers[i]; + if (selectedPageTocElem === undefined && getRect(header).top >= 0) { + if (getRect(header).top < margin) { + selectedPageTocElem = header; + } else { + selectedPageTocElem = headers[Math.max(0, i - 1)]; + } + } + // a very long last section's heading is over the screen + if (selectedPageTocElem === undefined && i === headers.length - 1) { + selectedPageTocElem = header; + } + } + } + + // Remove the active flag from all pagetoc elements + for (const pageTocElement of pagetoc.children) { + pageTocElement.classList.remove("active"); + } + + // If we have a selected heading, set it to active and scroll to it + if (selectedPageTocElem !== undefined) { + for (const pageTocElement of pagetoc.children) { + if (selectedPageTocElem.href.localeCompare(pageTocElement.href) === 0) { + pageTocElement.classList.add("active"); + if (overflowTop(pagetoc, pageTocElement) > 0) { + pagetoc.scrollTop = pageTocElement.offsetTop; + } + if (overflowBottom(pagetoc, pageTocElement) < 0) { + pagetoc.scrollTop -= overflowBottom(pagetoc, pageTocElement); + } + } + } + } +} + +if (document.getElementById("sidetoc") === null && + document.getElementsByClassName("header").length > 0) { + // The sidetoc element doesn't exist yet, let's create it + + // Create the empty sidetoc and pagetoc elements + const sidetoc = document.createElement("div"); + const pagetoc = document.createElement("div"); + sidetoc.id = "sidetoc"; + pagetoc.id = "pagetoc"; + sidetoc.appendChild(pagetoc); + + // And append them to the current DOM + const main = document.querySelector('main'); + main.insertBefore(sidetoc, main.firstChild); + + // Populate sidebar on load + window.addEventListener("load", () => { + for (const header of document.getElementsByClassName("header")) { + const link = document.createElement("a"); + link.innerHTML = header.innerHTML; + link.href = header.hash; + link.classList.add("pagetoc-" + header.parentElement.tagName); + document.getElementById("pagetoc").appendChild(link); + link.onclick = () => updatePageToc(link); + } + updatePageToc(); + }); + + // Update page table of contents selected heading on scroll + window.addEventListener("scroll", () => updatePageToc()); +} diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index b631041b6bfa..6ec700b9b4dc 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -2b5e239c6b86cde974b0ef0f8e23754fb08ff3c5 +6bcdcc73bd11568fd85f5a38b58e1eda054ad1cd diff --git a/src/doc/rustc-dev-guide/src/SUMMARY.md b/src/doc/rustc-dev-guide/src/SUMMARY.md index e3c0d50fcc73..025a078ae5b0 100644 --- a/src/doc/rustc-dev-guide/src/SUMMARY.md +++ b/src/doc/rustc-dev-guide/src/SUMMARY.md @@ -107,7 +107,6 @@ - [Installation](./autodiff/installation.md) - [How to debug](./autodiff/debugging.md) - [Autodiff flags](./autodiff/flags.md) - - [Current limitations](./autodiff/limitations.md) # Source Code Representation @@ -176,6 +175,7 @@ - [Next-gen trait solving](./solve/trait-solving.md) - [Invariants of the type system](./solve/invariants.md) - [The solver](./solve/the-solver.md) + - [Candidate preference](./solve/candidate-preference.md) - [Canonicalization](./solve/canonicalization.md) - [Coinduction](./solve/coinduction.md) - [Caching](./solve/caching.md) diff --git a/src/doc/rustc-dev-guide/src/appendix/humorust.md b/src/doc/rustc-dev-guide/src/appendix/humorust.md index 6df3b212aa77..8681512ed56a 100644 --- a/src/doc/rustc-dev-guide/src/appendix/humorust.md +++ b/src/doc/rustc-dev-guide/src/appendix/humorust.md @@ -3,7 +3,7 @@ What's a project without a sense of humor? And frankly some of these are enlightening? -- [Weird exprs test](https://github.com/rust-lang/rust/blob/master/tests/ui/weird-exprs.rs) +- [Weird exprs test](https://github.com/rust-lang/rust/blob/master/tests/ui/expr/weird-exprs.rs) - [Ferris Rap](https://fitzgen.com/2018/12/13/rust-raps.html) - [The Genesis of Generic Germination](https://github.com/rust-lang/rust/pull/53645#issue-210543221) - [The Bastion of the Turbofish test](https://github.com/rust-lang/rust/blob/79d8a0fcefa5134db2a94739b1d18daa01fc6e9f/src/test/ui/bastion-of-the-turbofish.rs) diff --git a/src/doc/rustc-dev-guide/src/asm.md b/src/doc/rustc-dev-guide/src/asm.md index 1bb493e73d58..b5857d5465e1 100644 --- a/src/doc/rustc-dev-guide/src/asm.md +++ b/src/doc/rustc-dev-guide/src/asm.md @@ -1,7 +1,5 @@ # Inline assembly - - ## Overview Inline assembly in rustc mostly revolves around taking an `asm!` macro invocation and plumbing it diff --git a/src/doc/rustc-dev-guide/src/autodiff/limitations.md b/src/doc/rustc-dev-guide/src/autodiff/limitations.md deleted file mode 100644 index 90afbd51f3fd..000000000000 --- a/src/doc/rustc-dev-guide/src/autodiff/limitations.md +++ /dev/null @@ -1,27 +0,0 @@ -# Current limitations - -## Safety and Soundness - -Enzyme currently assumes that the user passes shadow arguments (`dx`, `dy`, ...) of appropriate size. Under Reverse Mode, we additionally assume that shadow arguments are mutable. In Reverse Mode we adjust the outermost pointer or reference to be mutable. Therefore `&f32` will receive the shadow type `&mut f32`. However, we do not check length for other types than slices (e.g. enums, Vec). We also do not enforce mutability of inner references, but will warn if we recognize them. We do intend to add additional checks over time. - -## ABI adjustments - -In some cases, a function parameter might get lowered in a way that we currently don't handle correctly, leading to a compile time type mismatch in the `rustc_codegen_llvm` backend. Here are some [examples](https://github.com/EnzymeAD/rust/issues/105). - -## Compile Times - -Enzyme will often achieve excellent runtime performance, but might increase your compile time by a large factor. For Rust, we already have made significant improvements and have a list of further improvements planed - please reach out if you have time to help here. - -### Type Analysis - -Most of the times, Type Analysis (TA) is the reason of large (>5x) compile time increases when using Enzyme. This poster explains why we need to run Type Analysis in the bottom left part: [Poster Link](https://c.wsmoses.com/posters/Enzyme-llvmdev.pdf). - -We intend to increase the number of locations where we pass down Type information based on Rust types, which in turn will reduce the number of locations where Enzyme has to run Type Analysis, which will help compile times. - -### Duplicated Optimizations - -The key reason for Enzyme offering often excellent performance is that Enzyme differentiates already optimized LLVM-IR. However, we also (have to) run LLVM's optimization pipeline after differentiating, to make sure that the code which Enzyme generates is optimized properly. As a result you should have excellent runtime performance (please fill an issue if not), but at a compile time cost for running optimizations twice. - -### Fat-LTO - -The usage of `#[autodiff(...)]` currently requires compiling your project with Fat-LTO. We technically only need LTO if the function being differentiated calls functions in other compilation units. Therefore, other solutions are possible, but this is the most simple one to get started. diff --git a/src/doc/rustc-dev-guide/src/backend/backend-agnostic.md b/src/doc/rustc-dev-guide/src/backend/backend-agnostic.md index 0f81d3cb48a1..2fdda4eda99a 100644 --- a/src/doc/rustc-dev-guide/src/backend/backend-agnostic.md +++ b/src/doc/rustc-dev-guide/src/backend/backend-agnostic.md @@ -1,7 +1,5 @@ # Backend Agnostic Codegen - - [`rustc_codegen_ssa`] provides an abstract interface for all backends to implement, namely LLVM, [Cranelift], and [GCC]. diff --git a/src/doc/rustc-dev-guide/src/backend/implicit-caller-location.md b/src/doc/rustc-dev-guide/src/backend/implicit-caller-location.md index c5ee00813a34..9ca4bcab078e 100644 --- a/src/doc/rustc-dev-guide/src/backend/implicit-caller-location.md +++ b/src/doc/rustc-dev-guide/src/backend/implicit-caller-location.md @@ -1,7 +1,5 @@ # Implicit caller location - - Approved in [RFC 2091], this feature enables the accurate reporting of caller location during panics initiated from functions like `Option::unwrap`, `Result::expect`, and `Index::index`. This feature adds the [`#[track_caller]`][attr-reference] attribute for functions, the diff --git a/src/doc/rustc-dev-guide/src/backend/monomorph.md b/src/doc/rustc-dev-guide/src/backend/monomorph.md index 7ebb4d2b1e81..e9d98597ee0d 100644 --- a/src/doc/rustc-dev-guide/src/backend/monomorph.md +++ b/src/doc/rustc-dev-guide/src/backend/monomorph.md @@ -1,7 +1,5 @@ # Monomorphization - - As you probably know, Rust has a very expressive type system that has extensive support for generic types. But of course, assembly is not generic, so we need to figure out the concrete types of all the generics before the code can diff --git a/src/doc/rustc-dev-guide/src/backend/updating-llvm.md b/src/doc/rustc-dev-guide/src/backend/updating-llvm.md index 18c822aad790..ebef15d40baf 100644 --- a/src/doc/rustc-dev-guide/src/backend/updating-llvm.md +++ b/src/doc/rustc-dev-guide/src/backend/updating-llvm.md @@ -1,7 +1,5 @@ # Updating LLVM - - Rust supports building against multiple LLVM versions: diff --git a/src/doc/rustc-dev-guide/src/borrow_check/moves_and_initialization/move_paths.md b/src/doc/rustc-dev-guide/src/borrow_check/moves_and_initialization/move_paths.md index ad9c75d62960..95518fbc0184 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/moves_and_initialization/move_paths.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/moves_and_initialization/move_paths.md @@ -1,7 +1,5 @@ # Move paths - - In reality, it's not enough to track initialization at the granularity of local variables. Rust also allows us to do moves and initialization at the field granularity: diff --git a/src/doc/rustc-dev-guide/src/borrow_check/region_inference.md b/src/doc/rustc-dev-guide/src/borrow_check/region_inference.md index 85e71b4fa429..0d55ab955836 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/region_inference.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/region_inference.md @@ -1,7 +1,5 @@ # Region inference (NLL) - - The MIR-based region checking code is located in [the `rustc_mir::borrow_check` module][nll]. diff --git a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/constraint_propagation.md b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/constraint_propagation.md index 4c30d25e0406..c3f8c03cb29f 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/constraint_propagation.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/constraint_propagation.md @@ -1,7 +1,5 @@ # Constraint propagation - - The main work of the region inference is **constraint propagation**, which is done in the [`propagate_constraints`] function. There are three sorts of constraints that are used in NLL, and we'll explain how diff --git a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/lifetime_parameters.md b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/lifetime_parameters.md index fadfac404569..2d337dbc020f 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/lifetime_parameters.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/lifetime_parameters.md @@ -1,7 +1,5 @@ # Universal regions - - "Universal regions" is the name that the code uses to refer to "named lifetimes" -- e.g., lifetime parameters and `'static`. The name derives from the fact that such lifetimes are "universally quantified" diff --git a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/member_constraints.md b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/member_constraints.md index fd7c87ffcea7..2804c97724f5 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/member_constraints.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/member_constraints.md @@ -1,7 +1,5 @@ # Member constraints - - A member constraint `'m member of ['c_1..'c_N]` expresses that the region `'m` must be *equal* to some **choice regions** `'c_i` (for some `i`). These constraints cannot be expressed by users, but they diff --git a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/placeholders_and_universes.md b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/placeholders_and_universes.md index 91c8c4526119..11fd2a5fc7db 100644 --- a/src/doc/rustc-dev-guide/src/borrow_check/region_inference/placeholders_and_universes.md +++ b/src/doc/rustc-dev-guide/src/borrow_check/region_inference/placeholders_and_universes.md @@ -1,7 +1,5 @@ # Placeholders and universes - - From time to time we have to reason about regions that we can't concretely know. For example, consider this program: diff --git a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md index 55436261fdef..6b13c97023f5 100644 --- a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md +++ b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md @@ -1,7 +1,5 @@ # Procedures for breaking changes - - This page defines the best practices procedure for making bug fixes or soundness corrections in the compiler that can cause existing code to stop compiling. This text is based on diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md index c9c0d64a604e..93b11c0690a9 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md @@ -1,120 +1,102 @@ # Debugging bootstrap -There are two main ways to debug bootstrap itself. The first is through println logging, and the second is through the `tracing` feature. - -> FIXME: this section should be expanded +There are two main ways of debugging (and profiling bootstrap). The first is through println logging, and the second is through the `tracing` feature. ## `println` logging Bootstrap has extensive unstructured logging. Most of it is gated behind the `--verbose` flag (pass `-vv` for even more detail). -If you want to know which `Step` ran a command, you could invoke bootstrap like so: +If you want to see verbose output of executed Cargo commands and other kinds of detailed logs, pass `-v` or `-vv` when invoking bootstrap. Note that the logs are unstructured and may be overwhelming. ``` $ ./x dist rustc --dry-run -vv learning about cargo running: RUSTC_BOOTSTRAP="1" "/home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "metadata" "--format-version" "1" "--no-deps" "--manifest-path" "/home/jyn/src/rust2/Cargo.toml" (failure_mode=Exit) (created at src/bootstrap/src/core/metadata.rs:81:25, executed at src/bootstrap/src/core/metadata.rs:92:50) running: RUSTC_BOOTSTRAP="1" "/home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "metadata" "--format-version" "1" "--no-deps" "--manifest-path" "/home/jyn/src/rust2/library/Cargo.toml" (failure_mode=Exit) (created at src/bootstrap/src/core/metadata.rs:81:25, executed at src/bootstrap/src/core/metadata.rs:92:50) -> Assemble { target_compiler: Compiler { stage: 1, host: x86_64-unknown-linux-gnu } } - > Libdir { compiler: Compiler { stage: 1, host: x86_64-unknown-linux-gnu }, target: x86_64-unknown-linux-gnu } - > Sysroot { compiler: Compiler { stage: 1, host: x86_64-unknown-linux-gnu }, force_recompile: false } -Removing sysroot /home/jyn/src/rust2/build/tmp-dry-run/x86_64-unknown-linux-gnu/stage1 to avoid caching bugs - < Sysroot { compiler: Compiler { stage: 1, host: x86_64-unknown-linux-gnu }, force_recompile: false } - < Libdir { compiler: Compiler { stage: 1, host: x86_64-unknown-linux-gnu }, target: x86_64-unknown-linux-gnu } -... -``` - -This will go through all the recursive dependency calculations, where `Step`s internally call `builder.ensure()`, without actually running cargo or the compiler. - -In some cases, even this may not be enough logging (if so, please add more!). In that case, you can omit `--dry-run`, which will show the normal output inline with the debug logging: - -``` - c Sysroot { compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu }, force_recompile: false } -using sysroot /home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0-sysroot -Building stage0 library artifacts (x86_64-unknown-linux-gnu) -running: cd "/home/jyn/src/rust2" && env ... RUSTC_VERBOSE="2" RUSTC_WRAPPER="/home/jyn/src/rust2/build/bootstrap/debug/rustc" "/home/jyn/src/rust2/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "build" "--target" "x86_64-unknown-linux-gnu" "-Zbinary-dep-depinfo" "-Zroot-dir=/home/jyn/src/rust2" "-v" "-v" "--manifest-path" "/home/jyn/src/rust2/library/sysroot/Cargo.toml" "--message-format" "json-render-diagnostics" - 0.293440230s INFO prepare_target{force=false package_id=sysroot v0.0.0 (/home/jyn/src/rust2/library/sysroot) target="sysroot"}: cargo::core::compiler::fingerprint: fingerprint error for sysroot v0.0.0 (/home/jyn/src/rust2/library/sysroot)/Build/TargetInner { name_inferred: true, ..: lib_target("sysroot", ["lib"], "/home/jyn/src/rust2/library/sysroot/src/lib.rs", Edition2021) } ... ``` -In most cases this should not be necessary. +## `tracing` in bootstrap -TODO: we should convert all this to structured logging so it's easier to control precisely. +Bootstrap has a conditional `tracing` feature, which provides the following features: +- It enables structured logging using [`tracing`][tracing] events and spans. +- It generates a [Chrome trace file] that can be used to visualize the hierarchy and durations of executed steps and commands. + - You can open the generated `chrome-trace.json` file using Chrome, on the `chrome://tracing` tab, or e.g. using [Perfetto]. +- It generates [GraphViz] graphs that visualize the dependencies between executed steps. + - You can open the generated `step-graph-*.dot` file using e.g. [xdot] to visualize the step graph, or use e.g. `dot -Tsvg` to convert the GraphViz file to an SVG file. +- It generates a command execution summary, which shows which commands were executed, how many of their executions were cached, and what commands were the slowest to run. + - The generated `command-stats.txt` file is in a simple human-readable format. -## `tracing` in bootstrap +The structured logs will be written to standard error output (`stderr`), while the other outputs will be stored in files in the `/bootstrap-trace/` directory. For convenience, bootstrap will also create a symlink to the latest generated trace output directory at `/bootstrap-trace/latest`. -Bootstrap has conditional [`tracing`][tracing] setup to provide structured logging. +> Note that if you execute bootstrap with `--dry-run`, the tracing output directory might change. Bootstrap will always print a path where the tracing output files were stored at the end of its execution. [tracing]: https://docs.rs/tracing/0.1.41/tracing/index.html +[Chrome trace file]: https://www.chromium.org/developers/how-tos/trace-event-profiling-tool/ +[Perfetto]: https://ui.perfetto.dev/ +[GraphViz]: https://graphviz.org/doc/info/lang.html +[xdot]: https://github.com/jrfonseca/xdot.py ### Enabling `tracing` output -Bootstrap will conditionally build `tracing` support and enable `tracing` output if the `BOOTSTRAP_TRACING` env var is set. - -#### Basic usage +To enable the conditional `tracing` feature, run bootstrap with the `BOOTSTRAP_TRACING` environment variable. -Example basic usage[^just-trace]: - -[^just-trace]: It is not recommended to use *just* `BOOTSTRAP_TRACING=TRACE` because that will dump *everything* at `TRACE` level, including logs intentionally gated behind custom targets as they are too verbose even for `TRACE` level by default. +[tracing_subscriber filter]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html ```bash -$ BOOTSTRAP_TRACING=bootstrap=TRACE ./x build library --stage 1 +$ BOOTSTRAP_TRACING=trace ./x build library --stage 1 ``` Example output[^unstable]: ``` -$ BOOTSTRAP_TRACING=bootstrap=TRACE ./x check src/bootstrap/ +$ BOOTSTRAP_TRACING=trace ./x build library --stage 1 --dry-run Building bootstrap - Compiling bootstrap v0.0.0 (/home/joe/repos/rust/src/bootstrap) - Finished `dev` profile [unoptimized] target(s) in 2.74s - DEBUG bootstrap parsing flags - bootstrap::core::config::flags::Flags::parse args=["check", "src/bootstrap/"] - DEBUG bootstrap parsing config based on flags - DEBUG bootstrap creating new build based on config - bootstrap::Build::build - TRACE bootstrap setting up job management - TRACE bootstrap downloading rustfmt early - bootstrap::handling hardcoded subcommands (Format, Suggest, Perf) - DEBUG bootstrap not a hardcoded subcommand; returning to normal handling, cmd=Check { all_targets: false } - DEBUG bootstrap handling subcommand normally - bootstrap::executing real run - bootstrap::(1) executing dry-run sanity-check - bootstrap::(2) executing actual run -Checking stage0 library artifacts (x86_64-unknown-linux-gnu) - Finished `release` profile [optimized + debuginfo] target(s) in 0.04s -Checking stage0 compiler artifacts {rustc-main, rustc_abi, rustc_arena, rustc_ast, rustc_ast_ir, rustc_ast_lowering, rustc_ast_passes, rustc_ast_pretty, rustc_attr_data_structures, rustc_attr_parsing, rustc_baked_icu_data, rustc_borrowck, rustc_builtin_macros, rustc_codegen_llvm, rustc_codegen_ssa, rustc_const_eval, rustc_data_structures, rustc_driver, rustc_driver_impl, rustc_error_codes, rustc_error_messages, rustc_errors, rustc_expand, rustc_feature, rustc_fluent_macro, rustc_fs_util, rustc_graphviz, rustc_hir, rustc_hir_analysis, rustc_hir_pretty, rustc_hir_typeck, rustc_incremental, rustc_index, rustc_index_macros, rustc_infer, rustc_interface, rustc_lexer, rustc_lint, rustc_lint_defs, rustc_llvm, rustc_log, rustc_macros, rustc_metadata, rustc_middle, rustc_mir_build, rustc_mir_dataflow, rustc_mir_transform, rustc_monomorphize, rustc_next_trait_solver, rustc_parse, rustc_parse_format, rustc_passes, rustc_pattern_analysis, rustc_privacy, rustc_query_impl, rustc_query_system, rustc_resolve, rustc_sanitizers, rustc_serialize, rustc_session, rustc_smir, rustc_span, rustc_symbol_mangling, rustc_target, rustc_trait_selection, rustc_traits, rustc_transmute, rustc_ty_utils, rustc_type_ir, rustc_type_ir_macros, stable_mir} (x86_64-unknown-linux-gnu) - Finished `release` profile [optimized + debuginfo] target(s) in 0.23s -Checking stage0 bootstrap artifacts (x86_64-unknown-linux-gnu) - Checking bootstrap v0.0.0 (/home/joe/repos/rust/src/bootstrap) - Finished `release` profile [optimized + debuginfo] target(s) in 0.64s - DEBUG bootstrap checking for postponed test failures from `test --no-fail-fast` -Build completed successfully in 0:00:08 + Finished `dev` profile [unoptimized] target(s) in 0.05s +15:56:52.477 INFO > tool::LibcxxVersionTool {target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715) +15:56:52.575 INFO > compile::Assemble {target_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }} (builder/mod.rs:1715) +15:56:52.575 INFO > tool::Compiletest {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715) +15:56:52.576 INFO > tool::ToolBuild {build_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu, tool: "compiletest", path: "src/tools/compiletest", mode: ToolBootstrap, source_type: InTree, extra_features: [], allow_features: "internal_output_capture", cargo_args: [], artifact_kind: Binary} (builder/mod.rs:1715) +15:56:52.576 INFO > builder::Libdir {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715) +15:56:52.576 INFO > compile::Sysroot {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, force_recompile: false} (builder/mod.rs:1715) +15:56:52.578 INFO > compile::Assemble {target_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }} (builder/mod.rs:1715) +15:56:52.578 INFO > tool::Compiletest {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715) +15:56:52.578 INFO > tool::ToolBuild {build_compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu, tool: "compiletest", path: "src/tools/compiletest", mode: ToolBootstrap, source_type: InTree, extra_features: [], allow_features: "internal_output_capture", cargo_args: [], artifact_kind: Binary} (builder/mod.rs:1715) +15:56:52.578 INFO > builder::Libdir {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, target: x86_64-unknown-linux-gnu} (builder/mod.rs:1715) +15:56:52.578 INFO > compile::Sysroot {compiler: Compiler { stage: 0, host: x86_64-unknown-linux-gnu, forced_compiler: false }, force_recompile: false} (builder/mod.rs:1715) + Finished `release` profile [optimized] target(s) in 0.11s +Tracing/profiling output has been written to /build/bootstrap-trace/latest +Build completed successfully in 0:00:00 ``` +[^unstable]: This output is always subject to further changes. + #### Controlling tracing output -The env var `BOOTSTRAP_TRACING` accepts a [`tracing` env-filter][tracing-env-filter]. +The environment variable `BOOTSTRAP_TRACING` accepts a [`tracing_subscriber` filter][tracing-env-filter]. If you set `BOOTSTRAP_TRACING=trace`, you will enable all logs, but that can be overwhelming. You can thus use the filter to reduce the amount of data logged. There are two orthogonal ways to control which kind of tracing logs you want: -1. You can specify the log **level**, e.g. `DEBUG` or `TRACE`. -2. You can also control the log **target**, e.g. `bootstrap` or `bootstrap::core::config` vs custom targets like `CONFIG_HANDLING`. - - Custom targets are used to limit what is output when `BOOTSTRAP_TRACING=bootstrap=TRACE` is used, as they can be too verbose even for `TRACE` level by default. Currently used custom targets: - - `CONFIG_HANDLING` - -The `TRACE` filter will enable *all* `trace` level or less verbose level tracing output. +1. You can specify the log **level**, e.g. `debug` or `trace`. + - If you select a level, all events/spans with an equal or higher priority level will be shown. +2. You can also control the log **target**, e.g. `bootstrap` or `bootstrap::core::config` or a custom target like `CONFIG_HANDLING` or `STEP`. + - Custom targets are used to limit what kinds of spans you are interested in, as the `BOOTSTRAP_TRACING=trace` output can be quite verbose. Currently, you can use the following custom targets: + - `CONFIG_HANDLING`: show spans related to config handling. + - `STEP`: show all executed steps. Executed commands have `info` event level. + - `COMMAND`: show all executed commands. Executed commands have `trace` event level. + - `IO`: show performed I/O operations. Executed commands have `trace` event level. + - Note that many I/O are currently not being traced. You can of course combine them (custom target logs are typically gated behind `TRACE` log level additionally): ```bash -$ BOOTSTRAP_TRACING=CONFIG_HANDLING=TRACE ./x build library --stage 1 +$ BOOTSTRAP_TRACING=CONFIG_HANDLING=trace,STEP=info,COMMAND=trace ./x build library --stage 1 ``` -[^unstable]: This output is always subject to further changes. - [tracing-env-filter]: https://docs.rs/tracing-subscriber/0.3.19/tracing_subscriber/filter/struct.EnvFilter.html +Note that the level that you specify using `BOOTSTRAP_TRACING` also has an effect on the spans that will be recorded in the Chrome trace file. + ##### FIXME(#96176): specific tracing for `compiler()` vs `compiler_for()` The additional targets `COMPILER` and `COMPILER_FOR` are used to help trace what @@ -143,15 +125,6 @@ impl Step for Foo { todo!() } - #[cfg_attr( - feature = "tracing", - instrument( - level = "trace", - name = "Foo::run", - skip_all, - fields(compiler = ?builder.compiler), - ), - )] fn run(self, builder: &Builder<'_>) -> Self::Output { trace!(?run, "entered Foo::run"); @@ -166,21 +139,6 @@ For `#[instrument]`, it's recommended to: - Explicitly pick an instrumentation name via `name = ".."` to distinguish between e.g. `run` of different steps. - Take care to not cause diverging behavior via tracing, e.g. building extra things only when tracing infra is enabled. -### Profiling bootstrap - -You can set the `BOOTSTRAP_PROFILE=1` environment variable to enable command execution profiling during bootstrap. This generates: - -* A Chrome trace file (for visualization in `chrome://tracing` or [Perfetto](https://ui.perfetto.dev)) if tracing is enabled via `BOOTSTRAP_TRACING=COMMAND=trace` -* A plain-text summary file, `bootstrap-profile-{pid}.txt`, listing all commands sorted by execution time (slowest first), along with cache hits and working directories - -Note: the `.txt` report is always generated when `BOOTSTRAP_PROFILE=1` is set — tracing is not required. - -Example usage: - -```bash -$ BOOTSTRAP_PROFILE=1 BOOTSTRAP_TRACING=COMMAND=trace ./x build library -``` - ### rust-analyzer integration? Unfortunately, because bootstrap is a `rust-analyzer.linkedProjects`, you can't ask r-a to check/build bootstrap itself with `tracing` feature enabled to get relevant completions, due to lack of support as described in . diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md index 2793ad438152..da425d8d39bb 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md @@ -1,7 +1,5 @@ # What Bootstrapping does - - [*Bootstrapping*][boot] is the process of using a compiler to compile itself. More accurately, it means using an older compiler to compile a newer version of the same compiler. diff --git a/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md b/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md index d29cd1448102..b07d3533f59b 100644 --- a/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md +++ b/src/doc/rustc-dev-guide/src/building/how-to-build-and-run.md @@ -1,7 +1,5 @@ # How to build and run the compiler - -

For `profile = "library"` users, or users who use `download-rustc = true | "if-unchanged"`, please be advised that diff --git a/src/doc/rustc-dev-guide/src/building/new-target.md b/src/doc/rustc-dev-guide/src/building/new-target.md index e11a2cd8ee57..436aec8ee265 100644 --- a/src/doc/rustc-dev-guide/src/building/new-target.md +++ b/src/doc/rustc-dev-guide/src/building/new-target.md @@ -6,8 +6,6 @@ relevant to your desired goal. See also the associated documentation in the [target tier policy]. - - [target tier policy]: https://doc.rust-lang.org/rustc/target-tier-policy.html#adding-a-new-target ## Specifying a new LLVM diff --git a/src/doc/rustc-dev-guide/src/building/optimized-build.md b/src/doc/rustc-dev-guide/src/building/optimized-build.md index 62dfaca89d24..46def66d1782 100644 --- a/src/doc/rustc-dev-guide/src/building/optimized-build.md +++ b/src/doc/rustc-dev-guide/src/building/optimized-build.md @@ -1,7 +1,5 @@ # Optimized build of the compiler - - There are multiple additional build configuration options and techniques that can be used to compile a build of `rustc` that is as optimized as possible (for example when building `rustc` for a Linux distribution). The status of these configuration options for various Rust targets is tracked [here]. @@ -120,7 +118,7 @@ Here is an example of how can `opt-dist` be used locally (outside of CI): ``` 3. Run the tool with the `local` mode and provide necessary parameters: ```bash - ./build/host/stage0-tools-bin/opt-dist local \ + ./build/host/stage1-tools-bin/opt-dist local \ --target-triple \ # select target, e.g. "x86_64-unknown-linux-gnu" --checkout-dir \ # path to rust checkout, e.g. "." --llvm-dir \ # path to built LLVM toolchain, e.g. "/foo/bar/llvm/install" diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index c046161e77f2..35c7e935b568 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -3,8 +3,6 @@ The full bootstrapping process takes quite a while. Here are some suggestions to make your life easier. - - ## Installing a pre-push hook CI will automatically fail your build if it doesn't pass `tidy`, our internal diff --git a/src/doc/rustc-dev-guide/src/compiler-debugging.md b/src/doc/rustc-dev-guide/src/compiler-debugging.md index 102e20207792..edd2aa6c5f64 100644 --- a/src/doc/rustc-dev-guide/src/compiler-debugging.md +++ b/src/doc/rustc-dev-guide/src/compiler-debugging.md @@ -1,7 +1,5 @@ # Debugging the compiler - - This chapter contains a few tips to debug the compiler. These tips aim to be useful no matter what you are working on. Some of the other chapters have advice about specific parts of the compiler (e.g. the [Queries Debugging and diff --git a/src/doc/rustc-dev-guide/src/compiler-src.md b/src/doc/rustc-dev-guide/src/compiler-src.md index 00aa96226849..d67bacb1b339 100644 --- a/src/doc/rustc-dev-guide/src/compiler-src.md +++ b/src/doc/rustc-dev-guide/src/compiler-src.md @@ -1,7 +1,5 @@ # High-level overview of the compiler source - - Now that we have [seen what the compiler does][orgch], let's take a look at the structure of the [`rust-lang/rust`] repository, where the rustc source code lives. diff --git a/src/doc/rustc-dev-guide/src/const-eval/interpret.md b/src/doc/rustc-dev-guide/src/const-eval/interpret.md index 51a539de5cb6..08382b12ff00 100644 --- a/src/doc/rustc-dev-guide/src/const-eval/interpret.md +++ b/src/doc/rustc-dev-guide/src/const-eval/interpret.md @@ -1,7 +1,5 @@ # Interpreter - - The interpreter is a virtual machine for executing MIR without compiling to machine code. It is usually invoked via `tcx.const_eval_*` functions. The interpreter is shared between the compiler (for compile-time function diff --git a/src/doc/rustc-dev-guide/src/contributing.md b/src/doc/rustc-dev-guide/src/contributing.md index b3fcd79ec818..963bef3af8de 100644 --- a/src/doc/rustc-dev-guide/src/contributing.md +++ b/src/doc/rustc-dev-guide/src/contributing.md @@ -1,7 +1,5 @@ # Contribution procedures - - ## Bug reports While bugs are unfortunate, they're a reality in software. We can't fix what we diff --git a/src/doc/rustc-dev-guide/src/coroutine-closures.md b/src/doc/rustc-dev-guide/src/coroutine-closures.md index 48cdba44a9f5..2617c824a391 100644 --- a/src/doc/rustc-dev-guide/src/coroutine-closures.md +++ b/src/doc/rustc-dev-guide/src/coroutine-closures.md @@ -1,7 +1,5 @@ # Async closures/"coroutine-closures" - - Please read [RFC 3668](https://rust-lang.github.io/rfcs/3668-async-closures.html) to understand the general motivation of the feature. This is a very technical and somewhat "vertical" chapter; ideally we'd split this and sprinkle it across all the relevant chapters, but for the purposes of understanding async closures *holistically*, I've put this together all here in one chapter. ## Coroutine-closures -- a technical deep dive diff --git a/src/doc/rustc-dev-guide/src/crates-io.md b/src/doc/rustc-dev-guide/src/crates-io.md index 4431585a2f02..677b1fc03134 100644 --- a/src/doc/rustc-dev-guide/src/crates-io.md +++ b/src/doc/rustc-dev-guide/src/crates-io.md @@ -11,7 +11,7 @@ you should avoid adding dependencies to the compiler for several reasons: - The dependency may have transitive dependencies that have one of the above problems. - + Note that there is no official policy for vetting new dependencies to the compiler. Decisions are made on a case-by-case basis, during code review. diff --git a/src/doc/rustc-dev-guide/src/debugging-support-in-rustc.md b/src/doc/rustc-dev-guide/src/debugging-support-in-rustc.md index ac629934e0a4..bd4f795ce03b 100644 --- a/src/doc/rustc-dev-guide/src/debugging-support-in-rustc.md +++ b/src/doc/rustc-dev-guide/src/debugging-support-in-rustc.md @@ -1,7 +1,5 @@ # Debugging support in the Rust compiler - - This document explains the state of debugging tools support in the Rust compiler (rustc). It gives an overview of GDB, LLDB, WinDbg/CDB, as well as infrastructure around Rust compiler to debug Rust code. diff --git a/src/doc/rustc-dev-guide/src/diagnostics.md b/src/doc/rustc-dev-guide/src/diagnostics.md index 33f5441d36e4..82191e0a6eaf 100644 --- a/src/doc/rustc-dev-guide/src/diagnostics.md +++ b/src/doc/rustc-dev-guide/src/diagnostics.md @@ -1,7 +1,5 @@ # Errors and lints - - A lot of effort has been put into making `rustc` have great error messages. This chapter is about how to emit compile errors and lints from the compiler. diff --git a/src/doc/rustc-dev-guide/src/early_late_parameters.md b/src/doc/rustc-dev-guide/src/early_late_parameters.md index 3f94b0905668..c472bdc2c481 100644 --- a/src/doc/rustc-dev-guide/src/early_late_parameters.md +++ b/src/doc/rustc-dev-guide/src/early_late_parameters.md @@ -1,8 +1,6 @@ # Early vs Late bound parameters - - > **NOTE**: This chapter largely talks about early/late bound as being solely relevant when discussing function item types/function definitions. This is potentially not completely true, async blocks and closures should likely be discussed somewhat in this chapter. ## What does it mean to be "early" bound or "late" bound diff --git a/src/doc/rustc-dev-guide/src/getting-started.md b/src/doc/rustc-dev-guide/src/getting-started.md index d6c5c3ac8521..04d2e37732fa 100644 --- a/src/doc/rustc-dev-guide/src/getting-started.md +++ b/src/doc/rustc-dev-guide/src/getting-started.md @@ -3,8 +3,6 @@ Thank you for your interest in contributing to Rust! There are many ways to contribute, and we appreciate all of them. - - If this is your first time contributing, the [walkthrough] chapter can give you a good example of how a typical contribution would go. diff --git a/src/doc/rustc-dev-guide/src/git.md b/src/doc/rustc-dev-guide/src/git.md index 8726ddfce20c..447c6fd45467 100644 --- a/src/doc/rustc-dev-guide/src/git.md +++ b/src/doc/rustc-dev-guide/src/git.md @@ -1,7 +1,5 @@ # Using Git - - The Rust project uses [Git] to manage its source code. In order to contribute, you'll need some familiarity with its features so that your changes can be incorporated into the compiler. diff --git a/src/doc/rustc-dev-guide/src/guides/editions.md b/src/doc/rustc-dev-guide/src/guides/editions.md index 9a92d4ebcb51..b65fbb13cd18 100644 --- a/src/doc/rustc-dev-guide/src/guides/editions.md +++ b/src/doc/rustc-dev-guide/src/guides/editions.md @@ -1,7 +1,5 @@ # Editions - - This chapter gives an overview of how Edition support works in rustc. This assumes that you are familiar with what Editions are (see the [Edition Guide]). diff --git a/src/doc/rustc-dev-guide/src/hir.md b/src/doc/rustc-dev-guide/src/hir.md index 72fb10701574..38ba33112f2e 100644 --- a/src/doc/rustc-dev-guide/src/hir.md +++ b/src/doc/rustc-dev-guide/src/hir.md @@ -1,7 +1,5 @@ # The HIR - - The HIR – "High-Level Intermediate Representation" – is the primary IR used in most of rustc. It is a compiler-friendly representation of the abstract syntax tree (AST) that is generated after parsing, macro expansion, and name diff --git a/src/doc/rustc-dev-guide/src/implementing_new_features.md b/src/doc/rustc-dev-guide/src/implementing_new_features.md index 76cf2386c826..00bce8599e43 100644 --- a/src/doc/rustc-dev-guide/src/implementing_new_features.md +++ b/src/doc/rustc-dev-guide/src/implementing_new_features.md @@ -1,7 +1,5 @@ # Implementing new language features - - When you want to implement a new significant feature in the compiler, you need to go through this process to make sure everything goes smoothly. **NOTE: This section is for *language* features, not *library* features, which use [a different process].** diff --git a/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md b/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md index 880363b94bf2..288b90f33c3d 100644 --- a/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md +++ b/src/doc/rustc-dev-guide/src/llvm-coverage-instrumentation.md @@ -1,7 +1,5 @@ # LLVM source-based code coverage - - `rustc` supports detailed source-based code and test coverage analysis with a command line option (`-C instrument-coverage`) that instruments Rust libraries and binaries with additional instructions and data, at compile time. diff --git a/src/doc/rustc-dev-guide/src/macro-expansion.md b/src/doc/rustc-dev-guide/src/macro-expansion.md index a90f717004f0..54d6d2b4e813 100644 --- a/src/doc/rustc-dev-guide/src/macro-expansion.md +++ b/src/doc/rustc-dev-guide/src/macro-expansion.md @@ -1,7 +1,5 @@ # Macro expansion - - Rust has a very powerful macro system. In the previous chapter, we saw how the parser sets aside macros to be expanded (using temporary [placeholders]). This chapter is about the process of expanding those macros iteratively until diff --git a/src/doc/rustc-dev-guide/src/mir/construction.md b/src/doc/rustc-dev-guide/src/mir/construction.md index f2559a22b955..8360d9ff1a8b 100644 --- a/src/doc/rustc-dev-guide/src/mir/construction.md +++ b/src/doc/rustc-dev-guide/src/mir/construction.md @@ -1,7 +1,5 @@ # MIR construction - - The lowering of [HIR] to [MIR] occurs for the following (probably incomplete) list of items: diff --git a/src/doc/rustc-dev-guide/src/mir/dataflow.md b/src/doc/rustc-dev-guide/src/mir/dataflow.md index 85e57dd839b8..970e61196c12 100644 --- a/src/doc/rustc-dev-guide/src/mir/dataflow.md +++ b/src/doc/rustc-dev-guide/src/mir/dataflow.md @@ -1,7 +1,5 @@ # Dataflow Analysis - - If you work on the MIR, you will frequently come across various flavors of [dataflow analysis][wiki]. `rustc` uses dataflow to find uninitialized variables, determine what variables are live across a generator `yield` diff --git a/src/doc/rustc-dev-guide/src/mir/drop-elaboration.md b/src/doc/rustc-dev-guide/src/mir/drop-elaboration.md index 3b321fd44d1d..4da612c83f0f 100644 --- a/src/doc/rustc-dev-guide/src/mir/drop-elaboration.md +++ b/src/doc/rustc-dev-guide/src/mir/drop-elaboration.md @@ -1,7 +1,5 @@ # Drop elaboration - - ## Dynamic drops According to the [reference][reference-drop]: diff --git a/src/doc/rustc-dev-guide/src/mir/index.md b/src/doc/rustc-dev-guide/src/mir/index.md index f355875aa156..8ba5f3ac8b78 100644 --- a/src/doc/rustc-dev-guide/src/mir/index.md +++ b/src/doc/rustc-dev-guide/src/mir/index.md @@ -1,7 +1,5 @@ # The MIR (Mid-level IR) - - MIR is Rust's _Mid-level Intermediate Representation_. It is constructed from [HIR](../hir.html). MIR was introduced in [RFC 1211]. It is a radically simplified form of Rust that is used for diff --git a/src/doc/rustc-dev-guide/src/name-resolution.md b/src/doc/rustc-dev-guide/src/name-resolution.md index 719ebce85536..2e96382f7797 100644 --- a/src/doc/rustc-dev-guide/src/name-resolution.md +++ b/src/doc/rustc-dev-guide/src/name-resolution.md @@ -1,7 +1,5 @@ # Name resolution - - In the previous chapters, we saw how the [*Abstract Syntax Tree* (`AST`)][ast] is built with all macros expanded. We saw how doing that requires doing some name resolution to resolve imports and macro names. In this chapter, we show diff --git a/src/doc/rustc-dev-guide/src/normalization.md b/src/doc/rustc-dev-guide/src/normalization.md index eb0962a41223..53e20f1c0db7 100644 --- a/src/doc/rustc-dev-guide/src/normalization.md +++ b/src/doc/rustc-dev-guide/src/normalization.md @@ -1,7 +1,5 @@ # Aliases and Normalization - - ## Aliases In Rust there are a number of types that are considered equal to some "underlying" type, for example inherent associated types, trait associated types, free type aliases (`type Foo = u32`), and opaque types (`-> impl RPIT`). We consider such types to be "aliases", alias types are represented by the [`TyKind::Alias`][tykind_alias] variant, with the kind of alias tracked by the [`AliasTyKind`][aliaskind] enum. diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index 1e792de3c8ce..b376e962ff63 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -8,7 +8,7 @@ First you need to clone and configure the Rust repository: ```bash git clone git@github.com:rust-lang/rust cd rust -./configure --enable-llvm-link-shared --release-channel=nightly --enable-llvm-assertions --enable-offload --enable-enzyme --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs +./configure --enable-llvm-link-shared --release-channel=nightly --enable-llvm-assertions --enable-llvm-offload --enable-llvm-enzyme --enable-clang --enable-lld --enable-option-checking --enable-ninja --disable-docs ``` Afterwards you can build rustc using: diff --git a/src/doc/rustc-dev-guide/src/overview.md b/src/doc/rustc-dev-guide/src/overview.md index 8a1a22fad660..378d8c4453f8 100644 --- a/src/doc/rustc-dev-guide/src/overview.md +++ b/src/doc/rustc-dev-guide/src/overview.md @@ -1,7 +1,5 @@ # Overview of the compiler - - This chapter is about the overall process of compiling a program -- how everything fits together. @@ -323,6 +321,10 @@ the name `'tcx`, which means that something is tied to the lifetime of the [`TyCtxt`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html +For more information about queries in the compiler, see [the queries chapter][queries]. + +[queries]: ./query.md + ### `ty::Ty` Types are really important in Rust, and they form the core of a lot of compiler diff --git a/src/doc/rustc-dev-guide/src/panic-implementation.md b/src/doc/rustc-dev-guide/src/panic-implementation.md index 468190ffccd5..dba3f2146d23 100644 --- a/src/doc/rustc-dev-guide/src/panic-implementation.md +++ b/src/doc/rustc-dev-guide/src/panic-implementation.md @@ -1,7 +1,5 @@ # Panicking in Rust - - ## Step 1: Invocation of the `panic!` macro. There are actually two panic macros - one defined in `core`, and one defined in `std`. diff --git a/src/doc/rustc-dev-guide/src/profile-guided-optimization.md b/src/doc/rustc-dev-guide/src/profile-guided-optimization.md index 2fa810210451..4e3dadd406ec 100644 --- a/src/doc/rustc-dev-guide/src/profile-guided-optimization.md +++ b/src/doc/rustc-dev-guide/src/profile-guided-optimization.md @@ -1,7 +1,5 @@ # Profile-guided optimization - - `rustc` supports doing profile-guided optimization (PGO). This chapter describes what PGO is and how the support for it is implemented in `rustc`. diff --git a/src/doc/rustc-dev-guide/src/queries/incremental-compilation-in-detail.md b/src/doc/rustc-dev-guide/src/queries/incremental-compilation-in-detail.md index 18e0e25c5315..46e38832e64d 100644 --- a/src/doc/rustc-dev-guide/src/queries/incremental-compilation-in-detail.md +++ b/src/doc/rustc-dev-guide/src/queries/incremental-compilation-in-detail.md @@ -1,7 +1,5 @@ # Incremental compilation in detail - - The incremental compilation scheme is, in essence, a surprisingly simple extension to the overall query system. It relies on the fact that: diff --git a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md index 6e5b4e8cc499..731ff3287d9f 100644 --- a/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md +++ b/src/doc/rustc-dev-guide/src/queries/incremental-compilation.md @@ -1,7 +1,5 @@ # Incremental compilation - - The incremental compilation scheme is, in essence, a surprisingly simple extension to the overall query system. We'll start by describing a slightly simplified variant of the real thing – the "basic algorithm" – diff --git a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md index 444e20bc580e..c1a4373f7dac 100644 --- a/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md +++ b/src/doc/rustc-dev-guide/src/queries/query-evaluation-model-in-detail.md @@ -1,7 +1,5 @@ # The Query Evaluation Model in detail - - This chapter provides a deeper dive into the abstract model queries are built on. It does not go into implementation details but tries to explain the underlying logic. The examples here, therefore, have been stripped down and diff --git a/src/doc/rustc-dev-guide/src/queries/salsa.md b/src/doc/rustc-dev-guide/src/queries/salsa.md index 1a7b7fa9a683..dc7160edc22c 100644 --- a/src/doc/rustc-dev-guide/src/queries/salsa.md +++ b/src/doc/rustc-dev-guide/src/queries/salsa.md @@ -1,7 +1,5 @@ # How Salsa works - - This chapter is based on the explanation given by Niko Matsakis in this [video](https://www.youtube.com/watch?v=_muY4HjSqVw) about [Salsa](https://github.com/salsa-rs/salsa). To find out more you may diff --git a/src/doc/rustc-dev-guide/src/query.md b/src/doc/rustc-dev-guide/src/query.md index 0ca1b360a701..8377a7b2f31a 100644 --- a/src/doc/rustc-dev-guide/src/query.md +++ b/src/doc/rustc-dev-guide/src/query.md @@ -1,7 +1,5 @@ # Queries: demand-driven compilation - - As described in [Overview of the compiler], the Rust compiler is still (as of July 2021) transitioning from a traditional "pass-based" setup to a "demand-driven" system. The compiler query diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals.md b/src/doc/rustc-dev-guide/src/rustdoc-internals.md index 0234d4a920ed..4affbafe4777 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals.md @@ -1,7 +1,5 @@ # Rustdoc Internals - - This page describes [`rustdoc`]'s passes and modes. For an overview of `rustdoc`, see the ["Rustdoc overview" chapter](./rustdoc.md). diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md index 3506431118ba..beff0a94c1ec 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/search.md @@ -7,8 +7,6 @@ in the crates in the doc bundle, and the second reads it, turns it into some in-memory structures, and scans them linearly to search. - - ## Search index format `search.js` calls this Raw, because it turns it into diff --git a/src/doc/rustc-dev-guide/src/rustdoc.md b/src/doc/rustc-dev-guide/src/rustdoc.md index 52ae48c3735c..9290fcd3b41c 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc.md +++ b/src/doc/rustc-dev-guide/src/rustdoc.md @@ -9,8 +9,6 @@ For more details about how rustdoc works, see the [Rustdoc internals]: ./rustdoc-internals.md - - `rustdoc` uses `rustc` internals (and, of course, the standard library), so you will have to build the compiler and `std` once before you can build `rustdoc`. diff --git a/src/doc/rustc-dev-guide/src/sanitizers.md b/src/doc/rustc-dev-guide/src/sanitizers.md index 29d9056c15d0..34c78d4d952d 100644 --- a/src/doc/rustc-dev-guide/src/sanitizers.md +++ b/src/doc/rustc-dev-guide/src/sanitizers.md @@ -45,7 +45,7 @@ implementation: [marked][sanitizer-attribute] with appropriate LLVM attribute: `SanitizeAddress`, `SanitizeHWAddress`, `SanitizeMemory`, or `SanitizeThread`. By default all functions are instrumented, but this - behaviour can be changed with `#[no_sanitize(...)]`. + behaviour can be changed with `#[sanitize(xyz = "on|off")]`. * The decision whether to perform instrumentation or not is possible only at a function granularity. In the cases were those decision differ between diff --git a/src/doc/rustc-dev-guide/src/solve/candidate-preference.md b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md new file mode 100644 index 000000000000..896052947353 --- /dev/null +++ b/src/doc/rustc-dev-guide/src/solve/candidate-preference.md @@ -0,0 +1,427 @@ +# Candidate preference + +There are multiple ways to prove `Trait` and `NormalizesTo` goals. Each such option is called a [`Candidate`]. If there are multiple applicable candidates, we prefer some candidates over others. We store the relevant information in their [`CandidateSource`]. + +This preference may result in incorrect inference or region constraints and would therefore be unsound during coherence. Because of this, we simply try to merge all candidates in coherence. + +## `Trait` goals + +Trait goals merge their applicable candidates in [`fn merge_trait_candidates`]. This document provides additional details and references to explain *why* we've got the current preference rules. + +### `CandidateSource::BuiltinImpl(BuiltinImplSource::Trivial))` + +Trivial builtin impls are builtin impls which are known to be always applicable for well-formed types. This means that if one exists, using another candidate should never have fewer constraints. We currently only consider `Sized` - and `MetaSized` - impls to be trivial. + +This is necessary to prevent a lifetime error for the following pattern + +```rust +trait Trait: Sized {} +impl<'a> Trait for &'a str {} +impl<'a> Trait for &'a str {} +fn is_sized(_: T) {} +fn foo<'a, 'b, T>(x: &'b str) +where + &'a str: Trait, +{ + // Elaborating the `&'a str: Trait` where-bound results in a + // `&'a str: Sized` where-bound. We do not want to prefer this + // over the builtin impl. + is_sized(x); +} +``` + +This preference is incorrect in case the builtin impl has a nested goal which relies on a non-param where-clause +```rust +struct MyType<'a, T: ?Sized>(&'a (), T); +fn is_sized() {} +fn foo<'a, T: ?Sized>() +where + (MyType<'a, T>,): Sized, + MyType<'static, T>: Sized, +{ + // The where-bound is trivial while the builtin `Sized` impl for tuples + // requires proving `MyType<'a, T>: Sized` which can only be proven by + // using the where-clause, adding an unnecessary `'static` constraint. + is_sized::<(MyType<'a, T>,)>(); + //~^ ERROR lifetime may not live long enough +} +``` + +### `CandidateSource::ParamEnv` + +Once there's at least one *non-global* `ParamEnv` candidate, we prefer *all* `ParamEnv` candidates over other candidate kinds. +A where-bound is global if it is not higher-ranked and doesn't contain any generic parameters. It may contain `'static`. + +We try to apply where-bounds over other candidates as users tends to have the most control over them, so they can most easily +adjust them in case our candidate preference is incorrect. + +#### Preference over `Impl` candidates + +This is necessary to avoid region errors in the following example + +```rust +trait Trait<'a> {} +impl Trait<'static> for T {} +fn impls_trait<'a, T: Trait<'a>>() {} +fn foo<'a, T: Trait<'a>>() { + impls_trait::<'a, T>(); +} +``` + +We also need this as shadowed impls can result in currently ambiguous solver cycles: [trait-system-refactor-initiative#76]. Without preference we'd be forced to fail with ambiguity +errors if the where-bound results in region constraints to avoid incompleteness. +```rust +trait Super { + type SuperAssoc; +} + +trait Trait: Super { + type TraitAssoc; +} + +impl Trait for T +where + T: Super, +{ + type TraitAssoc = U; +} + +fn overflow() { + // We can use the elaborated `Super` where-bound + // to prove the where-bound of the `T: Trait` implementation. This currently results in + // overflow. + let x: ::TraitAssoc; +} +``` + +This preference causes a lot of issues. See [#24066]. Most of the +issues are caused by prefering where-bounds over impls even if the where-bound guides type inference: +```rust +trait Trait { + fn call_me(&self, x: T) {} +} +impl Trait for T {} +impl Trait for T {} +fn bug, U>(x: T) { + x.call_me(1u32); + //~^ ERROR mismatched types +} +``` +However, even if we only apply this preference if the where-bound doesn't guide inference, it may still result +in incorrect lifetime constraints: +```rust +trait Trait<'a> {} +impl<'a> Trait<'a> for &'a str {} +fn impls_trait<'a, T: Trait<'a>>(_: T) {} +fn foo<'a, 'b>(x: &'b str) +where + &'a str: Trait<'b> +{ + // Need to prove `&'x str: Trait<'b>` with `'b: 'x`. + impls_trait::<'b, _>(x); + //~^ ERROR lifetime may not live long enough +} +``` + +#### Preference over `AliasBound` candidates + +This is necessary to avoid region errors in the following example +```rust +trait Bound<'a> {} +trait Trait<'a> { + type Assoc: Bound<'a>; +} + +fn impls_bound<'b, T: Bound<'b>>() {} +fn foo<'a, 'b, 'c, T>() +where + T: Trait<'a>, + for<'hr> T::Assoc: Bound<'hr>, +{ + impls_bound::<'b, T::Assoc>(); + impls_bound::<'c, T::Assoc>(); +} +``` +It can also result in unnecessary constraints +```rust +trait Bound<'a> {} +trait Trait<'a> { + type Assoc: Bound<'a>; +} + +fn impls_bound<'b, T: Bound<'b>>() {} +fn foo<'a, 'b, T>() +where + T: for<'hr> Trait<'hr>, + >::Assoc: Bound<'a>, +{ + // Using the where-bound for `>::Assoc: Bound<'a>` + // unnecessarily equates `>::Assoc` with the + // `>::Assoc` from the env. + impls_bound::<'a, >::Assoc>(); + // For a `>::Assoc: Bound<'b>` the self type of the + // where-bound matches, but the arguments of the trait bound don't. + impls_bound::<'b, >::Assoc>(); +} +``` + +#### Why no preference for global where-bounds + +Global where-bounds are either fully implied by an impl or unsatisfiable. If they are unsatisfiable, we don't really care what happens. If a where-bound is fully implied then using the impl to prove the trait goal cannot result in additional constraints. For trait goals this is only useful for where-bounds which use `'static`: + +```rust +trait A { + fn test(&self); +} + +fn foo(x: &dyn A) +where + dyn A + 'static: A, // Using this bound would lead to a lifetime error. +{ + x.test(); +} +``` +More importantly, by using impls here we prevent global where-bounds from shadowing impls when normalizing associated types. There are no known issues from preferring impls over global where-bounds. + +#### Why still consider global where-bounds + +Given that we just use impls even if there exists a global where-bounds, you may ask why we don't just ignore these global where-bounds entirely: we use them to weaken the inference guidance from non-global where-bounds. + +Without a global where-bound, we currently prefer non-global where bounds even though there would be an applicable impl as well. By adding a non-global where-bound, this unnecessary inference guidance is disabled, allowing the following to compile: +```rust +fn check(color: Color) +where + Vec: Into + Into, +{ + let _: f32 = Vec.into(); + // Without the global `Vec: Into` bound we'd + // eagerly use the non-global `Vec: Into` bound + // here, causing this to fail. +} + +struct Vec; +impl From for f32 { + fn from(_: Vec) -> Self { + loop {} + } +} +``` + +### `CandidateSource::AliasBound` + +We prefer alias-bound candidates over impls. We currently use this preference to guide type inference, causing the following to compile. I personally don't think this preference is desirable 🤷 +```rust +pub trait Dyn { + type Word: Into; + fn d_tag(&self) -> Self::Word; + fn tag32(&self) -> Option { + self.d_tag().into().try_into().ok() + // prove `Self::Word: Into` and then select a method + // on `?0`, needs eager inference. + } +} +``` +```rust +fn impl_trait() -> impl Into { + 0u16 +} + +fn main() { + // There are two possible types for `x`: + // - `u32` by using the "alias bound" of `impl Into` + // - `impl Into`, i.e. `u16`, by using `impl From for T` + // + // We infer the type of `x` to be `u32` even though this is not + // strictly necessary and can even lead to surprising errors. + let x = impl_trait().into(); + println!("{}", std::mem::size_of_val(&x)); +} +``` +This preference also avoids ambiguity due to region constraints, I don't know whether people rely on this in practice. +```rust +trait Bound<'a> {} +impl Bound<'static> for T {} +trait Trait<'a> { + type Assoc: Bound<'a>; +} + +fn impls_bound<'b, T: Bound<'b>>() {} +fn foo<'a, T: Trait<'a>>() { + // Should we infer this to `'a` or `'static`. + impls_bound::<'_, T::Assoc>(); +} +``` + +### `CandidateSource::BuiltinImpl(BuiltinImplSource::Object(_))` + +We prefer builtin trait object impls over user-written impls. This is **unsound** and should be remoed in the future. See [#57893](https://github.com/rust-lang/rust/issues/57893) and [#141347](https://github.com/rust-lang/rust/pull/141347) for more details. + +## `NormalizesTo` goals + +The candidate preference behavior during normalization is implemented in [`fn assemble_and_merge_candidates`]. + +### Where-bounds shadow impls + +Normalization of associated items does not consider impls if the corresponding trait goal has been proven via a `ParamEnv` or `AliasBound` candidate. +This means that for where-bounds which do not constrain associated types, the associated types remain *rigid*. + +This is necessary to avoid unnecessary region constraints from applying impls. +```rust +trait Trait<'a> { + type Assoc; +} +impl Trait<'static> for u32 { + type Assoc = u32; +} + +fn bar<'b, T: Trait<'b>>() -> T::Assoc { todo!() } +fn foo<'a>() +where + u32: Trait<'a>, +{ + // Normalizing the return type would use the impl, proving + // the `T: Trait` where-bound would use the where-bound, resulting + // in different region constraints. + bar::<'_, u32>(); +} +``` + +### We always consider `AliasBound` candidates + +In case the where-bound does not specify the associated item, we consider `AliasBound` candidates instead of treating the alias as rigid, even though the trait goal was proven via a `ParamEnv` candidate. + +```rust +trait Super { + type Assoc; +} +trait Bound { + type Assoc: Super; +} +trait Trait: Super {} + +// Elaborating the environment results in a `T::Assoc: Super` where-bound. +// This where-bound must not prevent normalization via the `Super` +// item bound. +fn heck>(x: ::Assoc) -> u32 { + x +} +``` +Using such an alias can result in additional region constraints, cc [#133044]. +```rust +trait Bound<'a> { + type Assoc; +} +trait Trait { + type Assoc: Bound<'static, Assoc = u32>; +} + +fn heck<'a, T: Trait>>(x: >::Assoc) { + // Normalizing the associated type requires `T::Assoc: Bound<'static>` as it + // uses the `Bound<'static>` alias-bound instead of keeping the alias rigid. + drop(x); +} +``` + +### We prefer `ParamEnv` candidates over `AliasBound` + +While we use `AliasBound` candidates if the where-bound does not specify the associated type, in case it does, we prefer the where-bound. +This is necessary for the following example: +```rust +// Make sure we prefer the `I::IntoIterator: Iterator` +// where-bound over the `I::Intoiterator: Iterator` +// alias-bound. + +trait Iterator { + type Item; +} + +trait IntoIterator { + type Item; + type IntoIter: Iterator; +} + +fn normalize>() {} + +fn foo() +where + I: IntoIterator, + I::IntoIter: Iterator, +{ + // We need to prefer the `I::IntoIterator: Iterator` + // where-bound over the `I::Intoiterator: Iterator` + // alias-bound. + normalize::(); +} +``` + +### We always consider where-bounds + +Even if the trait goal was proven via an impl, we still prefer `ParamEnv` candidates, if any exist. + +#### We prefer "orphaned" where-bounds + +We add "orphaned" `Projection` clauses into the `ParamEnv` when normalizing item bounds of GATs and RPITIT in `fn check_type_bounds`. +We need to prefer these `ParamEnv` candidates over impls and other where-bounds. +```rust +#![feature(associated_type_defaults)] +trait Foo { + // We should be able to prove that `i32: Baz` because of + // the impl below, which requires that `Self::Bar<()>: Eq` + // which is true, because we assume `for Self::Bar = i32`. + type Bar: Baz = i32; +} +trait Baz {} +impl Baz for i32 where T::Bar<()>: Eq {} +trait Eq {} +impl Eq for T {} +``` + +I don't fully understand the cases where this preference is actually necessary and haven't been able to exploit this in fun ways yet, but 🤷 + +#### We prefer global where-bounds over impls + +This is necessary for the following to compile. I don't know whether anything relies on it in practice 🤷 +```rust +trait Id { + type This; +} +impl Id for T { + type This = T; +} + +fn foo(x: T) -> ::This +where + u32: Id, +{ + x +} +``` +This means normalization can result in additional region constraints, cc [#133044]. +```rust +trait Trait { + type Assoc; +} + +impl Trait for &u32 { + type Assoc = u32; +} + +fn trait_bound() {} +fn normalize>() {} + +fn foo<'a>() +where + &'static u32: Trait, +{ + trait_bound::<&'a u32>(); // ok, proven via impl + normalize::<&'a u32>(); // error, proven via where-bound +} +``` + +[`Candidate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/assembly/struct.Candidate.html +[`CandidateSource`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/enum.CandidateSource.html +[`fn merge_trait_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs#L1342-L1424 +[`fn assemble_and_merge_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs#L920-L1003 +[trait-system-refactor-initiative#76]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/76 +[#24066]: https://github.com/rust-lang/rust/issues/24066 +[#133044]: https://github.com/rust-lang/rust/issues/133044 \ No newline at end of file diff --git a/src/doc/rustc-dev-guide/src/stability.md b/src/doc/rustc-dev-guide/src/stability.md index 230925252bac..3c4c65fdd5a8 100644 --- a/src/doc/rustc-dev-guide/src/stability.md +++ b/src/doc/rustc-dev-guide/src/stability.md @@ -6,8 +6,6 @@ APIs to use unstable APIs internally in the rustc standard library. **NOTE**: this section is for *library* features, not *language* features. For instructions on stabilizing a language feature see [Stabilizing Features](./stabilization_guide.md). - - ## unstable The `#[unstable(feature = "foo", issue = "1234", reason = "lorem ipsum")]` @@ -183,4 +181,12 @@ the `deprecated_in_future` lint is triggered which is default `allow`, but most of the standard library raises it to a warning with `#![warn(deprecated_in_future)]`. +## unstable_feature_bound +The `#[unstable_feature_bound(foo)]` attribute can be used together with `#[unstable]` attribute to mark an `impl` of stable type and stable trait as unstable. In std/core, an item annotated with `#[unstable_feature_bound(foo)]` can only be used by another item that is also annotated with `#[unstable_feature_bound(foo)]`. Outside of std/core, using an item with `#[unstable_feature_bound(foo)]` requires the feature to be enabled with `#![feature(foo)]` attribute on the crate. + +Currently, the items that can be annotated with `#[unstable_feature_bound]` are: +- `impl` +- free function +- trait + [blog]: https://www.ralfj.de/blog/2018/07/19/const.html diff --git a/src/doc/rustc-dev-guide/src/stabilization_guide.md b/src/doc/rustc-dev-guide/src/stabilization_guide.md index f155272e5a2c..e399930fc523 100644 --- a/src/doc/rustc-dev-guide/src/stabilization_guide.md +++ b/src/doc/rustc-dev-guide/src/stabilization_guide.md @@ -6,8 +6,6 @@ Once an unstable feature has been well-tested with no outstanding concerns, anyone may push for its stabilization, though involving the people who have worked on it is prudent. Follow these steps: - - ## Write an RFC, if needed If the feature was part of a [lang experiment], the lang team generally will want to first accept an RFC before stabilization. diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index e906dd29f25f..f09d73631998 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -1,7 +1,5 @@ # The `#[test]` attribute - - Many Rust programmers rely on a built-in attribute called `#[test]`. All diff --git a/src/doc/rustc-dev-guide/src/tests/adding.md b/src/doc/rustc-dev-guide/src/tests/adding.md index 895eabfbd56a..e5c26bef11d0 100644 --- a/src/doc/rustc-dev-guide/src/tests/adding.md +++ b/src/doc/rustc-dev-guide/src/tests/adding.md @@ -1,7 +1,5 @@ # Adding new tests - - **In general, we expect every PR that fixes a bug in rustc to come accompanied by a regression test of some kind.** This test should fail in master but pass after the PR. These tests are really useful for preventing us from repeating the diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index a108dfdef9b3..4980ed845d6d 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -1,7 +1,5 @@ # Compiletest - - ## Introduction `compiletest` is the main test harness of the Rust test suite. It allows test diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index 89e4d3e9b58e..f4ba9a044e66 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -1,7 +1,5 @@ # Compiletest directives - - @@ -52,6 +50,8 @@ not be exhaustive. Directives can generally be found by browsing the ### Auxiliary builds +See [Building auxiliary crates](compiletest.html#building-auxiliary-crates) + | Directive | Explanation | Supported test suites | Possible values | |-----------------------|-------------------------------------------------------------------------------------------------------|-----------------------|-----------------------------------------------| | `aux-bin` | Build a aux binary, made available in `auxiliary/bin` relative to test directory | All except `run-make` | Path to auxiliary `.rs` file | @@ -61,8 +61,7 @@ not be exhaustive. Directives can generally be found by browsing the | `proc-macro` | Similar to `aux-build`, but for aux forces host and don't use `-Cprefer-dynamic`[^pm]. | All except `run-make` | Path to auxiliary proc-macro `.rs` file | | `build-aux-docs` | Build docs for auxiliaries as well. Note that this only works with `aux-build`, not `aux-crate`. | All except `run-make` | N/A | -[^pm]: please see the Auxiliary proc-macro section in the - [compiletest](./compiletest.md) chapter for specifics. +[^pm]: please see the [Auxiliary proc-macro section](compiletest.html#auxiliary-proc-macro) in the compiletest chapter for specifics. ### Controlling outcome expectations @@ -298,6 +297,7 @@ See [Pretty-printer](compiletest.md#pretty-printer-tests). - [`should-ice`](compiletest.md#incremental-tests) — incremental cfail should ICE - [`reference`] — an annotation linking to a rule in the reference +- `disable-gdb-pretty-printers` — disable gdb pretty printers for debuginfo tests [`reference`]: https://github.com/rust-lang/reference/blob/master/docs/authoring.md#test-rule-annotations @@ -358,7 +358,7 @@ described below: - Example: `x86_64-unknown-linux-gnu` See -[`tests/ui/commandline-argfile.rs`](https://github.com/rust-lang/rust/blob/master/tests/ui/argfile/commandline-argfile.rs) +[`tests/ui/argfile/commandline-argfile.rs`](https://github.com/rust-lang/rust/blob/master/tests/ui/argfile/commandline-argfile.rs) for an example of a test that uses this substitution. [output normalization]: ui.md#normalization diff --git a/src/doc/rustc-dev-guide/src/tests/docker.md b/src/doc/rustc-dev-guide/src/tests/docker.md index 032da1ca1e8d..ae0939842237 100644 --- a/src/doc/rustc-dev-guide/src/tests/docker.md +++ b/src/doc/rustc-dev-guide/src/tests/docker.md @@ -6,12 +6,12 @@ need to install Docker on a Linux, Windows, or macOS system (typically Linux will be much faster than Windows or macOS because the latter use virtual machines to emulate a Linux environment). -Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. If you want to run a CI job locally in the simplest way possible, you can use a provided helper Python script that tries to replicate what happens on CI as closely as possible: +Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. If you want to run a CI job locally in the simplest way possible, you can use a provided helper `citool` that tries to replicate what happens on CI as closely as possible: ```bash -python3 src/ci/github-actions/ci.py run-local +cargo run --manifest-path src/ci/citool/Cargo.toml run-local # For example: -python3 src/ci/github-actions/ci.py run-local dist-x86_64-linux-alt +cargo run --manifest-path src/ci/citool/Cargo.toml run-local dist-x86_64-linux-alt ``` If the above script does not work for you, you would like to have more control of the Docker image execution, or you want to understand what exactly happens during Docker job execution, then continue reading below. @@ -53,15 +53,6 @@ Some additional notes about using the interactive mode: containers. With the container name, run `docker exec -it /bin/bash` where `` is the container name like `4ba195e95cef`. -The approach described above is a relatively low-level interface for running the Docker images -directly. If you want to run a full CI Linux job locally with Docker, in a way that is as close to CI as possible, you can use the following command: - -```bash -cargo run --manifest-path src/ci/citool/Cargo.toml run-local -# For example: -cargo run --manifest-path src/ci/citool/Cargo.toml run-local dist-x86_64-linux-alt -``` - [Docker]: https://www.docker.com/ [`src/ci/docker`]: https://github.com/rust-lang/rust/tree/master/src/ci/docker [`src/ci/docker/run.sh`]: https://github.com/rust-lang/rust/blob/master/src/ci/docker/run.sh diff --git a/src/doc/rustc-dev-guide/src/tests/intro.md b/src/doc/rustc-dev-guide/src/tests/intro.md index 79b96c450a8d..b90c16d602c3 100644 --- a/src/doc/rustc-dev-guide/src/tests/intro.md +++ b/src/doc/rustc-dev-guide/src/tests/intro.md @@ -1,7 +1,5 @@ # Testing the compiler - - The Rust project runs a wide variety of different tests, orchestrated by the build system (`./x test`). This section gives a brief overview of the different testing tools. Subsequent chapters dive into [running tests](running.md) and diff --git a/src/doc/rustc-dev-guide/src/tests/running.md b/src/doc/rustc-dev-guide/src/tests/running.md index 6526fe9c2357..317b65f98cd3 100644 --- a/src/doc/rustc-dev-guide/src/tests/running.md +++ b/src/doc/rustc-dev-guide/src/tests/running.md @@ -1,7 +1,5 @@ # Running tests - - You can run the entire test collection using `x`. But note that running the *entire* test collection is almost never what you want to do during local development because it takes a really long time. For local development, see the @@ -344,7 +342,6 @@ coordinate running tests (see [src/bootstrap/src/core/build_steps/test.rs]). > **TODO** > > - Is there any support for using an iOS emulator? -> - It's also unclear to me how the wasm or asm.js tests are run. [armhf-gnu]: https://github.com/rust-lang/rust/tree/master/src/ci/docker/host-x86_64/armhf-gnu/Dockerfile [QEMU]: https://www.qemu.org/ @@ -352,6 +349,28 @@ coordinate running tests (see [src/bootstrap/src/core/build_steps/test.rs]). [remote-test-server]: https://github.com/rust-lang/rust/tree/master/src/tools/remote-test-server [src/bootstrap/src/core/build_steps/test.rs]: https://github.com/rust-lang/rust/blob/master/src/bootstrap/src/core/build_steps/test.rs +## Testing tests on wasi (wasm32-wasip1) + +Some tests are specific to wasm targets. +To run theste tests, you have to pass `--target wasm32-wasip1` to `x test`. +Additionally, you need the wasi sdk. +Follow the install instructions from the [wasi sdk repository] to get a sysroot on your computer. +On the [wasm32-wasip1 target support page] a minimum version is specified that your sdk must be able to build. +Some cmake commands that take a while and give a lot of very concerning c++ warnings... +Then, in `bootstrap.toml`, point to the sysroot like so: + +``` +[target.wasm32-wasip1] +wasi-root = "/build/sysroot/install/share/wasi-sysroot" +``` + +In my case I git-cloned it next to my rust folder, so it was `../wasi-sdk/build/....` +Now, tests should just run, you don't have to set up anything else. + +[wasi sdk repository]: https://github.com/WebAssembly/wasi-sdk +[wasm32-wasip1 target support page]: https://github.com/rust-lang/rust/blob/master/src/doc/rustc/src/platform-support/wasm32-wasip1.md#building-the-target. + + ## Running rustc_codegen_gcc tests First thing to know is that it only supports linux x86_64 at the moment. We will diff --git a/src/doc/rustc-dev-guide/src/tests/ui.md b/src/doc/rustc-dev-guide/src/tests/ui.md index 782f78d76148..25dd5814cf6f 100644 --- a/src/doc/rustc-dev-guide/src/tests/ui.md +++ b/src/doc/rustc-dev-guide/src/tests/ui.md @@ -1,7 +1,5 @@ # UI tests - - UI tests are a particular [test suite](compiletest.md#test-suites) of compiletest. @@ -25,9 +23,9 @@ If you need to work with `#![no_std]` cross-compiling tests, consult the ## General structure of a test -A test consists of a Rust source file located anywhere in the `tests/ui` -directory, but they should be placed in a suitable sub-directory. For example, -[`tests/ui/hello.rs`] is a basic hello-world test. +A test consists of a Rust source file located in the `tests/ui` directory. +**Tests must be placed in the appropriate subdirectory** based on their purpose +and testing category - placing tests directly in `tests/ui` is not permitted. Compiletest will use `rustc` to compile the test, and compare the output against the expected output which is stored in a `.stdout` or `.stderr` file located @@ -46,8 +44,6 @@ pass/fail expectations](#controlling-passfail-expectations). By default, a test is built as an executable binary. If you need a different crate type, you can use the `#![crate_type]` attribute to set it as needed. -[`tests/ui/hello.rs`]: https://github.com/rust-lang/rust/blob/master/tests/ui/hello.rs - ## Output comparison UI tests store the expected output from the compiler in `.stderr` and `.stdout` diff --git a/src/doc/rustc-dev-guide/src/thir.md b/src/doc/rustc-dev-guide/src/thir.md index 73d09ad80bf9..3d3dafaef49b 100644 --- a/src/doc/rustc-dev-guide/src/thir.md +++ b/src/doc/rustc-dev-guide/src/thir.md @@ -1,7 +1,5 @@ # The THIR - - The THIR ("Typed High-Level Intermediate Representation"), previously called HAIR for "High-Level Abstract IR", is another IR used by rustc that is generated after [type checking]. It is (as of January 2024) used for diff --git a/src/doc/rustc-dev-guide/src/tracing.md b/src/doc/rustc-dev-guide/src/tracing.md index 0cfdf306e92d..5e5b81fc65b2 100644 --- a/src/doc/rustc-dev-guide/src/tracing.md +++ b/src/doc/rustc-dev-guide/src/tracing.md @@ -1,7 +1,5 @@ # Using tracing to debug the compiler - - The compiler has a lot of [`debug!`] (or `trace!`) calls, which print out logging information at many points. These are very useful to at least narrow down the location of a bug if not to find it entirely, or just to orient yourself as to why the diff --git a/src/doc/rustc-dev-guide/src/traits/goals-and-clauses.md b/src/doc/rustc-dev-guide/src/traits/goals-and-clauses.md index 40fd4581bf3e..2884ca5a05a1 100644 --- a/src/doc/rustc-dev-guide/src/traits/goals-and-clauses.md +++ b/src/doc/rustc-dev-guide/src/traits/goals-and-clauses.md @@ -1,7 +1,5 @@ # Goals and clauses - - In logic programming terms, a **goal** is something that you must prove and a **clause** is something that you know is true. As described in the [lowering to logic](./lowering-to-logic.html) diff --git a/src/doc/rustc-dev-guide/src/traits/lowering-to-logic.md b/src/doc/rustc-dev-guide/src/traits/lowering-to-logic.md index 1248d434610b..cc8b3bf800cb 100644 --- a/src/doc/rustc-dev-guide/src/traits/lowering-to-logic.md +++ b/src/doc/rustc-dev-guide/src/traits/lowering-to-logic.md @@ -1,7 +1,5 @@ # Lowering to logic - - The key observation here is that the Rust trait system is basically a kind of logic, and it can be mapped onto standard logical inference rules. We can then look for solutions to those inference rules in a diff --git a/src/doc/rustc-dev-guide/src/traits/resolution.md b/src/doc/rustc-dev-guide/src/traits/resolution.md index c62b0593694f..ccb2b04268e8 100644 --- a/src/doc/rustc-dev-guide/src/traits/resolution.md +++ b/src/doc/rustc-dev-guide/src/traits/resolution.md @@ -1,7 +1,5 @@ # Trait resolution (old-style) - - This chapter describes the general process of _trait resolution_ and points out some non-obvious things. diff --git a/src/doc/rustc-dev-guide/src/ty.md b/src/doc/rustc-dev-guide/src/ty.md index 767ac3fdba21..4055f475e992 100644 --- a/src/doc/rustc-dev-guide/src/ty.md +++ b/src/doc/rustc-dev-guide/src/ty.md @@ -1,7 +1,5 @@ # The `ty` module: representing types - - The `ty` module defines how the Rust compiler represents types internally. It also defines the *typing context* (`tcx` or `TyCtxt`), which is the central data structure in the compiler. diff --git a/src/doc/rustc-dev-guide/src/type-inference.md b/src/doc/rustc-dev-guide/src/type-inference.md index 888eb2439c5b..2243205f129b 100644 --- a/src/doc/rustc-dev-guide/src/type-inference.md +++ b/src/doc/rustc-dev-guide/src/type-inference.md @@ -1,7 +1,5 @@ # Type inference - - Type inference is the process of automatic detection of the type of an expression. diff --git a/src/doc/rustc-dev-guide/src/typing_parameter_envs.md b/src/doc/rustc-dev-guide/src/typing_parameter_envs.md index e21bc5155da1..db15467a47a0 100644 --- a/src/doc/rustc-dev-guide/src/typing_parameter_envs.md +++ b/src/doc/rustc-dev-guide/src/typing_parameter_envs.md @@ -1,7 +1,5 @@ # Typing/Parameter Environments - - ## Typing Environments When interacting with the type system there are a few variables to consider that can affect the results of trait solving. The set of in-scope where clauses, and what phase of the compiler type system operations are being performed in (the [`ParamEnv`][penv] and [`TypingMode`][tmode] structs respectively). diff --git a/src/doc/rustc-dev-guide/src/variance.md b/src/doc/rustc-dev-guide/src/variance.md index ad4fa4adfddb..7aa014071551 100644 --- a/src/doc/rustc-dev-guide/src/variance.md +++ b/src/doc/rustc-dev-guide/src/variance.md @@ -1,7 +1,5 @@ # Variance of type and lifetime parameters - - For a more general background on variance, see the [background] appendix. [background]: ./appendix/background.html diff --git a/src/doc/rustc-dev-guide/src/walkthrough.md b/src/doc/rustc-dev-guide/src/walkthrough.md index 48b3f8bb15d3..b4c3379347ed 100644 --- a/src/doc/rustc-dev-guide/src/walkthrough.md +++ b/src/doc/rustc-dev-guide/src/walkthrough.md @@ -1,7 +1,5 @@ # Walkthrough: a typical contribution - - There are _a lot_ of ways to contribute to the Rust compiler, including fixing bugs, improving performance, helping design features, providing feedback on existing features, etc. This chapter does not claim to scratch the surface. diff --git a/src/doc/rustc-dev-guide/triagebot.toml b/src/doc/rustc-dev-guide/triagebot.toml index b3f4c2d281cd..3ac5d57a52b0 100644 --- a/src/doc/rustc-dev-guide/triagebot.toml +++ b/src/doc/rustc-dev-guide/triagebot.toml @@ -62,9 +62,6 @@ allow-unauthenticated = [ # Documentation at: https://forge.rust-lang.org/triagebot/issue-links.html [issue-links] -# Automatically close and reopen PRs made by bots to run CI on them -[bot-pull-requests] - [behind-upstream] days-threshold = 7 diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 7c688e32bc0d..b53494ed98d4 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -23,6 +23,7 @@ - [Linker-plugin-based LTO](linker-plugin-lto.md) - [Checking Conditional Configurations](check-cfg.md) - [Cargo Specifics](check-cfg/cargo-specifics.md) +- [Remap source paths](remap-source-paths.md) - [Exploit Mitigations](exploit-mitigations.md) - [Symbol Mangling](symbol-mangling/index.md) - [v0 Symbol Format](symbol-mangling/v0.md) @@ -47,6 +48,7 @@ - [\*-apple-visionos](platform-support/apple-visionos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) - [aarch64-unknown-linux-musl](platform-support/aarch64-unknown-linux-musl.md) + - [aarch64_be-unknown-none-softfloat](platform-support/aarch64_be-unknown-none-softfloat.md) - [amdgcn-amd-amdhsa](platform-support/amdgcn-amd-amdhsa.md) - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md) - [arm-none-eabi](platform-support/arm-none-eabi.md) @@ -65,6 +67,7 @@ - [armv7-sony-vita-newlibeabihf](platform-support/armv7-sony-vita-newlibeabihf.md) - [armv7-unknown-linux-uclibceabi](platform-support/armv7-unknown-linux-uclibceabi.md) - [armv7-unknown-linux-uclibceabihf](platform-support/armv7-unknown-linux-uclibceabihf.md) + - [armv7a-vex-v5](platform-support/armv7a-vex-v5.md) - [\*-android and \*-androideabi](platform-support/android.md) - [\*-linux-ohos](platform-support/openharmony.md) - [\*-hurd-gnu](platform-support/hurd.md) diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index 07eafdf4c4c6..445b10188e3f 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -375,12 +375,12 @@ linking time. It takes one of the following values: * `y`, `yes`, `on`, `true`, `fat`, or no value: perform "fat" LTO which attempts to perform optimizations across all crates within the dependency graph. -* `n`, `no`, `off`, `false`: disables LTO. * `thin`: perform ["thin" LTO](http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html). This is similar to "fat", but takes substantially less time to run while still achieving performance gains similar to "fat". For larger projects like the Rust compiler, ThinLTO can even result in better performance than fat LTO. +* `n`, `no`, `off`, `false`: disables LTO. If `-C lto` is not specified, then the compiler will attempt to perform "thin local LTO" which performs "thin" LTO on the local crate only across its diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index d45ad1be27b8..0b15fbc24dfc 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -418,22 +418,15 @@ This flag takes a number that specifies the width of the terminal in characters. Formatting of diagnostics will take the width into consideration to make them better fit on the screen. -## `--remap-path-prefix`: remap source names in output +## `--remap-path-prefix`: remap source paths in output Remap source path prefixes in all output, including compiler diagnostics, -debug information, macro expansions, etc. It takes a value of the form -`FROM=TO` where a path prefix equal to `FROM` is rewritten to the value `TO`. -The `FROM` may itself contain an `=` symbol, but the `TO` value may not. This -flag may be specified multiple times. - -This is useful for normalizing build products, for example by removing the -current directory out of pathnames emitted into the object files. The -replacement is purely textual, with no consideration of the current system's -pathname syntax. For example `--remap-path-prefix foo=bar` will match -`foo/lib.rs` but not `./foo/lib.rs`. - -When multiple remappings are given and several of them match, the **last** -matching one is applied. +debug information, macro expansions, etc. It takes a value of the form `FROM=TO` +where a path prefix equal to `FROM` is rewritten to the value `TO`. This flag may be +specified multiple times. + +Refer to the [Remap source paths](remap-source-paths.md) section of this book for +further details and explanation. ## `--json`: configure json messages printed by the compiler diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 65b706301538..c039517a9703 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -36,7 +36,6 @@ target | notes `aarch64-unknown-linux-gnu` | ARM64 Linux (kernel 4.1+, glibc 2.17+) [`i686-pc-windows-msvc`](platform-support/windows-msvc.md) | 32-bit MSVC (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment] `i686-unknown-linux-gnu` | 32-bit Linux (kernel 3.2+, glibc 2.17+, Pentium 4) [^x86_32-floats-return-ABI] -[`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+) [`x86_64-pc-windows-gnu`](platform-support/windows-gnu.md) | 64-bit MinGW (Windows 10+, Windows Server 2016+) [`x86_64-pc-windows-msvc`](platform-support/windows-msvc.md) | 64-bit MSVC (Windows 10+, Windows Server 2016+) `x86_64-unknown-linux-gnu` | 64-bit Linux (kernel 3.2+, glibc 2.17+) @@ -106,6 +105,7 @@ target | notes [`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20+, glibc 2.29) [`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | RISC-V Linux (kernel 4.20+, musl 1.2.3) [`s390x-unknown-linux-gnu`](platform-support/s390x-unknown-linux-gnu.md) | S390x Linux (kernel 3.2+, glibc 2.17) +[`x86_64-apple-darwin`](platform-support/apple-darwin.md) | 64-bit macOS (10.12+, Sierra+) [`x86_64-pc-windows-gnullvm`](platform-support/windows-gnullvm.md) | 64-bit x86 MinGW (Windows 10+), LLVM ABI [`x86_64-unknown-freebsd`](platform-support/freebsd.md) | 64-bit x86 FreeBSD [`x86_64-unknown-illumos`](platform-support/illumos.md) | illumos @@ -273,6 +273,7 @@ target | std | host | notes `aarch64_be-unknown-linux-gnu` | ✓ | ✓ | ARM64 Linux (big-endian) `aarch64_be-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (big-endian, ILP32 ABI) [`aarch64_be-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD (big-endian) +[`aarch64_be-unknown-none-softfloat`](platform-support/aarch64_be-unknown-none-softfloat.md) | * | | Bare big-endian ARM64, softfloat [`amdgcn-amd-amdhsa`](platform-support/amdgcn-amd-amdhsa.md) | * | | `-Ctarget-cpu=gfx...` to specify [the AMD GPU] to compile for [`arm64_32-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | Arm Apple WatchOS 64-bit with 32-bit pointers [`arm64e-apple-darwin`](platform-support/arm64e-apple-darwin.md) | ✓ | ✓ | ARM64e Apple Darwin @@ -297,6 +298,7 @@ target | std | host | notes [`armv7a-kmc-solid_asp3-eabi`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3 [`armv7a-kmc-solid_asp3-eabihf`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3, hardfloat [`armv7a-none-eabihf`](platform-support/arm-none-eabi.md) | * | | Bare Armv7-A, hardfloat +[`armv7a-vex-v5`](platform-support/armv7a-vex-v5.md) | ? | | Armv7-A Cortex-A9 VEX V5 Brain, VEXos [`armv7k-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | Armv7-A Apple WatchOS [`armv7s-apple-ios`](platform-support/apple-ios.md) | ✓ | | Armv7-A Apple-A6 Apple iOS [`armv8r-none-eabihf`](platform-support/armv8r-none-eabihf.md) | * | | Bare Armv8-R, hardfloat diff --git a/src/doc/rustc/src/platform-support/aarch64_be-unknown-none-softfloat.md b/src/doc/rustc/src/platform-support/aarch64_be-unknown-none-softfloat.md new file mode 100644 index 000000000000..a28ddcdf7f2f --- /dev/null +++ b/src/doc/rustc/src/platform-support/aarch64_be-unknown-none-softfloat.md @@ -0,0 +1,74 @@ +# aarch64_be-unknown-none-softfloat + +**Tier: 3** + +Target for freestanding/bare-metal big-endian ARM64 binaries in ELF format: +firmware, kernels, etc. + +## Target maintainers + +[@Gelbpunkt](https://github.com/Gelbpunkt) + +## Requirements + +This target is cross-compiled. There is no support for `std`. There is no +default allocator, but it's possible to use `alloc` by supplying an allocator. + +The target does not assume existence of a FPU and does not make use of any +non-GPR register. This allows the generated code to run in environments, such +as kernels, which may need to avoid the use of such registers or which may have +special considerations about the use of such registers (e.g. saving and +restoring them to avoid breaking userspace code using the same registers). You +can change code generation to use additional CPU features via the +`-C target-feature=` codegen options to rustc, or via the `#[target_feature]` +mechanism within Rust code. + +By default, code generated with the soft-float target should run on any +big-endian ARM64 hardware, enabling additional target features may raise this +baseline. + +`extern "C"` uses the [architecture's standard calling convention][aapcs64]. + +[aapcs64]: https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst + +The targets generate binaries in the ELF format. Any alternate formats or +special considerations for binary layout will require linker options or linker +scripts. + +## Building the target + +You can build Rust with support for the target by adding it to the `target` +list in `bootstrap.toml`: + +```toml +[build] +target = ["aarch64_be-unknown-none-softfloat"] +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will first need to build Rust with the target enabled (see +"Building the target" above). + +## Cross-compilation + +For cross builds, you will need an appropriate ARM64 C/C++ toolchain for +linking, or if you want to compile C code along with Rust (such as for Rust +crates with C dependencies). + +Rust *may* be able to use an `aarch64_be-unknown-linux-{gnu,musl}-` toolchain +with appropriate standalone flags to build for this target (depending on the +assumptions of that toolchain, see below), or you may wish to use a separate +`aarch64_be-unknown-none-softfloat` toolchain. + +On some ARM64 hosts that use ELF binaries, you *may* be able to use the host C +toolchain, if it does not introduce assumptions about the host environment that +don't match the expectations of a standalone environment. Otherwise, you may +need a separate toolchain for standalone/freestanding development, just as when +cross-compiling from a non-ARM64 platform. + +## Testing + +As the target supports a variety of different environments and does not support +`std`, it does not support running the Rust test suite. diff --git a/src/doc/rustc/src/platform-support/apple-darwin.md b/src/doc/rustc/src/platform-support/apple-darwin.md index e41aee9bdb24..bdbb3a663f12 100644 --- a/src/doc/rustc/src/platform-support/apple-darwin.md +++ b/src/doc/rustc/src/platform-support/apple-darwin.md @@ -4,9 +4,12 @@ Apple macOS targets. **Tier: 1** -- `x86_64-apple-darwin`: macOS on 64-bit x86. - `aarch64-apple-darwin`: macOS on ARM64 (M1-family or later Apple Silicon CPUs). +**Tier: 2** + +- `x86_64-apple-darwin`: macOS on 64-bit x86. + ## Target maintainers [@thomcc](https://github.com/thomcc) diff --git a/src/doc/rustc/src/platform-support/apple-ios-macabi.md b/src/doc/rustc/src/platform-support/apple-ios-macabi.md index d4b71dbd4f49..c6f68f7a1e81 100644 --- a/src/doc/rustc/src/platform-support/apple-ios-macabi.md +++ b/src/doc/rustc/src/platform-support/apple-ios-macabi.md @@ -56,6 +56,17 @@ Rust programs can be built for these targets by specifying `--target`, if $ rustc --target aarch64-apple-ios-macabi your-code.rs ``` +The target can be differentiated from the iOS targets with the +`target_env = "macabi"` cfg (or `target_abi = "macabi"` before Rust CURRENT_RUSTC_VERSION). + +```rust +if cfg!(target_env = "macabi") { + // Do something only on Mac Catalyst. +} +``` + +This is similar to the `TARGET_OS_MACCATALYST` define in C code. + ## Testing Mac Catalyst binaries can be run directly on macOS 10.15 Catalina or newer. diff --git a/src/doc/rustc/src/platform-support/apple-ios.md b/src/doc/rustc/src/platform-support/apple-ios.md index 64325554ab60..586afa652262 100644 --- a/src/doc/rustc/src/platform-support/apple-ios.md +++ b/src/doc/rustc/src/platform-support/apple-ios.md @@ -66,6 +66,20 @@ Rust programs can be built for these targets by specifying `--target`, if $ rustc --target aarch64-apple-ios your-code.rs ``` +The simulator variants can be differentiated from the variants running +on-device with the `target_env = "sim"` cfg (or `target_abi = "sim"` before +Rust CURRENT_RUSTC_VERSION). + +```rust +if cfg!(all(target_vendor = "apple", target_env = "sim")) { + // Do something on the iOS/tvOS/visionOS/watchOS Simulator. +} { + // Everything else, like Windows and non-Simulator iOS. +} +``` + +This is similar to the `TARGET_OS_SIMULATOR` define in C code. + ## Testing There is no support for running the Rust or standard library testsuite at the diff --git a/src/doc/rustc/src/platform-support/armv7a-vex-v5.md b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md new file mode 100644 index 000000000000..a7da1b16f7e3 --- /dev/null +++ b/src/doc/rustc/src/platform-support/armv7a-vex-v5.md @@ -0,0 +1,83 @@ +# `armv7a-vex-v5` + +**Tier: 3** + +Allows compiling user programs for the [VEX V5 Brain](https://www.vexrobotics.com/276-4810.html), a microcontroller for educational and competitive robotics. + +Rust support for this target is not affiliated with VEX Robotics or IFI. + +## Target maintainers + +This target is maintained by members of the [vexide](https://github.com/vexide) organization: + +- [@lewisfm](https://github.com/lewisfm) +- [@Tropix126](https://github.com/Tropix126) +- [@Gavin-Niederman](https://github.com/Gavin-Niederman) +- [@max-niederman](https://github.com/max-niederman) + +## Requirements + +This target is cross-compiled and currently requires `#![no_std]`. Dynamic linking is unsupported. + +When compiling for this target, the "C" calling convention maps to AAPCS with VFP registers (hard float ABI) and the "system" calling convention maps to AAPCS without VFP registers (soft float ABI). + +This target generates binaries in the ELF format that may uploaded to the brain with external tools. + +## Building the target + +You can build Rust with support for this target by adding it to the `target` list in `bootstrap.toml`, and then running `./x build --target armv7a-vex-v5 compiler`. + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will either need to build Rust with the target enabled (see +"Building the target" above), or build your own copy of `core` by using +`build-std` or similar. + +When the compiler builds a binary, an ELF build artifact will be produced. Additional tools are required for this artifact to be recognizable to VEXos as a user program. + +The [cargo-v5](https://github.com/vexide/cargo-v5) tool is capable of creating binaries that can be uploaded to the V5 brain. This tool wraps the `cargo build` command by supplying arguments necessary to build the target and produce an artifact recognizable to VEXos, while also providing functionality for uploading over USB to a V5 Controller or Brain. + +To install the tool, run: + +```sh +cargo install cargo-v5 +``` + +The following fields in your project's `Cargo.toml` are read by `cargo-v5` to configure upload behavior: + +```toml +[package.metadata.v5] +# Slot number to upload the user program to. This should be from 1-8. +slot = 1 +# Program icon/thumbnail that will be displayed on the dashboard. +icon = "cool-x" +# Use gzip compression when uploading binaries. +compress = true +``` + +To build an uploadable BIN file using the release profile, run: + +```sh +cargo v5 build --release +``` + +Programs can also be directly uploaded to the brain over a USB connection immediately after building: + +```sh +cargo v5 upload --release +``` + +## Testing + +Binaries built for this target can be run in an emulator (such as [vex-v5-qemu](https://github.com/vexide/vex-v5-qemu)), or uploaded to a physical device over a serial (USB) connection. + +The default Rust test runner is not supported. + +The Rust test suite for `library/std` is not yet supported. + +## Cross-compilation toolchains and C code + +This target can be cross-compiled from any host. + +Linking to C libraries is not supported. diff --git a/src/doc/rustc/src/platform-support/lynxos178.md b/src/doc/rustc/src/platform-support/lynxos178.md index 6463f95a0b8e..121e11fb653c 100644 --- a/src/doc/rustc/src/platform-support/lynxos178.md +++ b/src/doc/rustc/src/platform-support/lynxos178.md @@ -15,7 +15,7 @@ Target triples available: ## Target maintainers -- Renat Fatykhov, https://github.com/rfatykhov-lynx +[@rfatykhov-lynx](https://github.com/rfatykhov-lynx) ## Requirements diff --git a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md index 106ec562bfc7..36598982481b 100644 --- a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md +++ b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md @@ -10,6 +10,46 @@ platform. [@RDambrosio016](https://github.com/RDambrosio016) [@kjetilkjeka](https://github.com/kjetilkjeka) +## Requirements + +This target is `no_std` and will typically be built with crate-type `cdylib` and `-C linker-flavor=llbc`, which generates PTX. +The necessary components for this workflow are: + +- `rustup toolchain add nightly` +- `rustup component add llvm-tools --toolchain nightly` +- `rustup component add llvm-bitcode-linker --toolchain nightly` + +There are two options for using the core library: + +- `rustup component add rust-src --toolchain nightly` and build using `-Z build-std=core`. +- `rustup target add nvptx64-nvidia-cuda --toolchain nightly` + +### Target and features + +It is generally necessary to specify the target, such as `-C target-cpu=sm_89`, because the default is very old. This implies two target features: `sm_89` and `ptx78` (and all preceding features within `sm_*` and `ptx*`). Rust will default to using the oldest PTX version that supports the target processor (see [this table](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#release-notes-ptx-release-history)), which maximizes driver compatibility. +One can use `-C target-feature=+ptx80` to choose a later PTX version without changing the target (the default in this case, `ptx78`, requires CUDA driver version 11.8, while `ptx80` would require driver version 12.0). +Later PTX versions may allow more efficient code generation. + +Although Rust follows LLVM in representing `ptx*` and `sm_*` as target features, they should be thought of as having crate granularity, set via (either via `-Ctarget-cpu` and optionally `-Ctarget-feature`). +While the compiler accepts `#[target_feature(enable = "ptx80", enable = "sm_89")]`, it is not supported, may not behave as intended, and may become erroneous in the future. + +## Building Rust kernels + +A `no_std` crate containing one or more functions with `extern "ptx-kernel"` can be compiled to PTX using a command like the following. + +```console +$ RUSTFLAGS='-Ctarget-cpu=sm_89' cargo +nightly rustc --target=nvptx64-nvidia-cuda -Zbuild-std=core --crate-type=cdylib -- -Clinker-flavor=llbc -Zunstable-options +``` + +Intrinsics in `core::arch::nvptx` may use `#[cfg(target_feature = "...")]`, thus it's necessary to use `-Zbuild-std=core` with appropriate `RUSTFLAGS`. The following components are needed for this workflow: + +```console +$ rustup component add rust-src --toolchain nightly +$ rustup component add llvm-tools --toolchain nightly +$ rustup component add llvm-bitcode-linker --toolchain nightly +``` + + + + +
`; + out.insertBefore(hdr, window.searchState.outputElement()); + el = document.getElementsByClassName("search-input")[0]; + } + if (el instanceof HTMLInputElement) { + return el; + } + return null; + }, + containerElement: () => { let el = document.getElementById("search"); if (!el) { el = document.createElement("section"); @@ -285,6 +286,19 @@ function preLoadCss(cssUrl) { } return el; }, + outputElement: () => { + const container = window.searchState.containerElement(); + if (!container) { + return null; + } + let el = container.querySelector(".search-out"); + if (!el) { + el = document.createElement("div"); + el.className = "search-out"; + container.appendChild(el); + } + return el; + }, title: document.title, titleBeforeSearch: document.title, timeout: null, @@ -303,25 +317,52 @@ function preLoadCss(cssUrl) { } }, isDisplayed: () => { - const outputElement = window.searchState.outputElement(); - return !!outputElement && - !!outputElement.parentElement && - outputElement.parentElement.id === ALTERNATIVE_DISPLAY_ID; + const container = window.searchState.containerElement(); + if (!container) { + return false; + } + return !!container.parentElement && container.parentElement.id === + ALTERNATIVE_DISPLAY_ID; }, // Sets the focus on the search bar at the top of the page focus: () => { - window.searchState.input && window.searchState.input.focus(); + const inputElement = window.searchState.inputElement(); + window.searchState.showResults(); + if (inputElement) { + inputElement.focus(); + // Avoid glitch if something focuses the search button after clicking. + requestAnimationFrame(() => inputElement.focus()); + } }, // Removes the focus from the search bar. defocus: () => { - window.searchState.input && window.searchState.input.blur(); + nonnull(window.searchState.inputElement()).blur(); }, - showResults: search => { - if (search === null || typeof search === "undefined") { - search = window.searchState.outputElement(); + toggle: () => { + if (window.searchState.isDisplayed()) { + window.searchState.defocus(); + window.searchState.hideResults(); + } else { + window.searchState.focus(); } - switchDisplayedElement(search); + }, + showResults: () => { document.title = window.searchState.title; + if (window.searchState.isDisplayed()) { + return; + } + const search = window.searchState.containerElement(); + switchDisplayedElement(search); + const btn = document.querySelector("#search-button a"); + if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement && + window.searchState.getQueryStringParams().search === undefined + ) { + history.pushState(null, "", btn.href); + } + const btnLabel = document.querySelector("#search-button a span.label"); + if (btnLabel) { + btnLabel.innerHTML = "Exit"; + } }, removeQueryParameters: () => { // We change the document title. @@ -334,6 +375,10 @@ function preLoadCss(cssUrl) { switchDisplayedElement(null); // We also remove the query parameter from the URL. window.searchState.removeQueryParameters(); + const btnLabel = document.querySelector("#search-button a span.label"); + if (btnLabel) { + btnLabel.innerHTML = "Search"; + } }, getQueryStringParams: () => { /** @type {Object.} */ @@ -348,11 +393,11 @@ function preLoadCss(cssUrl) { return params; }, setup: () => { - const search_input = window.searchState.input; + let searchLoaded = false; + const search_input = window.searchState.inputElement(); if (!search_input) { return; } - let searchLoaded = false; // If you're browsing the nightly docs, the page might need to be refreshed for the // search to work because the hash of the JS scripts might have changed. function sendSearchForm() { @@ -363,21 +408,102 @@ function preLoadCss(cssUrl) { if (!searchLoaded) { searchLoaded = true; // @ts-expect-error - loadScript(getVar("static-root-path") + getVar("search-js"), sendSearchForm); - loadScript(resourcePath("search-index", ".js"), sendSearchForm); + window.rr_ = data => { + // @ts-expect-error + window.searchIndex = data; + }; + if (!window.StringdexOnload) { + window.StringdexOnload = []; + } + window.StringdexOnload.push(() => { + loadScript( + // @ts-expect-error + getVar("static-root-path") + getVar("search-js"), + sendSearchForm, + ); + }); + // @ts-expect-error + loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm); + loadScript(resourcePath("search.index/root", ".js"), sendSearchForm); } } search_input.addEventListener("focus", () => { - window.searchState.origPlaceholder = search_input.placeholder; - search_input.placeholder = "Type your search here."; loadSearch(); }); - if (search_input.value !== "") { - loadSearch(); + const btn = document.getElementById("search-button"); + if (btn) { + btn.onclick = event => { + if (event.ctrlKey || event.altKey || event.metaKey) { + return; + } + event.preventDefault(); + window.searchState.toggle(); + loadSearch(); + }; + } + + // Push and pop states are used to add search results to the browser + // history. + if (browserSupportsHistoryApi()) { + // Store the previous so we can revert back to it later. + const previousTitle = document.title; + + window.addEventListener("popstate", e => { + const params = window.searchState.getQueryStringParams(); + // Revert to the previous title manually since the History + // API ignores the title parameter. + document.title = previousTitle; + // Synchronize search bar with query string state and + // perform the search. This will empty the bar if there's + // nothing there, which lets you really go back to a + // previous state with nothing in the bar. + const inputElement = window.searchState.inputElement(); + if (params.search !== undefined && inputElement !== null) { + loadSearch(); + inputElement.value = params.search; + // Some browsers fire "onpopstate" for every page load + // (Chrome), while others fire the event only when actually + // popping a state (Firefox), which is why search() is + // called both here and at the end of the startSearch() + // function. + e.preventDefault(); + window.searchState.showResults(); + if (params.search === "") { + window.searchState.focus(); + } + } else { + // When browsing back from search results the main page + // visibility must be reset. + window.searchState.hideResults(); + } + }); } + // This is required in firefox to avoid this problem: Navigating to a search result + // with the keyboard, hitting enter, and then hitting back would take you back to + // the doc page, rather than the search that should overlay it. + // This was an interaction between the back-forward cache and our handlers + // that try to sync state between the URL and the search input. To work around it, + // do a small amount of re-init on page show. + window.onpageshow = () => { + const inputElement = window.searchState.inputElement(); + const qSearch = window.searchState.getQueryStringParams().search; + if (qSearch !== undefined && inputElement !== null) { + if (inputElement.value === "") { + inputElement.value = qSearch; + } + window.searchState.showResults(); + if (qSearch === "") { + loadSearch(); + window.searchState.focus(); + } + } else { + window.searchState.hideResults(); + } + }; + const params = window.searchState.getQueryStringParams(); if (params.search !== undefined) { window.searchState.setLoadingSearch(); @@ -386,13 +512,9 @@ function preLoadCss(cssUrl) { }, setLoadingSearch: () => { const search = window.searchState.outputElement(); - if (!search) { - return; - } - search.innerHTML = "<h3 class=\"search-loading\">" + - window.searchState.loadingText + - "</h3>"; - window.searchState.showResults(search); + nonnull(search).innerHTML = "<h3 class=\"search-loading\">" + + window.searchState.loadingText + "</h3>"; + window.searchState.showResults(); }, descShards: new Map(), loadDesc: async function({descShard, descIndex}) { @@ -1500,15 +1622,13 @@ function preLoadCss(cssUrl) { // @ts-expect-error function helpBlurHandler(event) { - // @ts-expect-error - if (!getHelpButton().contains(document.activeElement) && - // @ts-expect-error - !getHelpButton().contains(event.relatedTarget) && - // @ts-expect-error - !getSettingsButton().contains(document.activeElement) && - // @ts-expect-error - !getSettingsButton().contains(event.relatedTarget) - ) { + const isInPopover = onEachLazy( + document.querySelectorAll(".settings-menu, .help-menu"), + menu => { + return menu.contains(document.activeElement) || menu.contains(event.relatedTarget); + }, + ); + if (!isInPopover) { window.hidePopoverMenus(); } } @@ -1529,7 +1649,7 @@ function preLoadCss(cssUrl) { ["⏎", "Go to active search result"], ["+", "Expand all sections"], ["-", "Collapse all sections"], - // for the sake of brevity, we don't say "inherint impl blocks", + // for the sake of brevity, we don't say "inherit impl blocks", // although that would be more correct, // since trait impl blocks are collapsed by - ["_", "Collapse all sections, including impl blocks"], @@ -1571,10 +1691,9 @@ function preLoadCss(cssUrl) { const container = document.createElement("div"); if (!isHelpPage) { - container.className = "popover"; + container.className = "popover content"; } container.id = "help"; - container.style.display = "none"; const side_by_side = document.createElement("div"); side_by_side.className = "side-by-side"; @@ -1590,17 +1709,16 @@ function preLoadCss(cssUrl) { help_section.appendChild(container); // @ts-expect-error document.getElementById("main-content").appendChild(help_section); - container.style.display = "block"; } else { - const help_button = getHelpButton(); - // @ts-expect-error - help_button.appendChild(container); - - container.onblur = helpBlurHandler; - // @ts-expect-error - help_button.onblur = helpBlurHandler; - // @ts-expect-error - help_button.children[0].onblur = helpBlurHandler; + onEachLazy(document.getElementsByClassName("help-menu"), menu => { + if (menu.offsetWidth !== 0) { + menu.appendChild(container); + container.onblur = helpBlurHandler; + menu.onblur = helpBlurHandler; + menu.children[0].onblur = helpBlurHandler; + return true; + } + }); } return container; @@ -1621,80 +1739,57 @@ function preLoadCss(cssUrl) { * Hide all the popover menus. */ window.hidePopoverMenus = () => { - onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"), elem => { + onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => { elem.style.display = "none"; }); - const button = getHelpButton(); - if (button) { - removeClass(button, "help-open"); - } + onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => { + elem.parentElement.removeChild(elem); + }); }; - /** - * Returns the help menu element (not the button). - * - * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be - * built if it doesn't exist. - * - * @return {HTMLElement} - */ - function getHelpMenu(buildNeeded) { - // @ts-expect-error - let menu = getHelpButton().querySelector(".popover"); - if (!menu && buildNeeded) { - menu = buildHelpMenu(); - } - // @ts-expect-error - return menu; - } - /** * Show the help popup menu. */ function showHelp() { + window.hideAllModals(false); // Prevent `blur` events from being dispatched as a result of closing // other modals. - const button = getHelpButton(); - addClass(button, "help-open"); - // @ts-expect-error - button.querySelector("a").focus(); - const menu = getHelpMenu(true); - if (menu.style.display === "none") { - // @ts-expect-error - window.hideAllModals(); - menu.style.display = ""; - } + onEachLazy(document.querySelectorAll(".help-menu a"), menu => { + if (menu.offsetWidth !== 0) { + menu.focus(); + return true; + } + }); + buildHelpMenu(); } - const helpLink = document.querySelector(`#${HELP_BUTTON_ID} > a`); if (isHelpPage) { buildHelpMenu(); - } else if (helpLink) { - helpLink.addEventListener("click", event => { - // By default, have help button open docs in a popover. - // If user clicks with a moderator, though, use default browser behavior, - // probably opening in a new window or tab. - if (!helpLink.contains(helpLink) || - // @ts-expect-error - event.ctrlKey || - // @ts-expect-error - event.altKey || - // @ts-expect-error - event.metaKey) { - return; - } - event.preventDefault(); - const menu = getHelpMenu(true); - const shouldShowHelp = menu.style.display === "none"; - if (shouldShowHelp) { - showHelp(); - } else { - window.hidePopoverMenus(); - } + } else { + onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => { + helpLink.addEventListener( + "click", + /** @param {MouseEvent} event */ + event => { + // By default, have help button open docs in a popover. + // If user clicks with a moderator, though, use default browser behavior, + // probably opening in a new window or tab. + if (event.ctrlKey || + event.altKey || + event.metaKey) { + return; + } + event.preventDefault(); + if (document.getElementById("help")) { + window.hidePopoverMenus(); + } else { + showHelp(); + } + }, + ); }); } - setMobileTopbar(); addSidebarItems(); addSidebarCrates(); onHashChange(null); @@ -1746,7 +1841,15 @@ function preLoadCss(cssUrl) { // On larger, "desktop-sized" viewports (though that includes many // tablets), it's fixed-position, appears in the left side margin, // and it can be activated by resizing the sidebar into nothing. - const sidebarButton = document.getElementById("sidebar-button"); + let sidebarButton = document.getElementById("sidebar-button"); + const body = document.querySelector(".main-heading"); + if (!sidebarButton && body) { + sidebarButton = document.createElement("div"); + sidebarButton.id = "sidebar-button"; + const path = `${window.rootPath}${window.currentCrate}/all.html`; + sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`; + body.insertBefore(sidebarButton, body.firstChild); + } if (sidebarButton) { sidebarButton.addEventListener("click", e => { removeClass(document.documentElement, "hide-sidebar"); diff --git a/src/librustdoc/html/static/js/rustdoc.d.ts b/src/librustdoc/html/static/js/rustdoc.d.ts index a95897645478..28852125fe17 100644 --- a/src/librustdoc/html/static/js/rustdoc.d.ts +++ b/src/librustdoc/html/static/js/rustdoc.d.ts @@ -2,6 +2,8 @@ // not put into the JavaScript we include as part of the documentation. It is used for // type checking. See README.md in this directory for more info. +import { RoaringBitmap } from "./stringdex"; + /* eslint-disable */ declare global { /** Search engine data used by main.js and search.js */ @@ -10,6 +12,17 @@ declare global { declare function nonnull(x: T|null, msg: string|undefined); /** Defined and documented in `storage.js` */ declare function nonundef(x: T|undefined, msg: string|undefined); + interface PromiseConstructor { + /** + * Polyfill + * @template T + */ + withResolvers: function(): { + "promise": Promise<T>, + "resolve": (function(T): void), + "reject": (function(any): void) + }; + } interface Window { /** Make the current theme easy to find */ currentTheme: HTMLLinkElement|null; @@ -95,29 +108,28 @@ declare namespace rustdoc { interface SearchState { rustdocToolbar: HTMLElement|null; loadingText: string; - input: HTMLInputElement|null; + inputElement: function(): HTMLInputElement|null; + containerElement: function(): Element|null; title: string; titleBeforeSearch: string; - timeout: number|null; + timeout: ReturnType<typeof setTimeout>|null; currentTab: number; - focusedByTab: [number|null, number|null, number|null]; + focusedByTab: [Element|null, Element|null, Element|null]; clearInputTimeout: function; - outputElement(): HTMLElement|null; - focus(); - defocus(); - // note: an optional param is not the same as - // a nullable/undef-able param. - showResults(elem?: HTMLElement|null); - removeQueryParameters(); - hideResults(); - getQueryStringParams(): Object.<any, string>; - origPlaceholder: string; + outputElement: function(): Element|null; + focus: function(); + defocus: function(); + toggle: function(); + showResults: function(); + removeQueryParameters: function(); + hideResults: function(); + getQueryStringParams: function(): Object.<any, string>; setup: function(); setLoadingSearch(); descShards: Map<string, SearchDescShard[]>; loadDesc: function({descShard: SearchDescShard, descIndex: number}): Promise<string|null>; - loadedDescShard(string, number, string); - isDisplayed(): boolean, + loadedDescShard: function(string, number, string); + isDisplayed: function(): boolean; } interface SearchDescShard { @@ -129,14 +141,15 @@ declare namespace rustdoc { /** * A single parsed "atom" in a search query. For example, - * + * * std::fmt::Formatter, Write -> Result<()> - * ┏━━━━━━━━━━━━━━━━━━ ┌──── ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ - * ┃ │ ┗ QueryElement { ┊ - * ┃ │ name: Result ┊ - * ┃ │ generics: [ ┊ - * ┃ │ QueryElement ┘ - * ┃ │ name: () + * ┏━━━━━━━━━━━━━━━━━━ ┌──── ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ + * ┃ │ ┗ QueryElement { ┊ + * ┃ │ name: Result ┊ + * ┃ │ generics: [ ┊ + * ┃ │ QueryElement { ┘ + * ┃ │ name: () + * ┃ │ } * ┃ │ ] * ┃ │ } * ┃ └ QueryElement { @@ -156,14 +169,14 @@ declare namespace rustdoc { normalizedPathLast: string, generics: Array<QueryElement>, bindings: Map<number, Array<QueryElement>>, - typeFilter: number|null, + typeFilter: number, } /** * Same as QueryElement, but bindings and typeFilter support strings */ interface ParserQueryElement { - name: string|null, + name: string, id: number|null, fullPath: Array<string>, pathWithoutLast: Array<string>, @@ -172,7 +185,7 @@ declare namespace rustdoc { generics: Array<ParserQueryElement>, bindings: Map<string, Array<ParserQueryElement>>, bindingName: {name: string|null, generics: ParserQueryElement[]}|null, - typeFilter: number|string|null, + typeFilter: string|null, } /** @@ -215,35 +228,74 @@ declare namespace rustdoc { /** * An entry in the search index database. */ + interface EntryData { + krate: number, + ty: ItemType, + modulePath: number?, + exactModulePath: number?, + parent: number?, + deprecated: boolean, + associatedItemDisambiguator: string?, + } + + /** + * A path in the search index database + */ + interface PathData { + ty: ItemType, + modulePath: string, + exactModulePath: string?, + } + + /** + * A function signature in the search index database + * + * Note that some non-function items (eg. constants, struct fields) have a function signature so they can appear in type-based search. + */ + interface FunctionData { + functionSignature: FunctionSearchType|null, + paramNames: string[], + elemCount: number, + } + + /** + * A function signature in the search index database + */ + interface TypeData { + searchUnbox: boolean, + invertedFunctionSignatureIndex: RoaringBitmap[], + } + + /** + * A search entry of some sort. + */ interface Row { - crate: string, - descShard: SearchDescShard, id: number, - // This is the name of the item. For doc aliases, if you want the name of the aliased - // item, take a look at `Row.original.name`. + crate: string, + ty: ItemType, name: string, normalizedName: string, - word: string, - paramNames: string[], - parent: ({ty: number, name: string, path: string, exactPath: string}|null|undefined), - path: string, - ty: number, - type: FunctionSearchType | null, - descIndex: number, - bitIndex: number, - implDisambiguator: String | null, - is_alias?: boolean, - original?: Row, + modulePath: string, + exactModulePath: string, + entry: EntryData?, + path: PathData?, + type: FunctionData?, + deprecated: boolean, + parent: { path: PathData, name: string}?, } + type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | + 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | + 21 | 22 | 23 | 24 | 25 | 26; + /** * The viewmodel for the search engine results page. */ interface ResultsTable { - in_args: Array<ResultObject>, - returned: Array<ResultObject>, - others: Array<ResultObject>, - query: ParsedQuery, + in_args: AsyncGenerator<ResultObject>, + returned: AsyncGenerator<ResultObject>, + others: AsyncGenerator<ResultObject>, + query: ParsedQuery<rustdoc.ParserQueryElement>, } type Results = { max_dist?: number } & Map<number, ResultObject> @@ -252,25 +304,41 @@ declare namespace rustdoc { * An annotated `Row`, used in the viewmodel. */ interface ResultObject { - desc: string, + desc: Promise<string|null>, displayPath: string, fullPath: string, href: string, id: number, dist: number, path_dist: number, - name: string, - normalizedName: string, - word: string, index: number, - parent: (Object|undefined), - path: string, - ty: number, + parent: ({ + path: string, + exactPath: string, + name: string, + ty: number, + }|undefined), type?: FunctionSearchType, paramNames?: string[], displayTypeSignature: Promise<rustdoc.DisplayTypeSignature> | null, item: Row, - dontValidate?: boolean, + is_alias: boolean, + alias?: string, + } + + /** + * An annotated `Row`, used in the viewmodel. + */ + interface PlainResultObject { + id: number, + dist: number, + path_dist: number, + index: number, + elems: rustdoc.QueryElement[], + returned: rustdoc.QueryElement[], + is_alias: boolean, + alias?: string, + original?: rustdoc.Rlow, } /** @@ -364,7 +432,19 @@ declare namespace rustdoc { * Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null` * because `null` is four bytes while `0` is one byte. */ - type RawFunctionType = number | [number, Array<RawFunctionType>]; + type RawFunctionType = number | [number, Array<RawFunctionType>] | [number, Array<RawFunctionType>, Array<[RawFunctionType, RawFunctionType[]]>]; + + /** + * Utility typedef for deserializing compact JSON. + * + * R is the required part, O is the optional part, which goes afterward. + * For example, `ArrayWithOptionals<[A, B], [C, D]>` matches + * `[A, B] | [A, B, C] | [A, B, C, D]`. + */ + type ArrayWithOptionals<R extends any[], O extends any[]> = + O extends [infer First, ...infer Rest] ? + R | ArrayWithOptionals<[...R, First], Rest> : + R; /** * The type signature entry in the decoded search index. @@ -382,8 +462,8 @@ declare namespace rustdoc { */ interface FunctionType { id: null|number, - ty: number|null, - name?: string, + ty: ItemType, + name: string|null, path: string|null, exactPath: string|null, unboxFlag: boolean, @@ -403,70 +483,6 @@ declare namespace rustdoc { bindings: Map<number, FingerprintableType[]>; }; - /** - * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f` - * are arrays with the same length. `q`, `a`, and `c` use a sparse - * representation for compactness. - * - * `n[i]` contains the name of an item. - * - * `t[i]` contains the type of that item - * (as a string of characters that represent an offset in `itemTypes`). - * - * `d[i]` contains the description of that item. - * - * `q` contains the full paths of the items. For compactness, it is a set of - * (index, path) pairs used to create a map. If a given index `i` is - * not present, this indicates "same as the last index present". - * - * `i[i]` contains an item's parent, usually a module. For compactness, - * it is a set of indexes into the `p` array. - * - * `f` contains function signatures, or `0` if the item isn't a function. - * More information on how they're encoded can be found in rustc-dev-guide - * - * Functions are themselves encoded as arrays. The first item is a list of - * types representing the function's inputs, and the second list item is a list - * of types representing the function's output. Tuples are flattened. - * Types are also represented as arrays; the first item is an index into the `p` - * array, while the second is a list of types representing any generic parameters. - * - * b[i] contains an item's impl disambiguator. This is only present if an item - * is defined in an impl block and, the impl block's type has more than one associated - * item with the same name. - * - * `a` defines aliases with an Array of pairs: [name, offset], where `offset` - * points into the n/t/d/q/i/f arrays. - * - * `doc` contains the description of the crate. - * - * `p` is a list of path/type pairs. It is used for parents and function parameters. - * The first item is the type, the second is the name, the third is the visible path (if any) and - * the fourth is the canonical path used for deduplication (if any). - * - * `r` is the canonical path used for deduplication of re-exported items. - * It is not used for associated items like methods (that's the fourth element - * of `p`) but is used for modules items like free functions. - * - * `c` is an array of item indices that are deprecated. - */ - type RawSearchIndexCrate = { - doc: string, - a: { [key: string]: number[] }, - n: Array<string>, - t: string, - D: string, - e: string, - q: Array<[number, string]>, - i: string, - f: string, - p: Array<[number, string] | [number, string, number] | [number, string, number, number] | [number, string, number, number, string]>, - b: Array<[number, String]>, - c: string, - r: Array<[number, number]>, - P: Array<[number, string]>, - }; - type VlqData = VlqData[] | number; /** @@ -498,4 +514,13 @@ declare namespace rustdoc { options?: string[], default: string | boolean, } + + /** + * Single element in the data-locs field of a scraped example. + * First field is the start and end char index, + * other fields seem to be unused. + * + * Generated by `render_call_locations` in `render/mod.rs`. + */ + type ScrapedLoc = [[number, number], string, string] } diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js index d641405c8753..eeab591bcd80 100644 --- a/src/librustdoc/html/static/js/scrape-examples.js +++ b/src/librustdoc/html/static/js/scrape-examples.js @@ -1,7 +1,4 @@ -/* global addClass, hasClass, removeClass, onEachLazy */ - -// Eventually fix this. -// @ts-nocheck + /* global addClass, hasClass, removeClass, onEachLazy, nonnull */ "use strict"; @@ -14,8 +11,16 @@ const DEFAULT_MAX_LINES = 5; const HIDDEN_MAX_LINES = 10; - // Scroll code block to the given code location + /** + * Scroll code block to the given code location + * @param {HTMLElement} elt + * @param {[number, number]} loc + * @param {boolean} isHidden + */ function scrollToLoc(elt, loc, isHidden) { + /** @type {HTMLElement[]} */ + // blocked on https://github.com/microsoft/TypeScript/issues/29037 + // @ts-expect-error const lines = elt.querySelectorAll("[data-nosnippet]"); let scrollOffset; @@ -35,10 +40,15 @@ scrollOffset = offsetMid - halfHeight; } - lines[0].parentElement.scrollTo(0, scrollOffset); - elt.querySelector(".rust").scrollTo(0, scrollOffset); + nonnull(lines[0].parentElement).scrollTo(0, scrollOffset); + nonnull(elt.querySelector(".rust")).scrollTo(0, scrollOffset); } + /** + * @param {HTMLElement} parent + * @param {string} className + * @param {string} content + */ function createScrapeButton(parent, className, content) { const button = document.createElement("button"); button.className = className; @@ -50,20 +60,24 @@ window.updateScrapedExample = (example, buttonHolder) => { let locIndex = 0; const highlights = Array.prototype.slice.call(example.querySelectorAll(".highlight")); - const link = example.querySelector(".scraped-example-title a"); + + /** @type {HTMLAnchorElement} */ + const link = nonnull(example.querySelector(".scraped-example-title a")); let expandButton = null; if (!example.classList.contains("expanded")) { expandButton = createScrapeButton(buttonHolder, "expand", "Show all"); } - const isHidden = example.parentElement.classList.contains("more-scraped-examples"); + const isHidden = nonnull(example.parentElement).classList.contains("more-scraped-examples"); + // @ts-expect-error const locs = example.locs; if (locs.length > 1) { const next = createScrapeButton(buttonHolder, "next", "Next usage"); const prev = createScrapeButton(buttonHolder, "prev", "Previous usage"); // Toggle through list of examples in a given file + /** @type {function(function(): void): void} */ const onChangeLoc = changeIndex => { removeClass(highlights[locIndex], "focus"); changeIndex(); @@ -106,10 +120,19 @@ } }; + /** + * Initialize the `locs` field + * + * @param {HTMLElement & {locs?: rustdoc.ScrapedLoc[]}} example + * @param {boolean} isHidden + */ function setupLoc(example, isHidden) { - example.locs = JSON.parse(example.attributes.getNamedItem("data-locs").textContent); + const locs_str = nonnull(example.attributes.getNamedItem("data-locs")).textContent; + const locs = + JSON.parse(nonnull(nonnull(locs_str))); + example.locs = locs; // Start with the first example in view - scrollToLoc(example, example.locs[0][0], isHidden); + scrollToLoc(example, locs[0][0], isHidden); } const firstExamples = document.querySelectorAll(".scraped-example-list > .scraped-example"); diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 2caf214ff73d..42b87d562529 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1,9 +1,16 @@ // ignore-tidy-filelength -/* global addClass, getNakedUrl, getSettingValue, getVar */ -/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */ +/* global addClass, getNakedUrl, getVar, nonnull, getSettingValue */ +/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi */ "use strict"; +/** + * @param {stringdex.Stringdex} Stringdex + * @param {typeof stringdex.RoaringBitmap} RoaringBitmap + * @param {stringdex.Hooks} hooks + */ +const initSearch = async function(Stringdex, RoaringBitmap, hooks) { + // polyfill // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced if (!Array.prototype.toSpliced) { @@ -20,31 +27,65 @@ if (!Array.prototype.toSpliced) { * * @template T * @param {Iterable<T>} arr - * @param {function(T): any} func + * @param {function(T): Promise<any>} func * @param {function(T): boolean} funcBtwn */ -function onEachBtwn(arr, func, funcBtwn) { +async function onEachBtwnAsync(arr, func, funcBtwn) { let skipped = true; for (const value of arr) { if (!skipped) { funcBtwn(value); } - skipped = func(value); + skipped = await func(value); } } /** - * Convert any `undefined` to `null`. - * - * @template T - * @param {T|undefined} x - * @returns {T|null} + * Allow the browser to redraw. + * @returns {Promise<void>} */ -function undef2null(x) { - if (x !== undefined) { - return x; - } - return null; +const yieldToBrowser = typeof window !== "undefined" && window.requestIdleCallback ? + function() { + return new Promise((resolve, _reject) => { + window.requestIdleCallback(resolve); + }); + } : + function() { + return new Promise((resolve, _reject) => { + setTimeout(resolve, 0); + }); + }; + +/** + * Promise-based timer wrapper. + * @param {number} ms + * @returns {Promise<void>} + */ +const timeout = function(ms) { + return new Promise((resolve, _reject) => { + setTimeout(resolve, ms); + }); +}; + +if (!Promise.withResolvers) { + /** + * Polyfill + * @template T + * @returns {{ + "promise": Promise<T>, + "resolve": (function(T): void), + "reject": (function(any): void) + }} + */ + Promise.withResolvers = () => { + let resolve, reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + // @ts-expect-error + return {promise, resolve, reject}; + }; } // ==================== Core search logic begin ==================== @@ -81,13 +122,22 @@ const itemTypes = [ ]; // used for special search precedence -const TY_PRIMITIVE = itemTypes.indexOf("primitive"); -const TY_GENERIC = itemTypes.indexOf("generic"); -const TY_IMPORT = itemTypes.indexOf("import"); -const TY_TRAIT = itemTypes.indexOf("trait"); -const TY_FN = itemTypes.indexOf("fn"); -const TY_METHOD = itemTypes.indexOf("method"); -const TY_TYMETHOD = itemTypes.indexOf("tymethod"); +/** @type {rustdoc.ItemType} */ +const TY_PRIMITIVE = 1; +/** @type {rustdoc.ItemType} */ +const TY_GENERIC = 26; +/** @type {rustdoc.ItemType} */ +const TY_IMPORT = 4; +/** @type {rustdoc.ItemType} */ +const TY_TRAIT = 10; +/** @type {rustdoc.ItemType} */ +const TY_FN = 7; +/** @type {rustdoc.ItemType} */ +const TY_METHOD = 13; +/** @type {rustdoc.ItemType} */ +const TY_TYMETHOD = 12; +/** @type {rustdoc.ItemType} */ +const TY_ASSOCTYPE = 17; const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../"; // Hard limit on how deep to recurse into generics when doing type-driven search. @@ -242,7 +292,9 @@ function isEndCharacter(c) { } /** - * @param {number} ty + * Same thing as ItemType::is_fn_like in item_type.rs + * + * @param {rustdoc.ItemType} ty * @returns */ function isFnLikeTy(ty) { @@ -1023,6 +1075,7 @@ class VlqHexDecoder { this.string = string; this.cons = cons; this.offset = 0; + this.elemCount = 0; /** @type {T[]} */ this.backrefQueue = []; } @@ -1060,6 +1113,7 @@ class VlqHexDecoder { n = (n << 4) | (c & 0xF); const [sign, value] = [n & 1, n >> 1]; this.offset += 1; + this.elemCount += 1; return sign ? -value : value; } /** @@ -1086,1303 +1140,159 @@ class VlqHexDecoder { return result; } } -class RoaringBitmap { - /** @param {string} str */ - constructor(str) { - // https://github.com/RoaringBitmap/RoaringFormatSpec - // - // Roaring bitmaps are used for flags that can be kept in their - // compressed form, even when loaded into memory. This decoder - // turns the containers into objects, but uses byte array - // slices of the original format for the data payload. - const strdecoded = atob(str); - const u8array = new Uint8Array(strdecoded.length); - for (let j = 0; j < strdecoded.length; ++j) { - u8array[j] = strdecoded.charCodeAt(j); - } - const has_runs = u8array[0] === 0x3b; - const size = has_runs ? - ((u8array[2] | (u8array[3] << 8)) + 1) : - ((u8array[4] | (u8array[5] << 8) | (u8array[6] << 16) | (u8array[7] << 24))); - let i = has_runs ? 4 : 8; - let is_run; - if (has_runs) { - const is_run_len = Math.floor((size + 7) / 8); - is_run = u8array.slice(i, i + is_run_len); - i += is_run_len; - } else { - is_run = new Uint8Array(); - } - this.keys = []; - this.cardinalities = []; - for (let j = 0; j < size; ++j) { - this.keys.push(u8array[i] | (u8array[i + 1] << 8)); - i += 2; - this.cardinalities.push((u8array[i] | (u8array[i + 1] << 8)) + 1); - i += 2; - } - this.containers = []; - let offsets = null; - if (!has_runs || this.keys.length >= 4) { - offsets = []; - for (let j = 0; j < size; ++j) { - offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) | - (u8array[i + 3] << 24)); - i += 4; - } - } - for (let j = 0; j < size; ++j) { - if (offsets && offsets[j] !== i) { - // eslint-disable-next-line no-console - console.log(this.containers); - throw new Error(`corrupt bitmap ${j}: ${i} / ${offsets[j]}`); - } - if (is_run[j >> 3] & (1 << (j & 0x7))) { - const runcount = (u8array[i] | (u8array[i + 1] << 8)); - i += 2; - this.containers.push(new RoaringBitmapRun( - runcount, - u8array.slice(i, i + (runcount * 4)), - )); - i += runcount * 4; - } else if (this.cardinalities[j] >= 4096) { - this.containers.push(new RoaringBitmapBits(u8array.slice(i, i + 8192))); - i += 8192; - } else { - const end = this.cardinalities[j] * 2; - this.containers.push(new RoaringBitmapArray( - this.cardinalities[j], - u8array.slice(i, i + end), - )); - i += end; - } - } - } - /** @param {number} keyvalue */ - contains(keyvalue) { - const key = keyvalue >> 16; - const value = keyvalue & 0xFFFF; - // Binary search algorithm copied from - // https://en.wikipedia.org/wiki/Binary_search#Procedure - // - // Format is required by specification to be sorted. - // Because keys are 16 bits and unique, length can't be - // bigger than 2**16, and because we have 32 bits of safe int, - // left + right can't overflow. - let left = 0; - let right = this.keys.length - 1; - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const x = this.keys[mid]; - if (x < key) { - left = mid + 1; - } else if (x > key) { - right = mid - 1; - } else { - return this.containers[mid].contains(value); - } - } - return false; - } -} -class RoaringBitmapRun { - /** - * @param {number} runcount - * @param {Uint8Array} array - */ - constructor(runcount, array) { - this.runcount = runcount; - this.array = array; - } - /** @param {number} value */ - contains(value) { - // Binary search algorithm copied from - // https://en.wikipedia.org/wiki/Binary_search#Procedure - // - // Since runcount is stored as 16 bits, left + right - // can't overflow. - let left = 0; - let right = this.runcount - 1; - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const i = mid * 4; - const start = this.array[i] | (this.array[i + 1] << 8); - const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8); - if ((start + lenm1) < value) { - left = mid + 1; - } else if (start > value) { - right = mid - 1; - } else { - return true; - } - } - return false; - } -} -class RoaringBitmapArray { - /** - * @param {number} cardinality - * @param {Uint8Array} array - */ - constructor(cardinality, array) { - this.cardinality = cardinality; - this.array = array; - } - /** @param {number} value */ - contains(value) { - // Binary search algorithm copied from - // https://en.wikipedia.org/wiki/Binary_search#Procedure - // - // Since cardinality can't be higher than 4096, left + right - // cannot overflow. - let left = 0; - let right = this.cardinality - 1; - while (left <= right) { - const mid = Math.floor((left + right) / 2); - const i = mid * 2; - const x = this.array[i] | (this.array[i + 1] << 8); - if (x < value) { - left = mid + 1; - } else if (x > value) { - right = mid - 1; - } else { - return true; - } - } - return false; - } -} -class RoaringBitmapBits { - /** - * @param {Uint8Array} array - */ - constructor(array) { - this.array = array; - } - /** @param {number} value */ - contains(value) { - return !!(this.array[value >> 3] & (1 << (value & 7))); - } -} +/** @type {Array<string>} */ +const EMPTY_STRING_ARRAY = []; + +/** @type {Array<rustdoc.FunctionType>} */ +const EMPTY_GENERICS_ARRAY = []; + +/** @type {Array<[number, rustdoc.FunctionType[]]>} */ +const EMPTY_BINDINGS_ARRAY = []; + +/** @type {Map<number, Array<any>>} */ +const EMPTY_BINDINGS_MAP = new Map(); /** - * A prefix tree, used for name-based search. - * - * This data structure is used to drive prefix matches, - * such as matching the query "link" to `LinkedList`, - * and Lev-distance matches, such as matching the - * query "hahsmap" to `HashMap`. Substring matches, - * such as "list" to `LinkedList`, are done with a - * tailTable that deep-links into this trie. - * - * children - * : A [sparse array] of subtrees. The array index - * is a charCode. - * - * [sparse array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/ - * Indexed_collections#sparse_arrays - * - * matches - * : A list of search index IDs for this node. - * - * @type {{ - * children: NameTrie[], - * matches: number[], - * }} + * @param {string|null} typename + * @returns {number} */ -class NameTrie { - constructor() { - this.children = []; - this.matches = []; - } - /** - * @param {string} name - * @param {number} id - * @param {Map<string, NameTrie[]>} tailTable - */ - insert(name, id, tailTable) { - this.insertSubstring(name, 0, id, tailTable); - } - /** - * @param {string} name - * @param {number} substart - * @param {number} id - * @param {Map<string, NameTrie[]>} tailTable - */ - insertSubstring(name, substart, id, tailTable) { - const l = name.length; - if (substart === l) { - this.matches.push(id); - } else { - const sb = name.charCodeAt(substart); - let child; - if (this.children[sb] !== undefined) { - child = this.children[sb]; - } else { - child = new NameTrie(); - this.children[sb] = child; - /** @type {NameTrie[]} */ - let sste; - if (substart >= 2) { - const tail = name.substring(substart - 2, substart + 1); - const entry = tailTable.get(tail); - if (entry !== undefined) { - sste = entry; - } else { - sste = []; - tailTable.set(tail, sste); - } - sste.push(child); - } - } - child.insertSubstring(name, substart + 1, id, tailTable); - } - } - /** - * @param {string} name - * @param {Map<string, NameTrie[]>} tailTable - */ - search(name, tailTable) { - const results = new Set(); - this.searchSubstringPrefix(name, 0, results); - if (results.size < MAX_RESULTS && name.length >= 3) { - const levParams = name.length >= 6 ? - new Lev2TParametricDescription(name.length) : - new Lev1TParametricDescription(name.length); - this.searchLev(name, 0, levParams, results); - const tail = name.substring(0, 3); - const list = tailTable.get(tail); - if (list !== undefined) { - for (const entry of list) { - entry.searchSubstringPrefix(name, 3, results); - } - } - } - return [...results]; - } - /** - * @param {string} name - * @param {number} substart - * @param {Set<number>} results - */ - searchSubstringPrefix(name, substart, results) { - const l = name.length; - if (substart === l) { - for (const match of this.matches) { - results.add(match); - } - // breadth-first traversal orders prefix matches by length - /** @type {NameTrie[]} */ - let unprocessedChildren = []; - for (const child of this.children) { - if (child) { - unprocessedChildren.push(child); - } - } - /** @type {NameTrie[]} */ - let nextSet = []; - while (unprocessedChildren.length !== 0) { - /** @type {NameTrie} */ - // @ts-expect-error - const next = unprocessedChildren.pop(); - for (const child of next.children) { - if (child) { - nextSet.push(child); - } - } - for (const match of next.matches) { - results.add(match); - } - if (unprocessedChildren.length === 0) { - const tmp = unprocessedChildren; - unprocessedChildren = nextSet; - nextSet = tmp; - } - } - } else { - const sb = name.charCodeAt(substart); - if (this.children[sb] !== undefined) { - this.children[sb].searchSubstringPrefix(name, substart + 1, results); - } - } +function itemTypeFromName(typename) { + if (typename === null) { + return NO_TYPE_FILTER; } - /** - * @param {string} name - * @param {number} substart - * @param {Lev2TParametricDescription|Lev1TParametricDescription} levParams - * @param {Set<number>} results - */ - searchLev(name, substart, levParams, results) { - const stack = [[this, 0]]; - const n = levParams.n; - while (stack.length !== 0) { - // It's not empty - //@ts-expect-error - const [trie, levState] = stack.pop(); - for (const [charCode, child] of trie.children.entries()) { - if (!child) { - continue; - } - const levPos = levParams.getPosition(levState); - const vector = levParams.getVector( - name, - charCode, - levPos, - Math.min(name.length, levPos + (2 * n) + 1), - ); - const newLevState = levParams.transition( - levState, - levPos, - vector, - ); - if (newLevState >= 0) { - stack.push([child, newLevState]); - if (levParams.isAccept(newLevState)) { - for (const match of child.matches) { - results.add(match); - } - } - } - } - } + const index = itemTypes.findIndex(i => i === typename); + if (index < 0) { + throw ["Unknown type filter ", typename]; } + return index; } class DocSearch { /** - * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex * @param {string} rootPath - * @param {rustdoc.SearchState} searchState + * @param {stringdex.Database} database */ - constructor(rawSearchIndex, rootPath, searchState) { - /** - * @type {Map<String, RoaringBitmap>} - */ - this.searchIndexDeprecated = new Map(); - /** - * @type {Map<String, RoaringBitmap>} - */ - this.searchIndexEmptyDesc = new Map(); - /** - * @type {Uint32Array} - */ - this.functionTypeFingerprint = new Uint32Array(0); - /** - * Map from normalized type names to integers. Used to make type search - * more efficient. - * - * @type {Map<string, {id: number, assocOnly: boolean}>} - */ - this.typeNameIdMap = new Map(); - /** - * Map from type ID to associated type name. Used for display, - * not for search. - * - * @type {Map<number, string>} - */ - this.assocTypeIdNameMap = new Map(); - this.ALIASES = new Map(); - this.FOUND_ALIASES = new Set(); + constructor(rootPath, database) { this.rootPath = rootPath; - this.searchState = searchState; - - /** - * Special type name IDs for searching by array. - * @type {number} - */ - this.typeNameIdOfArray = this.buildTypeMapIndex("array"); - /** - * Special type name IDs for searching by slice. - * @type {number} - */ - this.typeNameIdOfSlice = this.buildTypeMapIndex("slice"); - /** - * Special type name IDs for searching by both array and slice (`[]` syntax). - * @type {number} - */ - this.typeNameIdOfArrayOrSlice = this.buildTypeMapIndex("[]"); - /** - * Special type name IDs for searching by tuple. - * @type {number} - */ - this.typeNameIdOfTuple = this.buildTypeMapIndex("tuple"); - /** - * Special type name IDs for searching by unit. - * @type {number} - */ - this.typeNameIdOfUnit = this.buildTypeMapIndex("unit"); - /** - * Special type name IDs for searching by both tuple and unit (`()` syntax). - * @type {number} - */ - this.typeNameIdOfTupleOrUnit = this.buildTypeMapIndex("()"); - /** - * Special type name IDs for searching `fn`. - * @type {number} - */ - this.typeNameIdOfFn = this.buildTypeMapIndex("fn"); - /** - * Special type name IDs for searching `fnmut`. - * @type {number} - */ - this.typeNameIdOfFnMut = this.buildTypeMapIndex("fnmut"); - /** - * Special type name IDs for searching `fnonce`. - * @type {number} - */ - this.typeNameIdOfFnOnce = this.buildTypeMapIndex("fnonce"); - /** - * Special type name IDs for searching higher order functions (`->` syntax). - * @type {number} - */ - this.typeNameIdOfHof = this.buildTypeMapIndex("->"); - /** - * Special type name IDs the output assoc type. - * @type {number} - */ - this.typeNameIdOfOutput = this.buildTypeMapIndex("output", true); - /** - * Special type name IDs for searching by reference. - * @type {number} - */ - this.typeNameIdOfReference = this.buildTypeMapIndex("reference"); + this.database = database; - /** - * Empty, immutable map used in item search types with no bindings. - * - * @type {Map<number, Array<any>>} - */ - this.EMPTY_BINDINGS_MAP = new Map(); + this.typeNameIdOfOutput = -1; + this.typeNameIdOfArray = -1; + this.typeNameIdOfSlice = -1; + this.typeNameIdOfArrayOrSlice = -1; + this.typeNameIdOfTuple = -1; + this.typeNameIdOfUnit = -1; + this.typeNameIdOfTupleOrUnit = -1; + this.typeNameIdOfReference = -1; + this.typeNameIdOfHof = -1; - /** - * Empty, immutable map used in item search types with no bindings. - * - * @type {Array<any>} - */ - this.EMPTY_GENERICS_ARRAY = []; + this.utf8decoder = new TextDecoder(); - /** - * Object pool for function types with no bindings or generics. - * This is reset after loading the index. - * - * @type {Map<number|null, rustdoc.FunctionType>} - */ + /** @type {Map<number|null, rustdoc.FunctionType>} */ this.TYPES_POOL = new Map(); - - /** - * A trie for finding items by name. - * This is used for edit distance and prefix finding. - * - * @type {NameTrie} - */ - this.nameTrie = new NameTrie(); - - /** - * Find items by 3-substring. This is a map from three-char - * prefixes into lists of subtries. - */ - this.tailTable = new Map(); - - /** - * @type {Array<rustdoc.Row>} - */ - this.searchIndex = this.buildIndex(rawSearchIndex); } /** - * Add an item to the type Name->ID map, or, if one already exists, use it. - * Returns the number. If name is "" or null, return null (pure generic). - * - * This is effectively string interning, so that function matching can be - * done more quickly. Two types with the same name but different item kinds - * get the same ID. - * - * @template T extends string - * @overload - * @param {T} name - * @param {boolean=} isAssocType - True if this is an assoc type - * @returns {T extends "" ? null : number} - * - * @param {string} name - * @param {boolean=} isAssocType - * @returns {number | null} - * + * Load search index. If you do not call this function, `execQuery` + * will never fulfill. */ - buildTypeMapIndex(name, isAssocType) { - if (name === "" || name === null) { - return null; - } - - const obj = this.typeNameIdMap.get(name); - if (obj !== undefined) { - obj.assocOnly = !!(isAssocType && obj.assocOnly); - return obj.id; - } else { - const id = this.typeNameIdMap.size; - this.typeNameIdMap.set(name, { id, assocOnly: !!isAssocType }); - return id; + async buildIndex() { + const nn = this.database.getIndex("normalizedName"); + if (!nn) { + return; } + // Each of these identifiers are used specially by + // type-driven search. + const [ + // output is the special associated type that goes + // after the arrow: the type checker desugars + // the path `Fn(a) -> b` into `Fn<Output=b, (a)>` + output, + // fn, fnmut, and fnonce all match `->` + fn, + fnMut, + fnOnce, + hof, + // array and slice both match `[]` + array, + slice, + arrayOrSlice, + // tuple and unit both match `()` + tuple, + unit, + tupleOrUnit, + // reference matches `&` + reference, + // never matches `!` + never, + ] = await Promise.all([ + nn.search("output"), + nn.search("fn"), + nn.search("fnmut"), + nn.search("fnonce"), + nn.search("->"), + nn.search("array"), + nn.search("slice"), + nn.search("[]"), + nn.search("tuple"), + nn.search("unit"), + nn.search("()"), + nn.search("reference"), + nn.search("never"), + ]); + /** + * @param {stringdex.Trie|null|undefined} trie + * @param {rustdoc.ItemType} ty + * @param {string} modulePath + * @returns {Promise<number>} + * */ + const first = async(trie, ty, modulePath) => { + if (trie) { + for (const id of trie.matches().entries()) { + const pathData = await this.getPathData(id); + if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) { + return id; + } + } + } + return -1; + }; + this.typeNameIdOfOutput = await first(output, TY_ASSOCTYPE, ""); + this.typeNameIdOfFnPtr = await first(fn, TY_PRIMITIVE, ""); + this.typeNameIdOfFn = await first(fn, TY_TRAIT, "core::ops"); + this.typeNameIdOfFnMut = await first(fnMut, TY_TRAIT, "core::ops"); + this.typeNameIdOfFnOnce = await first(fnOnce, TY_TRAIT, "core::ops"); + this.typeNameIdOfArray = await first(array, TY_PRIMITIVE, ""); + this.typeNameIdOfSlice = await first(slice, TY_PRIMITIVE, ""); + this.typeNameIdOfArrayOrSlice = await first(arrayOrSlice, TY_PRIMITIVE, ""); + this.typeNameIdOfTuple = await first(tuple, TY_PRIMITIVE, ""); + this.typeNameIdOfUnit = await first(unit, TY_PRIMITIVE, ""); + this.typeNameIdOfTupleOrUnit = await first(tupleOrUnit, TY_PRIMITIVE, ""); + this.typeNameIdOfReference = await first(reference, TY_PRIMITIVE, ""); + this.typeNameIdOfHof = await first(hof, TY_PRIMITIVE, ""); + this.typeNameIdOfNever = await first(never, TY_PRIMITIVE, ""); } /** - * Convert a list of RawFunctionType / ID to object-based FunctionType. - * - * Crates often have lots of functions in them, and it's common to have a large number of - * functions that operate on a small set of data types, so the search index compresses them - * by encoding function parameter and return types as indexes into an array of names. - * - * Even when a general-purpose compression algorithm is used, this is still a win. - * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985 + * Parses the query. * - * The format for individual function types is encoded in - * librustdoc/html/render/mod.rs: impl Serialize for RenderType + * The supported syntax by this parser is given in the rustdoc book chapter + * /src/doc/rustdoc/src/read-documentation/search.md * - * @param {null|Array<rustdoc.RawFunctionType>} types - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} paths - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean, - * }>} lowercasePaths + * When adding new things to the parser, add them there, too! * - * @return {Array<rustdoc.FunctionType>} - */ - buildItemSearchTypeAll(types, paths, lowercasePaths) { - return types && types.length > 0 ? - types.map(type => this.buildItemSearchType(type, paths, lowercasePaths)) : - this.EMPTY_GENERICS_ARRAY; - } - - /** - * Converts a single type. + * @param {string} userQuery - The user query * - * @param {rustdoc.RawFunctionType} type - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} paths - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean, - * }>} lowercasePaths - * @param {boolean=} isAssocType + * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query */ - buildItemSearchType(type, paths, lowercasePaths, isAssocType) { - const PATH_INDEX_DATA = 0; - const GENERICS_DATA = 1; - const BINDINGS_DATA = 2; - let pathIndex, generics, bindings; - if (typeof type === "number") { - pathIndex = type; - generics = this.EMPTY_GENERICS_ARRAY; - bindings = this.EMPTY_BINDINGS_MAP; - } else { - pathIndex = type[PATH_INDEX_DATA]; - generics = this.buildItemSearchTypeAll( - type[GENERICS_DATA], - paths, - lowercasePaths, - ); - // @ts-expect-error - if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) { - // @ts-expect-error - bindings = new Map(type[BINDINGS_DATA].map(binding => { - const [assocType, constraints] = binding; - // Associated type constructors are represented sloppily in rustdoc's - // type search, to make the engine simpler. - // - // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T> - // and both are, essentially - // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there. - // It's more like the value of a type binding is naturally an array, - // which rustdoc calls "constraints". - // - // As a result, the key should never have generics on it. - return [ - this.buildItemSearchType(assocType, paths, lowercasePaths, true).id, - this.buildItemSearchTypeAll(constraints, paths, lowercasePaths), - ]; - })); - } else { - bindings = this.EMPTY_BINDINGS_MAP; - } - } + static parseQuery(userQuery) { /** - * @type {rustdoc.FunctionType} - */ - let result; - if (pathIndex < 0) { - // types less than 0 are generic parameters - // the actual names of generic parameters aren't stored, since they aren't API - result = { - id: pathIndex, - name: "", - ty: TY_GENERIC, - path: null, - exactPath: null, - generics, - bindings, - unboxFlag: true, - }; - } else if (pathIndex === 0) { - // `0` is used as a sentinel because it's fewer bytes than `null` - result = { - id: null, - name: "", - ty: null, - path: null, - exactPath: null, - generics, - bindings, - unboxFlag: true, - }; - } else { - const item = lowercasePaths[pathIndex - 1]; - const id = this.buildTypeMapIndex(item.name, isAssocType); - if (isAssocType && id !== null) { - this.assocTypeIdNameMap.set(id, paths[pathIndex - 1].name); - } - result = { - id, - name: paths[pathIndex - 1].name, - ty: item.ty, - path: item.path, - exactPath: item.exactPath, - generics, - bindings, - unboxFlag: item.unboxFlag, - }; - } - const cr = this.TYPES_POOL.get(result.id); - if (cr) { - // Shallow equality check. Since this function is used - // to construct every type object, this should be mostly - // equivalent to a deep equality check, except if there's - // a conflict, we don't keep the old one around, so it's - // not a fully precise implementation of hashcons. - if (cr.generics.length === result.generics.length && - cr.generics !== result.generics && - cr.generics.every((x, i) => result.generics[i] === x) - ) { - result.generics = cr.generics; - } - if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) { - let ok = true; - for (const [k, v] of cr.bindings.entries()) { - // @ts-expect-error - const v2 = result.bindings.get(v); - if (!v2) { - ok = false; - break; - } - if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) { - result.bindings.set(k, v); - } else if (v !== v2) { - ok = false; - break; - } - } - if (ok) { - result.bindings = cr.bindings; - } - } - if (cr.ty === result.ty && cr.path === result.path - && cr.bindings === result.bindings && cr.generics === result.generics - && cr.ty === result.ty && cr.name === result.name - && cr.unboxFlag === result.unboxFlag - ) { - return cr; - } - } - this.TYPES_POOL.set(result.id, result); - return result; - } - - /** - * Type fingerprints allow fast, approximate matching of types. - * - * This algo creates a compact representation of the type set using a Bloom filter. - * This fingerprint is used three ways: - * - * - It accelerates the matching algorithm by checking the function fingerprint against the - * query fingerprint. If any bits are set in the query but not in the function, it can't - * match. - * - * - The fourth section has the number of items in the set. - * This is the distance function, used for filtering and for sorting. - * - * [^1]: Distance is the relatively naive metric of counting the number of distinct items in - * the function that are not present in the query. - * - * @param {rustdoc.FingerprintableType} type - a single type - * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits - */ - buildFunctionTypeFingerprint(type, output) { - let input = type.id; - // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter. - // Differentiating between arrays and slices, if the user asks for it, is - // still done in the matching algorithm. - if (input === this.typeNameIdOfArray || input === this.typeNameIdOfSlice) { - input = this.typeNameIdOfArrayOrSlice; - } - if (input === this.typeNameIdOfTuple || input === this.typeNameIdOfUnit) { - input = this.typeNameIdOfTupleOrUnit; - } - if (input === this.typeNameIdOfFn || input === this.typeNameIdOfFnMut || - input === this.typeNameIdOfFnOnce) { - input = this.typeNameIdOfHof; - } - /** - * http://burtleburtle.net/bob/hash/integer.html - * ~~ is toInt32. It's used before adding, so - * the number stays in safe integer range. - * @param {number} k - */ - const hashint1 = k => { - k = (~~k + 0x7ed55d16) + (k << 12); - k = (k ^ 0xc761c23c) ^ (k >>> 19); - k = (~~k + 0x165667b1) + (k << 5); - k = (~~k + 0xd3a2646c) ^ (k << 9); - k = (~~k + 0xfd7046c5) + (k << 3); - return (k ^ 0xb55a4f09) ^ (k >>> 16); - }; - /** @param {number} k */ - const hashint2 = k => { - k = ~k + (k << 15); - k ^= k >>> 12; - k += k << 2; - k ^= k >>> 4; - k = Math.imul(k, 2057); - return k ^ (k >> 16); - }; - if (input !== null) { - const h0a = hashint1(input); - const h0b = hashint2(input); - // Less Hashing, Same Performance: Building a Better Bloom Filter - // doi=10.1.1.72.2442 - const h1a = ~~(h0a + Math.imul(h0b, 2)); - const h1b = ~~(h0a + Math.imul(h0b, 3)); - const h2a = ~~(h0a + Math.imul(h0b, 4)); - const h2b = ~~(h0a + Math.imul(h0b, 5)); - output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32)); - output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32)); - output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32)); - // output[3] is the total number of items in the type signature - output[3] += 1; - } - for (const g of type.generics) { - this.buildFunctionTypeFingerprint(g, output); - } - /** - * @type {{ - * id: number|null, - * ty: number, - * generics: rustdoc.FingerprintableType[], - * bindings: Map<number, rustdoc.FingerprintableType[]> - * }} - */ - const fb = { - id: null, - ty: 0, - generics: this.EMPTY_GENERICS_ARRAY, - bindings: this.EMPTY_BINDINGS_MAP, - }; - for (const [k, v] of type.bindings.entries()) { - fb.id = k; - fb.generics = v; - this.buildFunctionTypeFingerprint(fb, output); - } - } - - /** - * Convert raw search index into in-memory search index. - * - * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex - * @returns {rustdoc.Row[]} - */ - buildIndex(rawSearchIndex) { - /** - * Convert from RawFunctionSearchType to FunctionSearchType. - * - * Crates often have lots of functions in them, and function signatures are sometimes - * complex, so rustdoc uses a pretty tight encoding for them. This function converts it - * to a simpler, object-based encoding so that the actual search code is more readable - * and easier to debug. - * - * The raw function search type format is generated using serde in - * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string - * - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} paths - * @param {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} lowercasePaths - * - * @return {function(rustdoc.RawFunctionSearchType): null|rustdoc.FunctionSearchType} - */ - const buildFunctionSearchTypeCallback = (paths, lowercasePaths) => { - /** - * @param {rustdoc.RawFunctionSearchType} functionSearchType - */ - const cb = functionSearchType => { - if (functionSearchType === 0) { - return null; - } - const INPUTS_DATA = 0; - const OUTPUT_DATA = 1; - /** @type {rustdoc.FunctionType[]} */ - let inputs; - /** @type {rustdoc.FunctionType[]} */ - let output; - if (typeof functionSearchType[INPUTS_DATA] === "number") { - inputs = [ - this.buildItemSearchType( - functionSearchType[INPUTS_DATA], - paths, - lowercasePaths, - ), - ]; - } else { - inputs = this.buildItemSearchTypeAll( - functionSearchType[INPUTS_DATA], - paths, - lowercasePaths, - ); - } - if (functionSearchType.length > 1) { - if (typeof functionSearchType[OUTPUT_DATA] === "number") { - output = [ - this.buildItemSearchType( - functionSearchType[OUTPUT_DATA], - paths, - lowercasePaths, - ), - ]; - } else { - output = this.buildItemSearchTypeAll( - // @ts-expect-error - functionSearchType[OUTPUT_DATA], - paths, - lowercasePaths, - ); - } - } else { - output = []; - } - const where_clause = []; - const l = functionSearchType.length; - for (let i = 2; i < l; ++i) { - where_clause.push(typeof functionSearchType[i] === "number" - // @ts-expect-error - ? [this.buildItemSearchType(functionSearchType[i], paths, lowercasePaths)] - : this.buildItemSearchTypeAll( - // @ts-expect-error - functionSearchType[i], - paths, - lowercasePaths, - )); - } - return { - inputs, output, where_clause, - }; - }; - return cb; - }; - - /** @type {rustdoc.Row[]} */ - const searchIndex = []; - let currentIndex = 0; - let id = 0; - - // Function type fingerprints are 128-bit bloom filters that are used to - // estimate the distance between function and query. - // This loop counts the number of items to allocate a fingerprint for. - for (const crate of rawSearchIndex.values()) { - // Each item gets an entry in the fingerprint array, and the crate - // does, too - id += crate.t.length + 1; - } - this.functionTypeFingerprint = new Uint32Array((id + 1) * 4); - // This loop actually generates the search item indexes, including - // normalized names, type signature objects and fingerprints, and aliases. - id = 0; - - /** @type {Array<[string, { [key: string]: Array<number> }, number]>} */ - const allAliases = []; - for (const [crate, crateCorpus] of rawSearchIndex) { - // a string representing the lengths of each description shard - // a string representing the list of function types - const itemDescShardDecoder = new VlqHexDecoder(crateCorpus.D, noop => { - /** @type {number} */ - // @ts-expect-error - const n = noop; - return n; - }); - let descShard = { - crate, - shard: 0, - start: 0, - len: itemDescShardDecoder.next(), - promise: null, - resolve: null, - }; - const descShardList = [descShard]; - - // Deprecated items and items with no description - this.searchIndexDeprecated.set(crate, new RoaringBitmap(crateCorpus.c)); - this.searchIndexEmptyDesc.set(crate, new RoaringBitmap(crateCorpus.e)); - let descIndex = 0; - - /** - * List of generic function type parameter names. - * Used for display, not for searching. - * @type {string[]} - */ - let lastParamNames = []; - - // This object should have exactly the same set of fields as the "row" - // object defined below. Your JavaScript runtime will thank you. - // https://mathiasbynens.be/notes/shapes-ics - let normalizedName = crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""); - const crateRow = { - crate, - ty: 3, // == ExternCrate - name: crate, - path: "", - descShard, - descIndex, - exactPath: "", - desc: crateCorpus.doc, - parent: undefined, - type: null, - paramNames: lastParamNames, - id, - word: crate, - normalizedName, - bitIndex: 0, - implDisambiguator: null, - }; - this.nameTrie.insert(normalizedName, id, this.tailTable); - id += 1; - searchIndex.push(crateRow); - currentIndex += 1; - // it's not undefined - // @ts-expect-error - if (!this.searchIndexEmptyDesc.get(crate).contains(0)) { - descIndex += 1; - } - - // see `RawSearchIndexCrate` in `rustdoc.d.ts` for a more - // up to date description of these fields - const itemTypes = crateCorpus.t; - // an array of (String) item names - const itemNames = crateCorpus.n; - // an array of [(Number) item index, - // (String) full path] - // an item whose index is not present will fall back to the previous present path - // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present, - // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11 - const itemPaths = new Map(crateCorpus.q); - // An array of [(Number) item index, (Number) path index] - // Used to de-duplicate inlined and re-exported stuff - const itemReexports = new Map(crateCorpus.r); - // an array of (Number) the parent path index + 1 to `paths`, or 0 if none - const itemParentIdxDecoder = new VlqHexDecoder(crateCorpus.i, noop => noop); - // a map Number, string for impl disambiguators - const implDisambiguator = new Map(crateCorpus.b); - const rawPaths = crateCorpus.p; - const aliases = crateCorpus.a; - // an array of [(Number) item index, - // (String) comma-separated list of function generic param names] - // an item whose index is not present will fall back to the previous present path - const itemParamNames = new Map(crateCorpus.P); - - /** - * @type {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} - */ - const lowercasePaths = []; - /** - * @type {Array<{ - * name: string, - * ty: number, - * path: string|null, - * exactPath: string|null, - * unboxFlag: boolean - * }>} - */ - const paths = []; - - // a string representing the list of function types - const itemFunctionDecoder = new VlqHexDecoder( - crateCorpus.f, - // @ts-expect-error - buildFunctionSearchTypeCallback(paths, lowercasePaths), - ); - - // convert `rawPaths` entries into object form - // generate normalizedPaths for function search mode - let len = rawPaths.length; - let lastPath = undef2null(itemPaths.get(0)); - for (let i = 0; i < len; ++i) { - const elem = rawPaths[i]; - const ty = elem[0]; - const name = elem[1]; - /** - * @param {2|3} idx - * @param {string|null} if_null - * @param {string|null} if_not_found - * @returns {string|null} - */ - const elemPath = (idx, if_null, if_not_found) => { - if (elem.length > idx && elem[idx] !== undefined) { - const p = itemPaths.get(elem[idx]); - if (p !== undefined) { - return p; - } - return if_not_found; - } - return if_null; - }; - const path = elemPath(2, lastPath, null); - const exactPath = elemPath(3, path, path); - const unboxFlag = elem.length > 4 && !!elem[4]; - - lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath, unboxFlag }); - paths[i] = { ty, name, path, exactPath, unboxFlag }; - } - - // Convert `item*` into an object form, and construct word indices. - // - // Before any analysis is performed, let's gather the search terms to - // search against apart from the rest of the data. This is a quick - // operation that is cached for the life of the page state so that - // all other search operations have access to this cached data for - // faster analysis operations - lastPath = ""; - len = itemTypes.length; - let lastName = ""; - let lastWord = ""; - for (let i = 0; i < len; ++i) { - const bitIndex = i + 1; - if (descIndex >= descShard.len && - // @ts-expect-error - !this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) { - descShard = { - crate, - shard: descShard.shard + 1, - start: descShard.start + descShard.len, - len: itemDescShardDecoder.next(), - promise: null, - resolve: null, - }; - descIndex = 0; - descShardList.push(descShard); - } - const name = itemNames[i] === "" ? lastName : itemNames[i]; - const word = itemNames[i] === "" ? lastWord : itemNames[i].toLowerCase(); - const pathU = itemPaths.get(i); - const path = pathU !== undefined ? pathU : lastPath; - const paramNameString = itemParamNames.get(i); - const paramNames = paramNameString !== undefined ? - paramNameString.split(",") : - lastParamNames; - const type = itemFunctionDecoder.next(); - if (type !== null) { - if (type) { - const fp = this.functionTypeFingerprint.subarray(id * 4, (id + 1) * 4); - for (const t of type.inputs) { - this.buildFunctionTypeFingerprint(t, fp); - } - for (const t of type.output) { - this.buildFunctionTypeFingerprint(t, fp); - } - for (const w of type.where_clause) { - for (const t of w) { - this.buildFunctionTypeFingerprint(t, fp); - } - } - } - } - // This object should have exactly the same set of fields as the "crateRow" - // object defined above. - const itemParentIdx = itemParentIdxDecoder.next(); - normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, ""); - /** @type {rustdoc.Row} */ - const row = { - crate, - ty: itemTypes.charCodeAt(i) - 65, // 65 = "A" - name, - path, - descShard, - descIndex, - exactPath: itemReexports.has(i) ? - // @ts-expect-error - itemPaths.get(itemReexports.get(i)) : path, - // @ts-expect-error - parent: itemParentIdx > 0 ? paths[itemParentIdx - 1] : undefined, - type, - paramNames, - id, - word, - normalizedName, - bitIndex, - implDisambiguator: undef2null(implDisambiguator.get(i)), - }; - this.nameTrie.insert(normalizedName, id, this.tailTable); - id += 1; - searchIndex.push(row); - lastPath = row.path; - lastParamNames = row.paramNames; - // @ts-expect-error - if (!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) { - descIndex += 1; - } - lastName = name; - lastWord = word; - } - - if (aliases) { - // We need to add the aliases in `searchIndex` after we finished filling it - // to not mess up indexes. - allAliases.push([crate, aliases, currentIndex]); - } - currentIndex += itemTypes.length; - this.searchState.descShards.set(crate, descShardList); - } - - for (const [crate, aliases, index] of allAliases) { - for (const [alias_name, alias_refs] of Object.entries(aliases)) { - if (!this.ALIASES.has(crate)) { - this.ALIASES.set(crate, new Map()); - } - const word = alias_name.toLowerCase(); - const crate_alias_map = this.ALIASES.get(crate); - if (!crate_alias_map.has(word)) { - crate_alias_map.set(word, []); - } - const aliases_map = crate_alias_map.get(word); - - const normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, ""); - for (const alias of alias_refs) { - const originalIndex = alias + index; - const original = searchIndex[originalIndex]; - /** @type {rustdoc.Row} */ - const row = { - crate, - name: alias_name, - normalizedName, - is_alias: true, - ty: original.ty, - type: original.type, - paramNames: [], - word, - id, - parent: undefined, - original, - path: "", - implDisambiguator: original.implDisambiguator, - // Needed to load the description of the original item. - // @ts-ignore - descShard: original.descShard, - descIndex: original.descIndex, - bitIndex: original.bitIndex, - }; - aliases_map.push(row); - this.nameTrie.insert(normalizedName, id, this.tailTable); - id += 1; - searchIndex.push(row); - } - } - } - // Drop the (rather large) hash table used for reusing function items - this.TYPES_POOL = new Map(); - return searchIndex; - } - - /** - * Parses the query. - * - * The supported syntax by this parser is given in the rustdoc book chapter - * /src/doc/rustdoc/src/read-documentation/search.md - * - * When adding new things to the parser, add them there, too! - * - * @param {string} userQuery - The user query - * - * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query - */ - static parseQuery(userQuery) { - /** - * @param {string} typename - * @returns {number} - */ - function itemTypeFromName(typename) { - const index = itemTypes.findIndex(i => i === typename); - if (index < 0) { - throw ["Unknown type filter ", typename]; - } - return index; - } - - /** - * @param {rustdoc.ParserQueryElement} elem - */ - function convertTypeFilterOnElem(elem) { - if (typeof elem.typeFilter === "string") { - let typeFilter = elem.typeFilter; - if (typeFilter === "const") { - typeFilter = "constant"; - } - elem.typeFilter = itemTypeFromName(typeFilter); - } else { - elem.typeFilter = NO_TYPE_FILTER; - } - for (const elem2 of elem.generics) { - convertTypeFilterOnElem(elem2); - } - for (const constraints of elem.bindings.values()) { - for (const constraint of constraints) { - convertTypeFilterOnElem(constraint); - } - } - } - - /** - * Takes the user search input and returns an empty `ParsedQuery`. - * - * @param {string} userQuery - * - * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} + * Takes the user search input and returns an empty `ParsedQuery`. + * + * @param {string} userQuery + * + * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} */ function newParsedQuery(userQuery) { return { @@ -2437,8 +1347,7 @@ class DocSearch { continue; } if (!foundStopChar) { - /** @type String[] */ - let extra = []; + let extra = EMPTY_STRING_ARRAY; if (isLastElemGeneric(query.elems, parserState)) { extra = [" after ", ">"]; } else if (prevIs(parserState, "\"")) { @@ -2515,236 +1424,623 @@ class DocSearch { try { parseInput(query, parserState); + + // Scan for invalid type filters, so that we can report the error + // outside the search loop. + /** @param {rustdoc.ParserQueryElement} elem */ + const checkTypeFilter = elem => { + const ty = itemTypeFromName(elem.typeFilter); + if (ty === TY_GENERIC && elem.generics.length !== 0) { + throw [ + "Generic type parameter ", + elem.name, + " does not accept generic parameters", + ]; + } + for (const generic of elem.generics) { + checkTypeFilter(generic); + } + for (const constraints of elem.bindings.values()) { + for (const constraint of constraints) { + checkTypeFilter(constraint); + } + } + }; for (const elem of query.elems) { - convertTypeFilterOnElem(elem); + checkTypeFilter(elem); + } + for (const elem of query.returned) { + checkTypeFilter(elem); + } + } catch (err) { + query = newParsedQuery(userQuery); + if (Array.isArray(err) && err.every(elem => typeof elem === "string")) { + query.error = err; + } else { + // rethrow the error if it isn't a string array + throw err; + } + + return query; + } + if (!query.literalSearch) { + // If there is more than one element in the query, we switch to literalSearch in any + // case. + query.literalSearch = parserState.totalElems > 1; + } + query.foundElems = query.elems.length + query.returned.length; + query.totalElems = parserState.totalElems; + return query; + } + + /** + * @param {number} id + * @returns {Promise<string|null>} + */ + async getName(id) { + const ni = this.database.getData("name"); + if (!ni) { + return null; + } + const name = await ni.at(id); + return name === undefined || name === null ? null : this.utf8decoder.decode(name); + } + + /** + * @param {number} id + * @returns {Promise<string|null>} + */ + async getDesc(id) { + const di = this.database.getData("desc"); + if (!di) { + return null; + } + const desc = await di.at(id); + return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc); + } + + /** + * @param {number} id + * @returns {Promise<number|null>} + */ + async getAliasTarget(id) { + const ai = this.database.getData("alias"); + if (!ai) { + return null; + } + const bytes = await ai.at(id); + if (bytes === undefined || bytes === null || bytes.length === 0) { + return null; + } else { + /** @type {string} */ + const encoded = this.utf8decoder.decode(bytes); + /** @type {number|null} */ + const decoded = JSON.parse(encoded); + return decoded; + } + } + + /** + * @param {number} id + * @returns {Promise<rustdoc.EntryData|null>} + */ + async getEntryData(id) { + const ei = this.database.getData("entry"); + if (!ei) { + return null; + } + const encoded = this.utf8decoder.decode(await ei.at(id)); + if (encoded === "" || encoded === undefined || encoded === null) { + return null; + } + /** + * krate, + * ty, + * module_path, + * exact_module_path, + * parent, + * deprecated, + * associated_item_disambiguator + * @type {rustdoc.ArrayWithOptionals<[ + * number, + * rustdoc.ItemType, + * number, + * number, + * number, + * number, + * ], [string]>} + */ + const raw = JSON.parse(encoded); + return { + krate: raw[0], + ty: raw[1], + modulePath: raw[2] === 0 ? null : raw[2] - 1, + exactModulePath: raw[3] === 0 ? null : raw[3] - 1, + parent: raw[4] === 0 ? null : raw[4] - 1, + deprecated: raw[5] === 1 ? true : false, + associatedItemDisambiguator: raw.length === 6 ? null : raw[6], + }; + } + + /** + * @param {number} id + * @returns {Promise<rustdoc.PathData|null>} + */ + async getPathData(id) { + const pi = this.database.getData("path"); + if (!pi) { + return null; + } + const encoded = this.utf8decoder.decode(await pi.at(id)); + if (encoded === "" || encoded === undefined || encoded === null) { + return null; + } + /** + * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index + * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>} + */ + const raw = JSON.parse(encoded); + return { + ty: raw[0], + modulePath: raw[1], + exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2], + }; + } + + /** + * @param {number} id + * @returns {Promise<rustdoc.FunctionData|null>} + */ + async getFunctionData(id) { + const fi = this.database.getData("function"); + if (!fi) { + return null; + } + const encoded = this.utf8decoder.decode(await fi.at(id)); + if (encoded === "" || encoded === undefined || encoded === null) { + return null; + } + /** + * function_signature, param_names + * @type {[string, string[]]} + */ + const raw = JSON.parse(encoded); + + const parser = new VlqHexDecoder(raw[0], async functionSearchType => { + if (typeof functionSearchType === "number") { + return null; + } + const INPUTS_DATA = 0; + const OUTPUT_DATA = 1; + /** @type {Promise<rustdoc.FunctionType[]>} */ + let inputs_; + /** @type {Promise<rustdoc.FunctionType[]>} */ + let output_; + if (typeof functionSearchType[INPUTS_DATA] === "number") { + inputs_ = Promise.all([ + this.buildItemSearchType(functionSearchType[INPUTS_DATA]), + ]); + } else { + // @ts-ignore + inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]); + } + if (functionSearchType.length > 1) { + if (typeof functionSearchType[OUTPUT_DATA] === "number") { + output_ = Promise.all([ + this.buildItemSearchType(functionSearchType[OUTPUT_DATA]), + ]); + } else { + // @ts-expect-error + output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]); + } + } else { + output_ = Promise.resolve(EMPTY_GENERICS_ARRAY); + } + /** @type {Promise<rustdoc.FunctionType[]>[]} */ + const where_clause_ = []; + const l = functionSearchType.length; + for (let i = 2; i < l; ++i) { + where_clause_.push(typeof functionSearchType[i] === "number" + // @ts-expect-error + ? Promise.all([this.buildItemSearchType(functionSearchType[i])]) + // @ts-expect-error + : this.buildItemSearchTypeAll(functionSearchType[i]), + ); + } + const [inputs, output, where_clause] = await Promise.all([ + inputs_, + output_, + Promise.all(where_clause_), + ]); + return { + inputs, output, where_clause, + }; + }); + + return { + functionSignature: await parser.next(), + paramNames: raw[1], + elemCount: parser.elemCount, + }; + } + + /** + * @param {number} id + * @returns {Promise<rustdoc.TypeData|null>} + */ + async getTypeData(id) { + const ti = this.database.getData("type"); + if (!ti) { + return null; + } + const encoded = this.utf8decoder.decode(await ti.at(id)); + if (encoded === "" || encoded === undefined || encoded === null) { + return null; + } + /** + * function_signature, param_names + * @type {[string, number] | [number] | [string] | [] | null} + */ + const raw = JSON.parse(encoded); + + if (!raw || raw.length === 0) { + return null; + } + + let searchUnbox = false; + const invertedFunctionSignatureIndex = []; + + if (typeof raw[0] === "string") { + if (raw[1]) { + searchUnbox = true; + } + // the inverted function signature index is a list of bitmaps, + // by number of types that appear in the function + let i = 0; + const pb = makeUint8ArrayFromBase64(raw[0]); + const l = pb.length; + while (i < l) { + if (pb[i] === 0) { + invertedFunctionSignatureIndex.push(RoaringBitmap.empty()); + i += 1; + } else { + const bitmap = new RoaringBitmap(pb, i); + i += bitmap.consumed_len_bytes; + invertedFunctionSignatureIndex.push(bitmap); + } + } + } else if (raw[0]) { + searchUnbox = true; + } + + return { searchUnbox, invertedFunctionSignatureIndex }; + } + + /** + * @returns {Promise<string[]>} + */ + async getCrateNameList() { + const crateNames = this.database.getData("crateNames"); + if (!crateNames) { + return []; + } + const l = crateNames.length; + const names = []; + for (let i = 0; i < l; ++i) { + names.push(crateNames.at(i).then(name => { + if (name === undefined) { + return ""; + } + return this.utf8decoder.decode(name); + })); + } + return Promise.all(names); + } + + /** + * @param {number} id non-negative generic index + * @returns {Promise<stringdex.RoaringBitmap[]>} + */ + async getGenericInvertedIndex(id) { + const gii = this.database.getData("generic_inverted_index"); + if (!gii) { + return []; + } + const pb = await gii.at(id); + if (pb === undefined || pb === null || pb.length === 0) { + return []; + } + + const invertedFunctionSignatureIndex = []; + // the inverted function signature index is a list of bitmaps, + // by number of types that appear in the function + let i = 0; + const l = pb.length; + while (i < l) { + if (pb[i] === 0) { + invertedFunctionSignatureIndex.push(RoaringBitmap.empty()); + i += 1; + } else { + const bitmap = new RoaringBitmap(pb, i); + i += bitmap.consumed_len_bytes; + invertedFunctionSignatureIndex.push(bitmap); + } + } + return invertedFunctionSignatureIndex; + } + + /** + * @param {number} id + * @returns {Promise<rustdoc.Row?>} + */ + async getRow(id) { + const [name_, entry, path, type] = await Promise.all([ + this.getName(id), + this.getEntryData(id), + this.getPathData(id), + this.getFunctionData(id), + ]); + if (!entry && !path) { + return null; + } + const [ + moduleName, + modulePathData, + exactModuleName, + exactModulePathData, + ] = await Promise.all([ + entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null, + entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null, + entry && entry.exactModulePath !== null ? + this.getName(entry.exactModulePath) : + null, + entry && entry.exactModulePath !== null ? + this.getPathData(entry.exactModulePath) : + null, + ]); + const name = name_ === null ? "" : name_; + const normalizedName = (name.indexOf("_") === -1 ? + name : + name.replace(/_/g, "")).toLowerCase(); + const modulePath = modulePathData === null || moduleName === null ? "" : + (modulePathData.modulePath === "" ? + moduleName : + `${modulePathData.modulePath}::${moduleName}`); + const [parentName, parentPath] = entry !== null && entry.parent !== null ? + await Promise.all([this.getName(entry.parent), this.getPathData(entry.parent)]) : + [null, null]; + return { + id, + crate: entry ? nonnull(await this.getName(entry.krate)) : "", + ty: entry ? entry.ty : nonnull(path).ty, + name, + normalizedName, + modulePath, + exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath : + (exactModulePathData.exactModulePath === "" ? + exactModuleName : + `${exactModulePathData.exactModulePath}::${exactModuleName}`), + entry, + path, + type, + deprecated: entry ? entry.deprecated : false, + parent: parentName !== null && parentPath !== null ? + { name: parentName, path: parentPath } : + null, + }; + } + + /** + * Convert a list of RawFunctionType / ID to object-based FunctionType. + * + * Crates often have lots of functions in them, and it's common to have a large number of + * functions that operate on a small set of data types, so the search index compresses them + * by encoding function parameter and return types as indexes into an array of names. + * + * Even when a general-purpose compression algorithm is used, this is still a win. + * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985 + * + * The format for individual function types is encoded in + * librustdoc/html/render/mod.rs: impl Serialize for RenderType + * + * @param {null|Array<rustdoc.RawFunctionType>} types + * + * @return {Promise<Array<rustdoc.FunctionType>>} + */ + async buildItemSearchTypeAll(types) { + return types && types.length > 0 ? + await Promise.all(types.map(type => this.buildItemSearchType(type))) : + EMPTY_GENERICS_ARRAY; + } + + /** + * Converts a single type. + * + * @param {rustdoc.RawFunctionType} type + * @return {Promise<rustdoc.FunctionType>} + */ + async buildItemSearchType(type) { + const PATH_INDEX_DATA = 0; + const GENERICS_DATA = 1; + const BINDINGS_DATA = 2; + let id, generics; + /** + * @type {Map<number, rustdoc.FunctionType[]>} + */ + let bindings; + if (typeof type === "number") { + id = type; + generics = EMPTY_GENERICS_ARRAY; + bindings = EMPTY_BINDINGS_MAP; + } else { + id = type[PATH_INDEX_DATA]; + generics = await this.buildItemSearchTypeAll(type[GENERICS_DATA]); + if (type[BINDINGS_DATA] && type[BINDINGS_DATA].length > 0) { + bindings = new Map((await Promise.all(type[BINDINGS_DATA].map( + /** + * @param {[rustdoc.RawFunctionType, rustdoc.RawFunctionType[]]} binding + * @returns {Promise<[number, rustdoc.FunctionType[]][]>} + */ + async binding => { + const [assocType, constraints] = binding; + // Associated type constructors are represented sloppily in rustdoc's + // type search, to make the engine simpler. + // + // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T> + // and both are, essentially + // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there. + // It's more like the value of a type binding is naturally an array, + // which rustdoc calls "constraints". + // + // As a result, the key should never have generics on it. + const [k, v] = await Promise.all([ + this.buildItemSearchType(assocType).then(t => t.id), + this.buildItemSearchTypeAll(constraints), + ]); + return k === null ? EMPTY_BINDINGS_ARRAY : [[k, v]]; + }, + ))).flat()); + } else { + bindings = EMPTY_BINDINGS_MAP; + } + } + /** + * @type {rustdoc.FunctionType} + */ + let result; + if (id < 0) { + // types less than 0 are generic parameters + // the actual names of generic parameters aren't stored, since they aren't API + result = { + id, + name: "", + ty: TY_GENERIC, + path: null, + exactPath: null, + generics, + bindings, + unboxFlag: true, + }; + } else if (id === 0) { + // `0` is used as a sentinel because it's fewer bytes than `null` + result = { + id: null, + name: "", + ty: TY_GENERIC, + path: null, + exactPath: null, + generics, + bindings, + unboxFlag: true, + }; + } else { + const [name, path, type] = await Promise.all([ + this.getName(id - 1), + this.getPathData(id - 1), + this.getTypeData(id - 1), + ]); + if (path === undefined || path === null || type === undefined || type === null) { + return { + id: null, + name: "", + ty: TY_GENERIC, + path: null, + exactPath: null, + generics, + bindings, + unboxFlag: true, + }; + } + result = { + id: id - 1, + name, + ty: path.ty, + path: path.modulePath, + exactPath: path.exactModulePath === null ? path.modulePath : path.exactModulePath, + generics, + bindings, + unboxFlag: type.searchUnbox, + }; + } + const cr = this.TYPES_POOL.get(result.id); + if (cr) { + // Shallow equality check. Since this function is used + // to construct every type object, this should be mostly + // equivalent to a deep equality check, except if there's + // a conflict, we don't keep the old one around, so it's + // not a fully precise implementation of hashcons. + if (cr.generics.length === result.generics.length && + cr.generics !== result.generics && + cr.generics.every((x, i) => result.generics[i] === x) + ) { + result.generics = cr.generics; } - for (const elem of query.returned) { - convertTypeFilterOnElem(elem); + if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) { + let ok = true; + for (const [k, v] of cr.bindings.entries()) { + const v2 = result.bindings.get(k); + if (!v2) { + ok = false; + break; + } + if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) { + result.bindings.set(k, v); + } else if (v !== v2) { + ok = false; + break; + } + } + if (ok) { + result.bindings = cr.bindings; + } } - } catch (err) { - query = newParsedQuery(userQuery); - if (Array.isArray(err) && err.every(elem => typeof elem === "string")) { - query.error = err; - } else { - // rethrow the error if it isn't a string array - throw err; + if (cr.ty === result.ty && cr.path === result.path + && cr.bindings === result.bindings && cr.generics === result.generics + && cr.ty === result.ty && cr.name === result.name + && cr.unboxFlag === result.unboxFlag + ) { + return cr; } - - return query; - } - if (!query.literalSearch) { - // If there is more than one element in the query, we switch to literalSearch in any - // case. - query.literalSearch = parserState.totalElems > 1; } - query.foundElems = query.elems.length + query.returned.length; - query.totalElems = parserState.totalElems; - return query; + this.TYPES_POOL.set(result.id, result); + return result; } /** * Executes the parsed query and builds a {ResultsTable}. * - * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} origParsedQuery + * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} parsedQuery * - The parsed user query * @param {Object} filterCrates - Crate to search in if defined * @param {string} currentCrate - Current crate, to rank results from this crate higher * * @return {Promise<rustdoc.ResultsTable>} */ - async execQuery(origParsedQuery, filterCrates, currentCrate) { - /** @type {rustdoc.Results} */ - const results_others = new Map(), - /** @type {rustdoc.Results} */ - results_in_args = new Map(), - /** @type {rustdoc.Results} */ - results_returned = new Map(); - - /** @type {rustdoc.ParsedQuery<rustdoc.QueryElement>} */ - // @ts-expect-error - const parsedQuery = origParsedQuery; - + async execQuery(parsedQuery, filterCrates, currentCrate) { const queryLen = parsedQuery.elems.reduce((acc, next) => acc + next.pathLast.length, 0) + parsedQuery.returned.reduce((acc, next) => acc + next.pathLast.length, 0); const maxEditDistance = Math.floor(queryLen / 3); - // We reinitialize the `FOUND_ALIASES` map. - this.FOUND_ALIASES.clear(); - - /** - * @type {Map<string, number>} - */ - const genericSymbols = new Map(); - - /** - * Convert names to ids in parsed query elements. - * This is not used for the "In Names" tab, but is used for the - * "In Params", "In Returns", and "In Function Signature" tabs. - * - * If there is no matching item, but a close-enough match, this - * function also that correction. - * - * See `buildTypeMapIndex` for more information. - * - * @param {rustdoc.QueryElement} elem - * @param {boolean=} isAssocType - */ - const convertNameToId = (elem, isAssocType) => { - const loweredName = elem.pathLast.toLowerCase(); - if (this.typeNameIdMap.has(loweredName) && - // @ts-expect-error - (isAssocType || !this.typeNameIdMap.get(loweredName).assocOnly)) { - // @ts-expect-error - elem.id = this.typeNameIdMap.get(loweredName).id; - } else if (!parsedQuery.literalSearch) { - let match = null; - let matchDist = maxEditDistance + 1; - let matchName = ""; - for (const [name, { id, assocOnly }] of this.typeNameIdMap) { - const dist = Math.min( - editDistance(name, loweredName, maxEditDistance), - editDistance(name, elem.normalizedPathLast, maxEditDistance), - ); - if (dist <= matchDist && dist <= maxEditDistance && - (isAssocType || !assocOnly)) { - if (dist === matchDist && matchName > name) { - continue; - } - match = id; - matchDist = dist; - matchName = name; - } - } - if (match !== null) { - parsedQuery.correction = matchName; - } - elem.id = match; - } - if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1 - && elem.generics.length === 0 && elem.bindings.size === 0) - || elem.typeFilter === TY_GENERIC) { - const id = genericSymbols.get(elem.normalizedPathLast); - if (id !== undefined) { - elem.id = id; - } else { - elem.id = -(genericSymbols.size + 1); - genericSymbols.set(elem.normalizedPathLast, elem.id); - } - if (elem.typeFilter === -1 && elem.normalizedPathLast.length >= 3) { - // Silly heuristic to catch if the user probably meant - // to not write a generic parameter. We don't use it, - // just bring it up. - const maxPartDistance = Math.floor(elem.normalizedPathLast.length / 3); - let matchDist = maxPartDistance + 1; - let matchName = ""; - for (const name of this.typeNameIdMap.keys()) { - const dist = editDistance( - name, - elem.normalizedPathLast, - maxPartDistance, - ); - if (dist <= matchDist && dist <= maxPartDistance) { - if (dist === matchDist && matchName > name) { - continue; - } - matchDist = dist; - matchName = name; - } - } - if (matchName !== "") { - parsedQuery.proposeCorrectionFrom = elem.name; - parsedQuery.proposeCorrectionTo = matchName; - } - } - elem.typeFilter = TY_GENERIC; - } - if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) { - // Rust does not have HKT - parsedQuery.error = [ - "Generic type parameter ", - elem.name, - " does not accept generic parameters", - ]; - } - for (const elem2 of elem.generics) { - convertNameToId(elem2); - } - elem.bindings = new Map(Array.from(elem.bindings.entries()) - .map(entry => { - const [name, constraints] = entry; - // @ts-expect-error - if (!this.typeNameIdMap.has(name)) { - parsedQuery.error = [ - "Type parameter ", - // @ts-expect-error - name, - " does not exist", - ]; - return [0, []]; - } - for (const elem2 of constraints) { - convertNameToId(elem2, false); - } - - // @ts-expect-error - return [this.typeNameIdMap.get(name).id, constraints]; - }), - ); - }; - - for (const elem of parsedQuery.elems) { - convertNameToId(elem, false); - this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint); - } - for (const elem of parsedQuery.returned) { - convertNameToId(elem, false); - this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint); - } - /** - * Creates the query results. - * - * @param {Array<rustdoc.ResultObject>} results_in_args - * @param {Array<rustdoc.ResultObject>} results_returned - * @param {Array<rustdoc.ResultObject>} results_others - * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} parsedQuery - * - * @return {rustdoc.ResultsTable} + * @param {rustdoc.Row} item + * @returns {[string, string, string]} */ - function createQueryResults( - results_in_args, - results_returned, - results_others, - parsedQuery) { - return { - "in_args": results_in_args, - "returned": results_returned, - "others": results_others, - "query": parsedQuery, - }; - } - - // @ts-expect-error const buildHrefAndPath = item => { let displayPath; let href; - if (item.is_alias) { - this.FOUND_ALIASES.add(item.word); - item = item.original; - } const type = itemTypes[item.ty]; const name = item.name; - let path = item.path; - let exactPath = item.exactPath; + let path = item.modulePath; + let exactPath = item.exactModulePath; if (type === "mod") { displayPath = path + "::"; href = this.rootPath + path.replace(/::/g, "/") + "/" + name + "/index.html"; } else if (type === "import") { - displayPath = item.path + "::"; - href = this.rootPath + item.path.replace(/::/g, "/") + + displayPath = item.modulePath + "::"; + href = this.rootPath + item.modulePath.replace(/::/g, "/") + "/index.html#reexport." + name; } else if (type === "primitive" || type === "keyword") { displayPath = ""; @@ -2754,13 +2050,13 @@ class DocSearch { } else if (type === "externcrate") { displayPath = ""; href = this.rootPath + name + "/index.html"; - } else if (item.parent !== undefined) { + } else if (item.parent) { const myparent = item.parent; let anchor = type + "." + name; - const parentType = itemTypes[myparent.ty]; + const parentType = itemTypes[myparent.path.ty]; let pageType = parentType; let pageName = myparent.name; - exactPath = `${myparent.exactPath}::${myparent.name}`; + exactPath = `${myparent.path.exactModulePath}::${myparent.name}`; if (parentType === "primitive") { displayPath = myparent.name + "::"; @@ -2768,9 +2064,9 @@ class DocSearch { } else if (type === "structfield" && parentType === "variant") { // Structfields belonging to variants are special: the // final path element is the enum name. - const enumNameIdx = item.path.lastIndexOf("::"); - const enumName = item.path.substr(enumNameIdx + 2); - path = item.path.substr(0, enumNameIdx); + const enumNameIdx = item.modulePath.lastIndexOf("::"); + const enumName = item.modulePath.substr(enumNameIdx + 2); + path = item.modulePath.substr(0, enumNameIdx); displayPath = path + "::" + enumName + "::" + myparent.name + "::"; anchor = "variant." + myparent.name + ".field." + name; pageType = "enum"; @@ -2778,16 +2074,16 @@ class DocSearch { } else { displayPath = path + "::" + myparent.name + "::"; } - if (item.implDisambiguator !== null) { - anchor = item.implDisambiguator + "/" + anchor; + if (item.entry && item.entry.associatedItemDisambiguator !== null) { + anchor = item.entry.associatedItemDisambiguator + "/" + anchor; } href = this.rootPath + path.replace(/::/g, "/") + "/" + pageType + "." + pageName + ".html#" + anchor; } else { - displayPath = item.path + "::"; - href = this.rootPath + item.path.replace(/::/g, "/") + + displayPath = item.modulePath + "::"; + href = this.rootPath + item.modulePath.replace(/::/g, "/") + "/" + type + "." + name + ".html"; } return [displayPath, href, `${exactPath}::${name}`]; @@ -2806,74 +2102,6 @@ class DocSearch { return tmp; } - /** - * Add extra data to result objects, and filter items that have been - * marked for removal. - * - * @param {rustdoc.ResultObject[]} results - * @param {"sig"|"elems"|"returned"|null} typeInfo - * @returns {rustdoc.ResultObject[]} - */ - const transformResults = (results, typeInfo) => { - const duplicates = new Set(); - const out = []; - - for (const result of results) { - if (result.id !== -1) { - const res = buildHrefAndPath(this.searchIndex[result.id]); - // many of these properties don't strictly need to be - // copied over, but copying them over satisfies tsc, - // and hopefully plays nice with the shape optimization - // of the browser engine. - /** @type {rustdoc.ResultObject} */ - const obj = Object.assign({ - parent: result.parent, - type: result.type, - dist: result.dist, - path_dist: result.path_dist, - index: result.index, - desc: result.desc, - item: result.item, - displayPath: pathSplitter(res[0]), - fullPath: "", - href: "", - displayTypeSignature: null, - }, this.searchIndex[result.id]); - - // To be sure than it some items aren't considered as duplicate. - obj.fullPath = res[2] + "|" + obj.ty; - - if (duplicates.has(obj.fullPath)) { - continue; - } - - // Exports are specifically not shown if the items they point at - // are already in the results. - if (obj.ty === TY_IMPORT && duplicates.has(res[2])) { - continue; - } - if (duplicates.has(res[2] + "|" + TY_IMPORT)) { - continue; - } - duplicates.add(obj.fullPath); - duplicates.add(res[2]); - - if (typeInfo !== null) { - obj.displayTypeSignature = - // @ts-expect-error - this.formatDisplayTypeSignature(obj, typeInfo); - } - - obj.href = res[1]; - out.push(obj); - if (out.length >= MAX_RESULTS) { - break; - } - } - } - return out; - }; - /** * Add extra data to result objects, and filter items that have been * marked for removal. @@ -2883,9 +2111,11 @@ class DocSearch { * * @param {rustdoc.ResultObject} obj * @param {"sig"|"elems"|"returned"|null} typeInfo + * @param {rustdoc.QueryElement[]} elems + * @param {rustdoc.QueryElement[]} returned * @returns {Promise<rustdoc.DisplayTypeSignature>} */ - this.formatDisplayTypeSignature = async(obj, typeInfo) => { + const formatDisplayTypeSignature = async(obj, typeInfo, elems, returned) => { const objType = obj.type; if (!objType) { return {type: [], mappedNames: new Map(), whereClause: new Map()}; @@ -2897,13 +2127,13 @@ class DocSearch { if (typeInfo !== "elems" && typeInfo !== "returned") { fnInputs = unifyFunctionTypes( objType.inputs, - parsedQuery.elems, + elems, objType.where_clause, null, mgensScratch => { fnOutput = unifyFunctionTypes( objType.output, - parsedQuery.returned, + returned, objType.where_clause, mgensScratch, mgensOut => { @@ -2917,10 +2147,9 @@ class DocSearch { 0, ); } else { - const arr = typeInfo === "elems" ? objType.inputs : objType.output; const highlighted = unifyFunctionTypes( - arr, - parsedQuery.elems, + typeInfo === "elems" ? objType.inputs : objType.output, + typeInfo === "elems" ? elems : returned, objType.where_clause, null, mgensOut => { @@ -2969,15 +2198,15 @@ class DocSearch { } }; - parsedQuery.elems.forEach(remapQuery); - parsedQuery.returned.forEach(remapQuery); + elems.forEach(remapQuery); + returned.forEach(remapQuery); /** * Write text to a highlighting array. * Index 0 is not highlighted, index 1 is highlighted, * index 2 is not highlighted, etc. * - * @param {{name?: string, highlighted?: boolean}} fnType - input + * @param {{name: string|null, highlighted?: boolean}} fnType - input * @param {string[]} result */ const pushText = (fnType, result) => { @@ -3004,8 +2233,9 @@ class DocSearch { * * @param {rustdoc.HighlightedFunctionType} fnType - input * @param {string[]} result + * @returns {Promise<void>} */ - const writeHof = (fnType, result) => { + const writeHof = async(fnType, result) => { const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || []; const hofInputs = fnType.generics; pushText(fnType, result); @@ -3016,7 +2246,7 @@ class DocSearch { pushText({ name: ", ", highlighted: false }, result); } needsComma = true; - writeFn(fnType, result); + await writeFn(fnType, result); } pushText({ name: hofOutput.length === 0 ? ")" : ") -> ", @@ -3031,7 +2261,7 @@ class DocSearch { pushText({ name: ", ", highlighted: false }, result); } needsComma = true; - writeFn(fnType, result); + await writeFn(fnType, result); } if (hofOutput.length > 1) { pushText({name: ")", highlighted: false}, result); @@ -3044,8 +2274,9 @@ class DocSearch { * * @param {rustdoc.HighlightedFunctionType} fnType * @param {string[]} result + * @returns {Promise<boolean>} */ - const writeSpecialPrimitive = (fnType, result) => { + const writeSpecialPrimitive = async(fnType, result) => { if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice || fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) { const [ob, sb] = @@ -3054,7 +2285,7 @@ class DocSearch { ["[", "]"] : ["(", ")"]; pushText({ name: ob, highlighted: fnType.highlighted }, result); - onEachBtwn( + await onEachBtwnAsync( fnType.generics, nested => writeFn(nested, result), // @ts-expect-error @@ -3065,11 +2296,11 @@ class DocSearch { } else if (fnType.id === this.typeNameIdOfReference) { pushText({ name: "&", highlighted: fnType.highlighted }, result); let prevHighlighted = false; - onEachBtwn( + await onEachBtwnAsync( fnType.generics, - value => { + async value => { prevHighlighted = !!value.highlighted; - writeFn(value, result); + await writeFn(value, result); }, // @ts-expect-error value => pushText({ @@ -3078,8 +2309,16 @@ class DocSearch { }, result), ); return true; - } else if (fnType.id === this.typeNameIdOfFn) { - writeHof(fnType, result); + } else if ( + fnType.id === this.typeNameIdOfFn || + fnType.id === this.typeNameIdOfFnMut || + fnType.id === this.typeNameIdOfFnOnce || + fnType.id === this.typeNameIdOfFnPtr + ) { + await writeHof(fnType, result); + return true; + } else if (fnType.id === this.typeNameIdOfNever) { + pushText({ name: "!", highlighted: fnType.highlighted }, result); return true; } return false; @@ -3091,8 +2330,9 @@ class DocSearch { * * @param {rustdoc.HighlightedFunctionType} fnType * @param {string[]} result + * @returns {Promise<void>} */ - const writeFn = (fnType, result) => { + const writeFn = async(fnType, result) => { if (fnType.id !== null && fnType.id < 0) { if (fnParamNames[-1 - fnType.id] === "") { // Normally, there's no need to shown an unhighlighted @@ -3101,7 +2341,7 @@ class DocSearch { fnType.generics : objType.where_clause[-1 - fnType.id]; for (const nested of generics) { - writeFn(nested, result); + await writeFn(nested, result); } return; } else if (mgens) { @@ -3120,7 +2360,7 @@ class DocSearch { }, result); /** @type{string[]} */ const where = []; - onEachBtwn( + await onEachBtwnAsync( fnType.generics, nested => writeFn(nested, where), // @ts-expect-error @@ -3131,32 +2371,61 @@ class DocSearch { } } else { if (fnType.ty === TY_PRIMITIVE) { - if (writeSpecialPrimitive(fnType, result)) { + if (await writeSpecialPrimitive(fnType, result)) { return; } } else if (fnType.ty === TY_TRAIT && ( fnType.id === this.typeNameIdOfFn || - fnType.id === this.typeNameIdOfFnMut || - fnType.id === this.typeNameIdOfFnOnce)) { - writeHof(fnType, result); + fnType.id === this.typeNameIdOfFnMut || + fnType.id === this.typeNameIdOfFnOnce || + fnType.id === this.typeNameIdOfFnPtr + )) { + await writeHof(fnType, result); + return; + } else if (fnType.name === "" && + fnType.bindings.size === 0 && + fnType.generics.length !== 0 + ) { + pushText({ name: "impl ", highlighted: false }, result); + if (fnType.generics.length > 1) { + pushText({ name: "(", highlighted: false }, result); + } + await onEachBtwnAsync( + fnType.generics, + value => writeFn(value, result), + // @ts-expect-error + () => pushText({ name: ", ", highlighted: false }, result), + ); + if (fnType.generics.length > 1) { + pushText({ name: ")", highlighted: false }, result); + } return; } pushText(fnType, result); let hasBindings = false; if (fnType.bindings.size > 0) { - onEachBtwn( - fnType.bindings, - ([key, values]) => { - const name = this.assocTypeIdNameMap.get(key); + await onEachBtwnAsync( + await Promise.all([...fnType.bindings.entries()].map( + /** + * @param {[number, rustdoc.HighlightedFunctionType[]]} param0 + * @returns {Promise<[ + * string|null, + * rustdoc.HighlightedFunctionType[], + * ]>} + */ + async([key, values]) => [await this.getName(key), values], + )), + async([name, values]) => { // @ts-expect-error if (values.length === 1 && values[0].id < 0 && // @ts-expect-error - `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]) { + `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id] + ) { // the internal `Item=Iterator::Item` type variable should be // shown in the where clause and name mapping output, but is // redundant in this spot for (const value of values) { - writeFn(value, []); + await writeFn(value, []); } return true; } @@ -3169,7 +2438,7 @@ class DocSearch { name: values.length !== 1 ? "=(" : "=", highlighted: false, }, result); - onEachBtwn( + await onEachBtwnAsync( values || [], value => writeFn(value, result), // @ts-expect-error @@ -3186,7 +2455,7 @@ class DocSearch { if (fnType.generics.length > 0) { pushText({ name: hasBindings ? ", " : "<", highlighted: false }, result); } - onEachBtwn( + await onEachBtwnAsync( fnType.generics, value => writeFn(value, result), // @ts-expect-error @@ -3199,14 +2468,14 @@ class DocSearch { }; /** @type {string[]} */ const type = []; - onEachBtwn( + await onEachBtwnAsync( fnInputs, fnType => writeFn(fnType, type), // @ts-expect-error () => pushText({ name: ", ", highlighted: false }, type), ); pushText({ name: " -> ", highlighted: false }, type); - onEachBtwn( + await onEachBtwnAsync( fnOutput, fnType => writeFn(fnType, type), // @ts-expect-error @@ -3217,169 +2486,252 @@ class DocSearch { }; /** - * This function takes a result map, and sorts it by various criteria, including edit - * distance, substring match, and the crate it comes from. + * Add extra data to result objects, and filter items that have been + * marked for removal. * - * @param {rustdoc.Results} results + * @param {[rustdoc.PlainResultObject, rustdoc.Row][]} results * @param {"sig"|"elems"|"returned"|null} typeInfo - * @param {string} preferredCrate - * @returns {Promise<rustdoc.ResultObject[]>} + * @param {Set<string>} duplicates + * @returns {rustdoc.ResultObject[]} */ - const sortResults = async(results, typeInfo, preferredCrate) => { - const userQuery = parsedQuery.userQuery; - const normalizedUserQuery = parsedQuery.userQuery.toLowerCase(); - const isMixedCase = normalizedUserQuery !== userQuery; - const result_list = []; - const isReturnTypeQuery = parsedQuery.elems.length === 0 || - typeInfo === "returned"; - for (const result of results.values()) { - result.item = this.searchIndex[result.id]; - result.word = this.searchIndex[result.id].word; - if (isReturnTypeQuery) { - // We are doing a return-type based search, deprioritize "clone-like" results, - // ie. functions that also take the queried type as an argument. - const resultItemType = result.item && result.item.type; - if (!resultItemType) { + const transformResults = (results, typeInfo, duplicates) => { + const out = []; + + for (const [result, item] of results) { + if (item.id !== -1) { + const res = buildHrefAndPath(item); + // many of these properties don't strictly need to be + // copied over, but copying them over satisfies tsc, + // and hopefully plays nice with the shape optimization + // of the browser engine. + /** @type {rustdoc.ResultObject} */ + const obj = Object.assign({ + parent: item.parent ? { + path: item.parent.path.modulePath, + exactPath: item.parent.path.exactModulePath || + item.parent.path.modulePath, + name: item.parent.name, + ty: item.parent.path.ty, + } : undefined, + type: item.type && item.type.functionSignature ? + item.type.functionSignature : + undefined, + paramNames: item.type && item.type.paramNames ? + item.type.paramNames : + undefined, + dist: result.dist, + path_dist: result.path_dist, + index: result.index, + desc: this.getDesc(result.id), + item, + displayPath: pathSplitter(res[0]), + fullPath: "", + href: "", + displayTypeSignature: null, + }, result); + + // To be sure than it some items aren't considered as duplicate. + obj.fullPath = res[2] + "|" + obj.item.ty; + + if (duplicates.has(obj.fullPath)) { + continue; + } + + // Exports are specifically not shown if the items they point at + // are already in the results. + if (obj.item.ty === TY_IMPORT && duplicates.has(res[2])) { + continue; + } + if (duplicates.has(res[2] + "|" + TY_IMPORT)) { continue; } - const inputs = resultItemType.inputs; - const where_clause = resultItemType.where_clause; - if (containsTypeFromQuery(inputs, where_clause)) { - result.path_dist *= 100; - result.dist *= 100; + duplicates.add(obj.fullPath); + duplicates.add(res[2]); + + if (typeInfo !== null) { + obj.displayTypeSignature = formatDisplayTypeSignature( + obj, + typeInfo, + result.elems, + result.returned, + ); + } + + obj.href = res[1]; + out.push(obj); + if (out.length >= MAX_RESULTS) { + break; } } - result_list.push(result); } - result_list.sort((aaa, bbb) => { - /** @type {number} */ - let a; - /** @type {number} */ - let b; + return out; + }; - // sort by exact case-sensitive match - if (isMixedCase) { - a = Number(aaa.item.name !== userQuery); - b = Number(bbb.item.name !== userQuery); - if (a !== b) { - return a - b; + const sortAndTransformResults = + /** + * @this {DocSearch} + * @param {Array<rustdoc.PlainResultObject|null>} results + * @param {"sig"|"elems"|"returned"|null} typeInfo + * @param {string} preferredCrate + * @param {Set<string>} duplicates + * @returns {AsyncGenerator<rustdoc.ResultObject, number>} + */ + async function*(results, typeInfo, preferredCrate, duplicates) { + const userQuery = parsedQuery.userQuery; + const normalizedUserQuery = parsedQuery.userQuery.toLowerCase(); + const isMixedCase = normalizedUserQuery !== userQuery; + /** + * @type {[rustdoc.PlainResultObject, rustdoc.Row][]} + */ + const result_list = []; + for (const result of results.values()) { + if (!result) { + continue; + } + /** + * @type {rustdoc.Row?} + */ + const item = await this.getRow(result.id); + if (!item) { + continue; + } + if (filterCrates !== null && item.crate !== filterCrates) { + continue; + } + if (item) { + result_list.push([result, item]); + } else { + continue; } } - // sort by exact match with regard to the last word (mismatch goes later) - a = Number(aaa.word !== normalizedUserQuery); - b = Number(bbb.word !== normalizedUserQuery); - if (a !== b) { - return a - b; - } + result_list.sort(([aaa, aai], [bbb, bbi]) => { + /** @type {number} */ + let a; + /** @type {number} */ + let b; + + if (typeInfo === null) { + // in name based search... + + // sort by exact case-sensitive match + if (isMixedCase) { + a = Number(aai.name !== userQuery); + b = Number(bbi.name !== userQuery); + if (a !== b) { + return a - b; + } + } - // sort by index of keyword in item name (no literal occurrence goes later) - a = Number(aaa.index < 0); - b = Number(bbb.index < 0); - if (a !== b) { - return a - b; - } + // sort by exact match with regard to the last word (mismatch goes later) + a = Number(aai.normalizedName !== normalizedUserQuery); + b = Number(bbi.normalizedName !== normalizedUserQuery); + if (a !== b) { + return a - b; + } - // in type based search, put functions first - if (parsedQuery.hasReturnArrow) { - a = Number(!isFnLikeTy(aaa.item.ty)); - b = Number(!isFnLikeTy(bbb.item.ty)); + // sort by index of keyword in item name (no literal occurrence goes later) + a = Number(aaa.index < 0); + b = Number(bbb.index < 0); + if (a !== b) { + return a - b; + } + } + + // Sort by distance in the path part, if specified + // (less changes required to match means higher rankings) + a = Number(aaa.path_dist); + b = Number(bbb.path_dist); if (a !== b) { return a - b; } - } - // Sort by distance in the path part, if specified - // (less changes required to match means higher rankings) - a = Number(aaa.path_dist); - b = Number(bbb.path_dist); - if (a !== b) { - return a - b; - } - - // (later literal occurrence, if any, goes later) - a = Number(aaa.index); - b = Number(bbb.index); - if (a !== b) { - return a - b; - } + // (later literal occurrence, if any, goes later) + a = Number(aaa.index); + b = Number(bbb.index); + if (a !== b) { + return a - b; + } - // Sort by distance in the name part, the last part of the path - // (less changes required to match means higher rankings) - a = Number(aaa.dist); - b = Number(bbb.dist); - if (a !== b) { - return a - b; - } + // Sort by distance in the name part, the last part of the path + // (less changes required to match means higher rankings) + a = Number(aaa.dist); + b = Number(bbb.dist); + if (a !== b) { + return a - b; + } - // sort deprecated items later - a = Number( - // @ts-expect-error - this.searchIndexDeprecated.get(aaa.item.crate).contains(aaa.item.bitIndex), - ); - b = Number( - // @ts-expect-error - this.searchIndexDeprecated.get(bbb.item.crate).contains(bbb.item.bitIndex), - ); - if (a !== b) { - return a - b; - } + // sort aliases lower + a = Number(aaa.is_alias); + b = Number(bbb.is_alias); + if (a !== b) { + return a - b; + } - // sort by crate (current crate comes first) - a = Number(aaa.item.crate !== preferredCrate); - b = Number(bbb.item.crate !== preferredCrate); - if (a !== b) { - return a - b; - } + // sort deprecated items later + a = Number(aai.deprecated); + b = Number(bbi.deprecated); + if (a !== b) { + return a - b; + } - // sort by item name length (longer goes later) - a = Number(aaa.word.length); - b = Number(bbb.word.length); - if (a !== b) { - return a - b; - } + // sort by crate (current crate comes first) + a = Number(aai.crate !== preferredCrate); + b = Number(bbi.crate !== preferredCrate); + if (a !== b) { + return a - b; + } - // sort by item name (lexicographically larger goes later) - let aw = aaa.word; - let bw = bbb.word; - if (aw !== bw) { - return (aw > bw ? +1 : -1); - } + // sort by item name length (longer goes later) + a = Number(aai.normalizedName.length); + b = Number(bbi.normalizedName.length); + if (a !== b) { + return a - b; + } - // sort by description (no description goes later) - a = Number( - // @ts-expect-error - this.searchIndexEmptyDesc.get(aaa.item.crate).contains(aaa.item.bitIndex), - ); - b = Number( - // @ts-expect-error - this.searchIndexEmptyDesc.get(bbb.item.crate).contains(bbb.item.bitIndex), - ); - if (a !== b) { - return a - b; - } + // sort by item name (lexicographically larger goes later) + let aw = aai.normalizedName; + let bw = bbi.normalizedName; + if (aw !== bw) { + return (aw > bw ? +1 : -1); + } - // sort by type (later occurrence in `itemTypes` goes later) - a = Number(aaa.item.ty); - b = Number(bbb.item.ty); - if (a !== b) { - return a - b; - } + // sort by description (no description goes later) + const di = this.database.getData("desc"); + if (di) { + a = Number(di.isEmpty(aaa.id)); + b = Number(di.isEmpty(bbb.id)); + if (a !== b) { + return a - b; + } + } - // sort by path (lexicographically larger goes later) - aw = aaa.item.path; - bw = bbb.item.path; - if (aw !== bw) { - return (aw > bw ? +1 : -1); - } + // sort by type (later occurrence in `itemTypes` goes later) + a = Number(aai.ty); + b = Number(bbi.ty); + if (a !== b) { + return a - b; + } - // que sera, sera - return 0; - }); + // sort by path (lexicographically larger goes later) + const ap = aai.modulePath; + const bp = bbi.modulePath; + aw = ap === undefined ? "" : ap; + bw = bp === undefined ? "" : bp; + if (aw !== bw) { + return (aw > bw ? +1 : -1); + } - return transformResults(result_list, typeInfo); - }; + // que sera, sera + return 0; + }); + + const transformed_result_list = transformResults(result_list, typeInfo, duplicates); + yield* transformed_result_list; + return transformed_result_list.length; + } + .bind(this); /** * This function checks if a list of search query `queryElems` can all be found in the @@ -3931,6 +3283,8 @@ class DocSearch { } return true; } else { + // For these special cases, matching code need added to the inverted index. + // search_index.rs -> convert_render_type does this if (queryElem.id === this.typeNameIdOfArrayOrSlice && (fnType.id === this.typeNameIdOfSlice || fnType.id === this.typeNameIdOfArray) ) { @@ -3941,10 +3295,12 @@ class DocSearch { ) { // () matches primitive:tuple or primitive:unit // if it matches, then we're fine, and this is an appropriate match candidate - } else if (queryElem.id === this.typeNameIdOfHof && - (fnType.id === this.typeNameIdOfFn || fnType.id === this.typeNameIdOfFnMut || - fnType.id === this.typeNameIdOfFnOnce) - ) { + } else if (queryElem.id === this.typeNameIdOfHof && ( + fnType.id === this.typeNameIdOfFn || + fnType.id === this.typeNameIdOfFnMut || + fnType.id === this.typeNameIdOfFnOnce || + fnType.id === this.typeNameIdOfFnPtr + )) { // -> matches fn, fnonce, and fnmut // if it matches, then we're fine, and this is an appropriate match candidate } else if (fnType.id !== queryElem.id || queryElem.id === null) { @@ -4127,21 +3483,13 @@ class DocSearch { * This function checks if the given list contains any * (non-generic) types mentioned in the query. * + * @param {rustdoc.QueryElement[]} elems * @param {rustdoc.FunctionType[]} list - A list of function types. * @param {rustdoc.FunctionType[][]} where_clause - Trait bounds for generic items. */ - function containsTypeFromQuery(list, where_clause) { + function containsTypeFromQuery(elems, list, where_clause) { if (!list) return false; - for (const ty of parsedQuery.returned) { - // negative type ids are generics - if (ty.id !== null && ty.id < 0) { - continue; - } - if (checkIfInList(list, ty, where_clause, null, 0)) { - return true; - } - } - for (const ty of parsedQuery.elems) { + for (const ty of elems) { if (ty.id !== null && ty.id < 0) { continue; } @@ -4233,10 +3581,10 @@ class DocSearch { /** * Compute an "edit distance" that ignores missing path elements. * @param {string[]} contains search query path - * @param {rustdoc.Row} ty indexed item + * @param {string[]} path indexed page path * @returns {null|number} edit distance */ - function checkPath(contains, ty) { + function checkPath(contains, path) { if (contains.length === 0) { return 0; } @@ -4244,11 +3592,6 @@ class DocSearch { contains.reduce((acc, next) => acc + next.length, 0) / 3, ); let ret_dist = maxPathEditDistance + 1; - const path = ty.path.split("::"); - - if (ty.parent && ty.parent.name) { - path.push(ty.parent.name.toLowerCase()); - } const length = path.length; const clength = contains.length; @@ -4274,7 +3617,32 @@ class DocSearch { return ret_dist > maxPathEditDistance ? null : ret_dist; } - // @ts-expect-error + /** + * Compute an "edit distance" that ignores missing path elements. + * @param {string[]} contains search query path + * @param {rustdoc.Row} row indexed item + * @returns {null|number} edit distance + */ + function checkRowPath(contains, row) { + if (contains.length === 0) { + return 0; + } + + const path = row.modulePath.split("::"); + + if (row.parent && row.parent.name) { + path.push(row.parent.name.toLowerCase()); + } + + return checkPath(contains, path); + } + + /** + * + * @param {number} filter + * @param {rustdoc.ItemType} type + * @returns + */ function typePassesFilter(filter, type) { // No filter or Exact mach if (filter <= NO_TYPE_FILTER || filter === type) return true; @@ -4296,366 +3664,839 @@ class DocSearch { return false; } - // @ts-expect-error - const handleAliases = async(ret, query, filterCrates, currentCrate) => { - const lowerQuery = query.toLowerCase(); - if (this.FOUND_ALIASES.has(lowerQuery)) { - return; - } - this.FOUND_ALIASES.add(lowerQuery); - // We separate aliases and crate aliases because we want to have current crate - // aliases to be before the others in the displayed results. - // @ts-expect-error - const aliases = []; - // @ts-expect-error - const crateAliases = []; - if (filterCrates !== null) { - if (this.ALIASES.has(filterCrates) - && this.ALIASES.get(filterCrates).has(lowerQuery)) { - const query_aliases = this.ALIASES.get(filterCrates).get(lowerQuery); - for (const alias of query_aliases) { - aliases.push(alias); - } + const innerRunNameQuery = + /** + * @this {DocSearch} + * @param {string} currentCrate + * @returns {AsyncGenerator<rustdoc.ResultObject>} + */ + async function*(currentCrate) { + const index = this.database.getIndex("normalizedName"); + if (!index) { + return; } - } else { - for (const [crate, crateAliasesIndex] of this.ALIASES) { - if (crateAliasesIndex.has(lowerQuery)) { - // @ts-expect-error - const pushTo = crate === currentCrate ? crateAliases : aliases; - const query_aliases = crateAliasesIndex.get(lowerQuery); - for (const alias of query_aliases) { - pushTo.push(alias); + const idDuplicates = new Set(); + const pathDuplicates = new Set(); + let count = 0; + const prefixResults = []; + const normalizedUserQuery = parsedQuery.userQuery + .replace(/[_"]/g, "") + .toLowerCase(); + /** + * @param {string} name + * @param {number} alias + * @param {number} dist + * @param {number} index + * @returns {Promise<rustdoc.PlainResultObject?>} + */ + const handleAlias = async(name, alias, dist, index) => { + return { + id: alias, + dist, + path_dist: 0, + index, + alias: name, + is_alias: true, + elems: [], // only used in type-based queries + returned: [], // only used in type-based queries + original: await this.getRow(alias), + }; + }; + /** + * @param {Promise<rustdoc.PlainResultObject|null>[]} data + * @returns {AsyncGenerator<rustdoc.ResultObject, boolean>} + */ + const flush = async function* (data) { + const satr = sortAndTransformResults( + await Promise.all(data), + null, + currentCrate, + pathDuplicates, + ); + data.length = 0; + for await (const processed of satr) { + yield processed; + count += 1; + if ((count & 0x7F) === 0) { + await yieldToBrowser(); + } + if (count >= MAX_RESULTS) { + return true; } } - } - } - - // @ts-expect-error - const sortFunc = (aaa, bbb) => { - if (aaa.original.path < bbb.original.path) { - return 1; - } else if (aaa.original.path === bbb.original.path) { - return 0; - } - return -1; - }; - // @ts-expect-error - crateAliases.sort(sortFunc); - aliases.sort(sortFunc); - - // @ts-expect-error - const pushFunc = alias => { - // Cloning `alias` to prevent its fields to be updated. - alias = {...alias}; - const res = buildHrefAndPath(alias); - alias.displayPath = pathSplitter(res[0]); - alias.fullPath = alias.displayPath + alias.name; - alias.href = res[1]; - - ret.others.unshift(alias); - if (ret.others.length > MAX_RESULTS) { - ret.others.pop(); - } - }; - - aliases.forEach(pushFunc); - // @ts-expect-error - crateAliases.forEach(pushFunc); - }; - - /** - * This function adds the given result into the provided `results` map if it matches the - * following condition: - * - * * If it is a "literal search" (`parsedQuery.literalSearch`), then `dist` must be 0. - * * If it is not a "literal search", `dist` must be <= `maxEditDistance`. - * - * The `results` map contains information which will be used to sort the search results: - * - * * `fullId` is an `integer`` used as the key of the object we use for the `results` map. - * * `id` is the index in the `searchIndex` array for this element. - * * `index` is an `integer`` used to sort by the position of the word in the item's name. - * * `dist` is the main metric used to sort the search results. - * * `path_dist` is zero if a single-component search query is used, otherwise it's the - * distance computed for everything other than the last path component. - * - * @param {rustdoc.Results} results - * @param {number} fullId - * @param {number} id - * @param {number} index - * @param {number} dist - * @param {number} path_dist - * @param {number} maxEditDistance - */ - function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) { - if (dist <= maxEditDistance || index !== -1) { - if (results.has(fullId)) { - const result = results.get(fullId); - if (result === undefined || result.dontValidate || result.dist <= dist) { - return; + return false; + }; + const aliasResults = await index.search(normalizedUserQuery); + if (aliasResults) { + for (const id of aliasResults.matches().entries()) { + const [name, alias] = await Promise.all([ + this.getName(id), + this.getAliasTarget(id), + ]); + if (name !== null && + alias !== null && + !idDuplicates.has(id) && + name.replace(/[_"]/g, "").toLowerCase() === normalizedUserQuery + ) { + prefixResults.push(handleAlias(name, alias, 0, 0)); + idDuplicates.add(id); + } } } - // @ts-expect-error - results.set(fullId, { - id: id, - index: index, - dontValidate: parsedQuery.literalSearch, - dist: dist, - path_dist: path_dist, - }); - } - } - - /** - * This function is called in case the query has more than one element. In this case, it'll - * try to match the items which validates all the elements. For `aa -> bb` will look for - * functions which have a parameter `aa` and has `bb` in its returned values. - * - * @param {rustdoc.Row} row - * @param {number} pos - Position in the `searchIndex`. - * @param {rustdoc.Results} results - */ - function handleArgs(row, pos, results) { - if (!row || (filterCrates !== null && row.crate !== filterCrates)) { - return; - } - const rowType = row.type; - if (!rowType) { - return; - } - - const tfpDist = compareTypeFingerprints( - row.id, - parsedQuery.typeFingerprint, - ); - if (tfpDist === null) { - return; - } - // @ts-expect-error - if (results.size >= MAX_RESULTS && tfpDist > results.max_dist) { - return; - } - - // If the result is too "bad", we return false and it ends this search. - if (!unifyFunctionTypes( - rowType.inputs, - parsedQuery.elems, - rowType.where_clause, - null, - // @ts-expect-error - mgens => { - return unifyFunctionTypes( - rowType.output, - parsedQuery.returned, - rowType.where_clause, - mgens, - checkTypeMgensForConflict, - 0, // unboxing depth - ); - }, - 0, // unboxing depth - )) { - return; - } - - results.max_dist = Math.max(results.max_dist || 0, tfpDist); - addIntoResults(results, row.id, pos, 0, tfpDist, 0, Number.MAX_VALUE); - } - - /** - * Compare the query fingerprint with the function fingerprint. - * - * @param {number} fullId - The function - * @param {Uint32Array} queryFingerprint - The query - * @returns {number|null} - Null if non-match, number if distance - * This function might return 0! - */ - const compareTypeFingerprints = (fullId, queryFingerprint) => { - const fh0 = this.functionTypeFingerprint[fullId * 4]; - const fh1 = this.functionTypeFingerprint[(fullId * 4) + 1]; - const fh2 = this.functionTypeFingerprint[(fullId * 4) + 2]; - const [qh0, qh1, qh2] = queryFingerprint; - // Approximate set intersection with bloom filters. - // This can be larger than reality, not smaller, because hashes have - // the property that if they've got the same value, they hash to the - // same thing. False positives exist, but not false negatives. - const [in0, in1, in2] = [fh0 & qh0, fh1 & qh1, fh2 & qh2]; - // Approximate the set of items in the query but not the function. - // This might be smaller than reality, but cannot be bigger. - // - // | in_ | qh_ | XOR | Meaning | - // | --- | --- | --- | ------------------------------------------------ | - // | 0 | 0 | 0 | Not present | - // | 1 | 0 | 1 | IMPOSSIBLE because `in_` is `fh_ & qh_` | - // | 1 | 1 | 0 | If one or both is false positive, false negative | - // | 0 | 1 | 1 | Since in_ has no false negatives, must be real | - if ((in0 ^ qh0) || (in1 ^ qh1) || (in2 ^ qh2)) { - return null; - } - return this.functionTypeFingerprint[(fullId * 4) + 3]; - }; - - - const innerRunQuery = () => { - if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) { + if (parsedQuery.error !== null || parsedQuery.elems.length === 0) { + yield* flush(prefixResults); + return; + } const elem = parsedQuery.elems[0]; - // use arrow functions to preserve `this`. - /** @type {function(number): void} */ - const handleNameSearch = id => { - const row = this.searchIndex[id]; - if (!typePassesFilter(elem.typeFilter, row.ty) || + const typeFilter = itemTypeFromName(elem.typeFilter); + /** + * @param {number} id + * @returns {Promise<rustdoc.PlainResultObject?>} + */ + const handleNameSearch = async id => { + const row = await this.getRow(id); + if (!row || !row.entry) { + return null; + } + if (!typePassesFilter(typeFilter, row.ty) || (filterCrates !== null && row.crate !== filterCrates)) { - return; + return null; } + /** @type {number|null} */ let pathDist = 0; if (elem.fullPath.length > 1) { - - const maybePathDist = checkPath(elem.pathWithoutLast, row); - if (maybePathDist === null) { - return; + pathDist = checkRowPath(elem.pathWithoutLast, row); + if (pathDist === null) { + return null; } - pathDist = maybePathDist; } if (parsedQuery.literalSearch) { - if (row.word === elem.pathLast) { - addIntoResults(results_others, row.id, id, 0, 0, pathDist, 0); - } + return row.name.toLowerCase() === elem.pathLast ? { + id, + dist: 0, + path_dist: 0, + index: 0, + elems: [], // only used in type-based queries + returned: [], // only used in type-based queries + is_alias: false, + } : null; } else { - addIntoResults( - results_others, - row.id, + return { id, - row.normalizedName.indexOf(elem.normalizedPathLast), - editDistance( + dist: editDistance( row.normalizedName, elem.normalizedPathLast, maxEditDistance, ), - pathDist, - maxEditDistance, + path_dist: pathDist, + index: row.normalizedName.indexOf(elem.normalizedPathLast), + elems: [], // only used in type-based queries + returned: [], // only used in type-based queries + is_alias: false, + }; + } + }; + if (elem.normalizedPathLast === "") { + // faster full-table scan for this specific case. + const nameData = this.database.getData("name"); + const l = nameData ? nameData.length : 0; + for (let id = 0; id < l; ++id) { + if (!idDuplicates.has(id)) { + idDuplicates.add(id); + prefixResults.push(handleNameSearch(id)); + } + if (yield* flush(prefixResults)) { + return; + } + } + return; + } + const results = await index.search(elem.normalizedPathLast); + if (results) { + for await (const result of results.prefixMatches()) { + for (const id of result.entries()) { + if (!idDuplicates.has(id)) { + idDuplicates.add(id); + prefixResults.push(handleNameSearch(id)); + const [name, alias] = await Promise.all([ + this.getName(id), + this.getAliasTarget(id), + ]); + if (name !== null && alias !== null) { + prefixResults.push(handleAlias(name, alias, 0, 0)); + } + } + } + if (yield* flush(prefixResults)) { + return; + } + } + if (yield* flush(prefixResults)) { + return; + } + } + const levSearchResults = index.searchLev(elem.normalizedPathLast); + const levResults = []; + for await (const levResult of levSearchResults) { + for (const id of levResult.matches().entries()) { + if (!idDuplicates.has(id)) { + idDuplicates.add(id); + levResults.push(handleNameSearch(id)); + const [name, alias] = await Promise.all([ + this.getName(id), + this.getAliasTarget(id), + ]); + if (name !== null && alias !== null) { + levResults.push(handleAlias( + name, + alias, + editDistance(elem.normalizedPathLast, name, maxEditDistance), + name.indexOf(elem.normalizedPathLast), + )); + } + } + } + } + yield* flush(levResults); + if (results) { + const substringResults = []; + for await (const result of results.substringMatches()) { + for (const id of result.entries()) { + if (!idDuplicates.has(id)) { + idDuplicates.add(id); + substringResults.push(handleNameSearch(id)); + const [name, alias] = await Promise.all([ + this.getName(id), + this.getAliasTarget(id), + ]); + if (name !== null && alias !== null) { + levResults.push(handleAlias( + name, + alias, + editDistance( + elem.normalizedPathLast, + name, + maxEditDistance, + ), + name.indexOf(elem.normalizedPathLast), + )); + } + } + } + if (yield* flush(substringResults)) { + return; + } + } + } + } + .bind(this); + + const innerRunTypeQuery = + /** + * @this {DocSearch} + * @param {rustdoc.ParserQueryElement[]} inputs + * @param {rustdoc.ParserQueryElement[]} output + * @param {"sig"|"elems"|"returned"|null} typeInfo + * @param {string} currentCrate + * @returns {AsyncGenerator<rustdoc.ResultObject>} + */ + async function*(inputs, output, typeInfo, currentCrate) { + const index = this.database.getIndex("normalizedName"); + if (!index) { + return; + } + /** @type {Map<string, number>} */ + const genericMap = new Map(); + /** + * @template Q + * @typedef {{ + * invertedIndex: stringdex.RoaringBitmap[], + * queryElem: Q, + * }} PostingsList + */ + /** @type {stringdex.RoaringBitmap[]} */ + const empty_inverted_index = []; + /** @type {PostingsList<any>[]} */ + const empty_postings_list = []; + /** @type {stringdex.RoaringBitmap[]} */ + const everything_inverted_index = []; + for (let i = 0; i < 64; ++i) { + everything_inverted_index.push(RoaringBitmap.everything()); + } + /** + * @type {PostingsList<rustdoc.QueryElement[]>} + */ + const everything_postings_list = { + invertedIndex: everything_inverted_index, + queryElem: [], + }; + /** + * @type {PostingsList<rustdoc.QueryElement[]>[]} + */ + const nested_everything_postings_list = [everything_postings_list]; + /** + * @param {...stringdex.RoaringBitmap[]} idx + * @returns {stringdex.RoaringBitmap[]} + */ + const intersectInvertedIndexes = (...idx) => { + let i = 0; + const l = idx.length; + while (i < l - 1 && idx[i] === everything_inverted_index) { + i += 1; + } + const result = [...idx[i]]; + for (; i < l; ++i) { + if (idx[i] === everything_inverted_index) { + continue; + } + if (idx[i].length < result.length) { + result.length = idx[i].length; + } + for (let j = 0; j < result.length; ++j) { + result[j] = result[j].intersection(idx[i][j]); + } + } + return result; + }; + /** + * Fetch a bitmap of potentially-matching functions, + * plus a list of query elements annotated with the correct IDs. + * + * More than one ID can exist because, for example, q=`Iter` can match + * `std::vec::Iter`, or `std::btree_set::Iter`, or anything else, and those + * items different IDs. What's worse, q=`Iter<Iter>` has N**2 possible + * matches, because it could be `vec::Iter<btree_set::Iter>`, + * `btree_set::Iter<vec::Iter>`, `vec::Iter<vec::Iter>`, + * `btree_set::Iter<btree_set::Iter>`, + * or anything else. This function returns all possible permutations. + * + * @param {rustdoc.ParserQueryElement|null} elem + * @returns {Promise<PostingsList<rustdoc.QueryElement>[]>} + */ + const unpackPostingsList = async elem => { + if (!elem) { + return empty_postings_list; + } + const typeFilter = itemTypeFromName(elem.typeFilter); + const searchResults = await index.search(elem.normalizedPathLast); + /** + * @type {Promise<[ + * number, + * string|null, + * rustdoc.TypeData|null, + * rustdoc.PathData|null, + * ]>[]} + * */ + const typePromises = []; + if (typeFilter !== TY_GENERIC && searchResults) { + for (const id of searchResults.matches().entries()) { + typePromises.push(Promise.all([ + this.getName(id), + this.getTypeData(id), + this.getPathData(id), + ]).then(([name, typeData, pathData]) => + [id, name, typeData, pathData])); + } + } + const types = (await Promise.all(typePromises)) + .filter(([_id, name, ty, path]) => + name !== null && name.toLowerCase() === elem.pathLast && + ty && !ty.invertedFunctionSignatureIndex.every(bitmap => { + return bitmap.isEmpty(); + }) && + path && path.ty !== TY_ASSOCTYPE && + (elem.pathWithoutLast.length === 0 || + checkPath( + elem.pathWithoutLast, + path.modulePath.split("::"), + ) === 0), + ); + if (types.length === 0) { + const areGenericsAllowed = typeFilter === TY_GENERIC || ( + typeFilter === -1 && + (parsedQuery.totalElems > 1 || parsedQuery.hasReturnArrow) && + elem.pathWithoutLast.length === 0 && + elem.generics.length === 0 && + elem.bindings.size === 0 ); + if (typeFilter !== TY_GENERIC && + (elem.name.length >= 3 || !areGenericsAllowed) + ) { + /** @type {string|null} */ + let chosenName = null; + /** @type {rustdoc.TypeData[]} */ + let chosenType = []; + /** @type {rustdoc.PathData[]} */ + let chosenPath = []; + /** @type {number[]} */ + let chosenId = []; + let chosenDist = Number.MAX_SAFE_INTEGER; + const levResults = index.searchLev(elem.normalizedPathLast); + for await (const searchResults of levResults) { + for (const id of searchResults.matches().entries()) { + const [name, ty, path] = await Promise.all([ + this.getName(id), + this.getTypeData(id), + this.getPathData(id), + ]); + if (name !== null && ty !== null && path !== null && + !ty.invertedFunctionSignatureIndex.every(bitmap => { + return bitmap.isEmpty(); + }) && + path.ty !== TY_ASSOCTYPE + ) { + let dist = editDistance( + name, + elem.pathLast, + maxEditDistance, + ); + if (elem.pathWithoutLast.length !== 0) { + const pathDist = checkPath( + elem.pathWithoutLast, + path.modulePath.split("::"), + ); + // guaranteed to be higher than the path limit + dist += pathDist === null ? + Number.MAX_SAFE_INTEGER : + pathDist; + } + if (name === chosenName) { + chosenId.push(id); + chosenType.push(ty); + chosenPath.push(path); + } else if (dist < chosenDist) { + chosenName = name; + chosenId = [id]; + chosenType = [ty]; + chosenPath = [path]; + chosenDist = dist; + } + } + } + if (chosenId.length !== 0) { + // searchLev returns results in order + // if we have working matches, we're done + break; + } + } + if (areGenericsAllowed) { + parsedQuery.proposeCorrectionFrom = elem.name; + parsedQuery.proposeCorrectionTo = chosenName; + } else { + parsedQuery.correction = chosenName; + for (let i = 0; i < chosenType.length; ++i) { + types.push([ + chosenId[i], + chosenName, + chosenType[i], + chosenPath[i], + ]); + } + } + } + if (areGenericsAllowed) { + let genericId = genericMap.get(elem.normalizedPathLast); + if (genericId === undefined) { + genericId = genericMap.size; + genericMap.set(elem.normalizedPathLast, genericId); + } + return [{ + invertedIndex: await this.getGenericInvertedIndex(genericId), + queryElem: { + name: elem.name, + id: (-genericId) - 1, + typeFilter: TY_GENERIC, + generics: [], + bindings: EMPTY_BINDINGS_MAP, + fullPath: elem.fullPath, + pathLast: elem.pathLast, + normalizedPathLast: elem.normalizedPathLast, + pathWithoutLast: elem.pathWithoutLast, + }, + }]; + } + } + types.sort(([_i, name1, _t, pathData1], [_i2, name2, _t2, pathData2]) => { + const p1 = !pathData1 ? "" : pathData1.modulePath; + const p2 = !pathData2 ? "" : pathData2.modulePath; + const n1 = name1 === null ? "" : name1; + const n2 = name2 === null ? "" : name2; + if (p1.length !== p2.length) { + return p1.length > p2.length ? +1 : -1; + } + if (n1.length !== n2.length) { + return n1.length > n2.length ? +1 : -1; + } + if (n1 !== n2) { + return n1 > n2 ? +1 : -1; + } + if (p1 !== p2) { + return p1 > p2 ? +1 : -1; + } + return 0; + }); + /** @type {PostingsList<rustdoc.QueryElement>[]} */ + const results = []; + for (const [id, _name, typeData] of types) { + if (!typeData || typeData.invertedFunctionSignatureIndex.every(bitmap => { + return bitmap.isEmpty(); + })) { + continue; + } + const upla = await unpackPostingsListAll(elem.generics); + const uplb = await unpackPostingsListBindings(elem.bindings); + for (const {invertedIndex: genericsIdx, queryElem: generics} of upla) { + for (const {invertedIndex: bindingsIdx, queryElem: bindings} of uplb) { + results.push({ + invertedIndex: intersectInvertedIndexes( + typeData.invertedFunctionSignatureIndex, + genericsIdx, + bindingsIdx, + ), + queryElem: { + name: elem.name, + id, + typeFilter, + generics, + bindings, + fullPath: elem.fullPath, + pathLast: elem.pathLast, + normalizedPathLast: elem.normalizedPathLast, + pathWithoutLast: elem.pathWithoutLast, + }, + }); + if ((results.length & 0x7F) === 0) { + await yieldToBrowser(); + } + } + } + } + return results; + }; + /** + * Fetch all possible matching permutations of a list of query elements. + * + * The empty list returns an "identity postings list", with a bitmap that + * matches everything and an empty list of elems. This allows you to safely + * take the intersection of this bitmap. + * + * @param {(rustdoc.ParserQueryElement|null)[]|null} elems + * @returns {Promise<PostingsList<rustdoc.QueryElement[]>[]>} + */ + const unpackPostingsListAll = async elems => { + if (!elems || elems.length === 0) { + return nested_everything_postings_list; + } + const [firstPostingsList, remainingAll] = await Promise.all([ + unpackPostingsList(elems[0]), + unpackPostingsListAll(elems.slice(1)), + ]); + /** @type {PostingsList<rustdoc.QueryElement[]>[]} */ + const results = []; + for (const { + invertedIndex: firstIdx, + queryElem: firstElem, + } of firstPostingsList) { + for (const { + invertedIndex: remainingIdx, + queryElem: remainingElems, + } of remainingAll) { + results.push({ + invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx), + queryElem: [firstElem, ...remainingElems], + }); + if ((results.length & 0x7F) === 0) { + await yieldToBrowser(); + } + } + } + return results; + }; + /** + * Fetch all possible matching permutations of a map query element bindings. + * + * The empty list returns an "identity postings list", with a bitmap that + * matches everything and an empty list of elems. This allows you to safely + * take the intersection of this bitmap. + * + * Heads up! This function mutates the Map that you provide. + * Before passing an actual parser item to it, make sure to clone the map. + * + * @param {Map<string, rustdoc.ParserQueryElement[]>} elems + * @returns {Promise<PostingsList< + * Map<number, rustdoc.QueryElement[]>, + * >[]>} + */ + const unpackPostingsListBindings = async elems => { + if (!elems) { + return [{ + invertedIndex: everything_inverted_index, + queryElem: new Map(), + }]; + } + const firstKey = elems.keys().next().value; + if (firstKey === undefined) { + return [{ + invertedIndex: everything_inverted_index, + queryElem: new Map(), + }]; + } + const firstList = elems.get(firstKey); + if (firstList === undefined) { + return [{ + invertedIndex: everything_inverted_index, + queryElem: new Map(), + }]; + } + const firstKeyIds = await index.search(firstKey); + if (!firstKeyIds) { + // User specified a non-existent key. + return [{ + invertedIndex: empty_inverted_index, + queryElem: new Map(), + }]; + } + elems.delete(firstKey); + const [firstPostingsList, remainingAll] = await Promise.all([ + unpackPostingsListAll(firstList), + unpackPostingsListBindings(elems), + ]); + /** @type {PostingsList<Map<number, rustdoc.QueryElement[]>>[]} */ + const results = []; + for (const keyId of firstKeyIds.matches().entries()) { + for (const { + invertedIndex: firstIdx, + queryElem: firstElem, + } of firstPostingsList) { + for (const { + invertedIndex: remainingIdx, + queryElem: remainingElems, + } of remainingAll) { + const elems = new Map(remainingElems); + elems.set(keyId, firstElem); + results.push({ + invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx), + queryElem: elems, + }); + if ((results.length & 0x7F) === 0) { + await yieldToBrowser(); + } + } + } } + elems.set(firstKey, firstList); + if (results.length === 0) { + // User specified a non-existent key. + return [{ + invertedIndex: empty_inverted_index, + queryElem: new Map(), + }]; + } + return results; }; - if (elem.normalizedPathLast !== "") { - const last = elem.normalizedPathLast; - for (const id of this.nameTrie.search(last, this.tailTable)) { - handleNameSearch(id); + + // finally, we can do the actual unification loop + const [allInputs, allOutput] = await Promise.all([ + unpackPostingsListAll(inputs), + unpackPostingsListAll(output), + ]); + let checkCounter = 0; + /** + * Finally, we can perform an incremental search, sorted by the number of + * entries that match a given query. + * + * The outer list gives the number of elements. The inner one is separate + * for each distinct name resolution. + * + * @type {{ + * bitmap: stringdex.RoaringBitmap, + * inputs: rustdoc.QueryElement[], + * output: rustdoc.QueryElement[], + * }[][]} + */ + const queryPlan = []; + for (const {invertedIndex: inputsIdx, queryElem: inputs} of allInputs) { + for (const {invertedIndex: outputIdx, queryElem: output} of allOutput) { + const invertedIndex = intersectInvertedIndexes(inputsIdx, outputIdx); + for (const [size, bitmap] of invertedIndex.entries()) { + checkCounter += 1; + if ((checkCounter & 0x7F) === 0) { + await yieldToBrowser(); + } + if (!queryPlan[size]) { + queryPlan[size] = []; + } + queryPlan[size].push({ + bitmap, inputs, output, + }); + } } } - const length = this.searchIndex.length; - - for (let i = 0, nSearchIndex = length; i < nSearchIndex; ++i) { - // queries that end in :: bypass the trie - if (elem.normalizedPathLast === "") { - handleNameSearch(i); - } - const row = this.searchIndex[i]; - if (filterCrates !== null && row.crate !== filterCrates) { - continue; - } - const tfpDist = compareTypeFingerprints( - row.id, - parsedQuery.typeFingerprint, - ); - if (tfpDist !== null) { - const in_args = row.type && row.type.inputs - && checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0); - const returned = row.type && row.type.output - && checkIfInList(row.type.output, elem, row.type.where_clause, null, 0); - if (in_args) { - results_in_args.max_dist = Math.max( - results_in_args.max_dist || 0, - tfpDist, - ); - const maxDist = results_in_args.size < MAX_RESULTS ? - (tfpDist + 1) : - results_in_args.max_dist; - addIntoResults(results_in_args, row.id, i, -1, tfpDist, 0, maxDist); + const resultPromises = []; + const dedup = new Set(); + let resultCounter = 0; + const isReturnTypeQuery = inputs.length === 0; + /** @type {rustdoc.PlainResultObject[]} */ + const pushToBottom = []; + plan: for (const queryStep of queryPlan) { + for (const {bitmap, inputs, output} of queryStep) { + for (const id of bitmap.entries()) { + checkCounter += 1; + if ((checkCounter & 0x7F) === 0) { + await yieldToBrowser(); + } + resultPromises.push(this.getFunctionData(id).then(async fnData => { + if (!fnData || !fnData.functionSignature) { + return null; + } + checkCounter += 1; + if ((checkCounter & 0x7F) === 0) { + await yieldToBrowser(); + } + const functionSignature = fnData.functionSignature; + if (!unifyFunctionTypes( + functionSignature.inputs, + inputs, + functionSignature.where_clause, + null, + mgens => { + return !!unifyFunctionTypes( + functionSignature.output, + output, + functionSignature.where_clause, + mgens, + checkTypeMgensForConflict, + 0, // unboxing depth + ); + }, + 0, // unboxing depth + )) { + return null; + } + const result = { + id, + dist: fnData.elemCount, + path_dist: 0, + index: -1, + elems: inputs, + returned: output, + is_alias: false, + }; + const entry = await this.getEntryData(id); + if ((entry && !isFnLikeTy(entry.ty)) || + (isReturnTypeQuery && + functionSignature && + containsTypeFromQuery( + output, + functionSignature.inputs, + functionSignature.where_clause, + ) + ) + ) { + pushToBottom.push(result); + return null; + } + return result; + })); } - if (returned) { - results_returned.max_dist = Math.max( - results_returned.max_dist || 0, - tfpDist, - ); - const maxDist = results_returned.size < MAX_RESULTS ? - (tfpDist + 1) : - results_returned.max_dist; - addIntoResults(results_returned, row.id, i, -1, tfpDist, 0, maxDist); + } + for await (const result of sortAndTransformResults( + await Promise.all(resultPromises), + typeInfo, + currentCrate, + dedup, + )) { + if (resultCounter >= MAX_RESULTS) { + break plan; } + yield result; + resultCounter += 1; } + resultPromises.length = 0; } - } else if (parsedQuery.foundElems > 0) { - // Sort input and output so that generic type variables go first and - // types with generic parameters go last. - // That's because of the way unification is structured: it eats off - // the end, and hits a fast path if the last item is a simple atom. - /** @type {function(rustdoc.QueryElement, rustdoc.QueryElement): number} */ - const sortQ = (a, b) => { - const ag = a.generics.length === 0 && a.bindings.size === 0; - const bg = b.generics.length === 0 && b.bindings.size === 0; - if (ag !== bg) { - // unary `+` converts booleans into integers. - return +ag - +bg; + if (resultCounter >= MAX_RESULTS) { + return; + } + for await (const result of sortAndTransformResults( + await Promise.all(pushToBottom), + typeInfo, + currentCrate, + dedup, + )) { + if (resultCounter >= MAX_RESULTS) { + break; } - const ai = a.id !== null && a.id > 0; - const bi = b.id !== null && b.id > 0; - return +ai - +bi; - }; - parsedQuery.elems.sort(sortQ); - parsedQuery.returned.sort(sortQ); - for (let i = 0, nSearchIndex = this.searchIndex.length; i < nSearchIndex; ++i) { - handleArgs(this.searchIndex[i], i, results_others); + yield result; + resultCounter += 1; } } - }; - - if (parsedQuery.error === null) { - innerRunQuery(); - } - - const isType = parsedQuery.foundElems !== 1 || parsedQuery.hasReturnArrow; - const [sorted_in_args, sorted_returned, sorted_others] = await Promise.all([ - sortResults(results_in_args, "elems", currentCrate), - sortResults(results_returned, "returned", currentCrate), - // @ts-expect-error - sortResults(results_others, (isType ? "query" : null), currentCrate), - ]); - const ret = createQueryResults( - sorted_in_args, - sorted_returned, - sorted_others, - parsedQuery); - await handleAliases(ret, parsedQuery.userQuery.replace(/"/g, ""), - filterCrates, currentCrate); - await Promise.all([ret.others, ret.returned, ret.in_args].map(async list => { - const descs = await Promise.all(list.map(result => { - // @ts-expect-error - return this.searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex) ? - "" : - // @ts-expect-error - this.searchState.loadDesc(result); - })); - for (const [i, result] of list.entries()) { - // @ts-expect-error - result.desc = descs[i]; - } - })); - if (parsedQuery.error !== null && ret.others.length !== 0) { - // It means some doc aliases were found so let's "remove" the error! - ret.query.error = null; + .bind(this); + + if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) { + // We never want the main tab to delay behind the other two tabs. + // This is a bit of a hack (because JS's scheduler doesn't have much of an API), + // along with making innerRunTypeQuery yield to the UI thread. + const { + promise: donePromise, + resolve: doneResolve, + reject: doneReject, + } = Promise.withResolvers(); + const doneTimeout = timeout(250); + return { + "in_args": (async function*() { + await Promise.race([donePromise, doneTimeout]); + yield* innerRunTypeQuery(parsedQuery.elems, [], "elems", currentCrate); + })(), + "returned": (async function*() { + await Promise.race([donePromise, doneTimeout]); + yield* innerRunTypeQuery([], parsedQuery.elems, "returned", currentCrate); + })(), + "others": (async function*() { + try { + yield* innerRunNameQuery(currentCrate); + doneResolve(null); + } catch (e) { + doneReject(e); + throw e; + } + })(), + "query": parsedQuery, + }; + } else if (parsedQuery.error !== null) { + return { + "in_args": (async function*() {})(), + "returned": (async function*() {})(), + "others": innerRunNameQuery(currentCrate), + "query": parsedQuery, + }; + } else { + const typeInfo = parsedQuery.elems.length === 0 ? + "returned" : ( + parsedQuery.returned.length === 0 ? "elems" : "sig" + ); + return { + "in_args": (async function*() {})(), + "returned": (async function*() {})(), + "others": parsedQuery.foundElems === 0 ? + (async function*() {})() : + innerRunTypeQuery( + parsedQuery.elems, + parsedQuery.returned, + typeInfo, + currentCrate, + ), + "query": parsedQuery, + }; } - return ret; } } // ==================== Core search logic end ==================== -/** @type {Map<string, rustdoc.RawSearchIndexCrate>} */ -let rawSearchIndex; -// @ts-expect-error +/** @type {DocSearch} */ let docSearch; const longItemTypes = [ "keyword", @@ -4755,12 +4596,8 @@ function buildUrl(search, filterCrates) { function getFilterCrates() { const elem = document.getElementById("crate-search"); - if (elem && - // @ts-expect-error - elem.value !== "all crates" && - // @ts-expect-error - window.searchIndex.has(elem.value) - ) { + // @ts-expect-error + if (elem && elem.value !== "all crates") { // @ts-expect-error return elem.value; } @@ -4770,8 +4607,7 @@ function getFilterCrates() { // @ts-expect-error function nextTab(direction) { const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length; - // @ts-expect-error - searchState.focusedByTab[searchState.currentTab] = document.activeElement; + window.searchState.focusedByTab[searchState.currentTab] = document.activeElement; printTab(next); focusSearchResult(); } @@ -4783,133 +4619,182 @@ function focusSearchResult() { document.querySelectorAll(".search-results.active a").item(0) || document.querySelectorAll("#search-tabs button").item(searchState.currentTab); searchState.focusedByTab[searchState.currentTab] = null; - if (target) { - // @ts-expect-error + if (target && target instanceof HTMLElement) { target.focus(); } } /** * Render a set of search results for a single tab. - * @param {Array<?>} array - The search results for this tab - * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} query + * @param {AsyncGenerator<rustdoc.ResultObject>} results - The search results for this tab + * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query * @param {boolean} display - True if this is the active tab + * @param {function(number, HTMLElement): any} finishedCallback + * @param {boolean} isTypeSearch + * @returns {Promise<HTMLElement>} */ -async function addTab(array, query, display) { +async function addTab(results, query, display, finishedCallback, isTypeSearch) { const extraClass = display ? " active" : ""; - const output = document.createElement( - array.length === 0 && query.error === null ? "div" : "ul", - ); - if (array.length > 0) { - output.className = "search-results " + extraClass; + /** @type {HTMLElement} */ + let output = document.createElement("ul"); + output.className = "search-results " + extraClass; - const lis = Promise.all(array.map(async item => { - const name = item.is_alias ? item.original.name : item.name; - const type = itemTypes[item.ty]; - const longType = longItemTypes[item.ty]; - const typeName = longType.length !== 0 ? `${longType}` : "?"; + let count = 0; - const link = document.createElement("a"); - link.className = "result-" + type; - link.href = item.href; + /** @type {Promise<string|null>[]} */ + const descList = []; - const resultName = document.createElement("span"); - resultName.className = "result-name"; + /** @param {rustdoc.ResultObject} obj */ + const addNextResultToOutput = async obj => { + count += 1; - resultName.insertAdjacentHTML( - "beforeend", - `<span class="typename">${typeName}</span>`); - link.appendChild(resultName); + const name = obj.item.name; + const type = itemTypes[obj.item.ty]; + const longType = longItemTypes[obj.item.ty]; + const typeName = longType.length !== 0 ? `${longType}` : "?"; - let alias = " "; - if (item.is_alias) { - alias = ` <div class="alias">\ -<b>${item.name}</b><i class="grey"> - see </i>\ + const link = document.createElement("a"); + link.className = "result-" + type; + link.href = obj.href; + + const resultName = document.createElement("span"); + resultName.className = "result-name"; + + resultName.insertAdjacentHTML( + "beforeend", + `<span class="typename">${typeName}</span>`); + link.appendChild(resultName); + + let alias = " "; + if (obj.alias !== undefined) { + alias = ` <div class="alias">\ +<b>${obj.alias}</b><i class="grey"> - see </i>\ </div>`; - } - resultName.insertAdjacentHTML( - "beforeend", - `<div class="path">${alias}\ -${item.displayPath}<span class="${type}">${name}</span>\ + } + resultName.insertAdjacentHTML( + "beforeend", + `<div class="path">${alias}\ +${obj.displayPath}<span class="${type}">${name}</span>\ </div>`); - const description = document.createElement("div"); - description.className = "desc"; - description.insertAdjacentHTML("beforeend", item.desc); - if (item.displayTypeSignature) { - const {type, mappedNames, whereClause} = await item.displayTypeSignature; - const displayType = document.createElement("div"); - // @ts-expect-error - type.forEach((value, index) => { - if (index % 2 !== 0) { - const highlight = document.createElement("strong"); - highlight.appendChild(document.createTextNode(value)); - displayType.appendChild(highlight); - } else { - displayType.appendChild(document.createTextNode(value)); + const description = document.createElement("div"); + description.className = "desc"; + obj.desc.then(desc => { + if (desc !== null) { + description.insertAdjacentHTML("beforeend", desc); + } + }); + descList.push(obj.desc); + if (obj.displayTypeSignature) { + const {type, mappedNames, whereClause} = await obj.displayTypeSignature; + const displayType = document.createElement("div"); + type.forEach((value, index) => { + if (index % 2 !== 0) { + const highlight = document.createElement("strong"); + highlight.appendChild(document.createTextNode(value)); + displayType.appendChild(highlight); + } else { + displayType.appendChild(document.createTextNode(value)); + } + }); + if (mappedNames.size > 0 || whereClause.size > 0) { + let addWhereLineFn = () => { + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode("where")); + displayType.appendChild(line); + addWhereLineFn = () => {}; + }; + for (const [qname, name] of mappedNames) { + // don't care unless the generic name is different + if (name === qname) { + continue; } - }); - if (mappedNames.size > 0 || whereClause.size > 0) { - let addWhereLineFn = () => { - const line = document.createElement("div"); - line.className = "where"; - line.appendChild(document.createTextNode("where")); - displayType.appendChild(line); - addWhereLineFn = () => {}; - }; - for (const [qname, name] of mappedNames) { - // don't care unless the generic name is different - if (name === qname) { - continue; - } - addWhereLineFn(); - const line = document.createElement("div"); - line.className = "where"; - line.appendChild(document.createTextNode(` ${qname} matches `)); - const lineStrong = document.createElement("strong"); - lineStrong.appendChild(document.createTextNode(name)); - line.appendChild(lineStrong); - displayType.appendChild(line); + addWhereLineFn(); + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode(` ${qname} matches `)); + const lineStrong = document.createElement("strong"); + lineStrong.appendChild(document.createTextNode(name)); + line.appendChild(lineStrong); + displayType.appendChild(line); + } + for (const [name, innerType] of whereClause) { + // don't care unless there's at least one highlighted entry + if (innerType.length <= 1) { + continue; } - for (const [name, innerType] of whereClause) { - // don't care unless there's at least one highlighted entry - if (innerType.length <= 1) { - continue; + addWhereLineFn(); + const line = document.createElement("div"); + line.className = "where"; + line.appendChild(document.createTextNode(` ${name}: `)); + innerType.forEach((value, index) => { + if (index % 2 !== 0) { + const highlight = document.createElement("strong"); + highlight.appendChild(document.createTextNode(value)); + line.appendChild(highlight); + } else { + line.appendChild(document.createTextNode(value)); } - addWhereLineFn(); - const line = document.createElement("div"); - line.className = "where"; - line.appendChild(document.createTextNode(` ${name}: `)); - // @ts-expect-error - innerType.forEach((value, index) => { - if (index % 2 !== 0) { - const highlight = document.createElement("strong"); - highlight.appendChild(document.createTextNode(value)); - line.appendChild(highlight); - } else { - line.appendChild(document.createTextNode(value)); - } - }); - displayType.appendChild(line); - } + }); + displayType.appendChild(line); } - displayType.className = "type-signature"; - link.appendChild(displayType); } + displayType.className = "type-signature"; + link.appendChild(displayType); + } + + link.appendChild(description); + output.appendChild(link); - link.appendChild(description); - return link; - })); - lis.then(lis => { - for (const li of lis) { - output.appendChild(li); + results.next().then(async nextResult => { + if (nextResult.value) { + addNextResultToOutput(nextResult.value); + } else { + await Promise.all(descList); + // need to make sure the element is shown before + // running this callback + yieldToBrowser().then(() => finishedCallback(count, output)); } }); - } else if (query.error === null) { - const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`; + }; + const firstResult = await results.next(); + let correctionOutput = ""; + if (query.correction !== null && isTypeSearch) { + const orig = query.returned.length > 0 + ? query.returned[0].name + : query.elems[0].name; + correctionOutput = "<h3 class=\"search-corrections\">" + + `Type "${orig}" not found. ` + + "Showing results for closest type name " + + `"${query.correction}" instead.</h3>`; + } + if (query.proposeCorrectionFrom !== null && isTypeSearch) { + const orig = query.proposeCorrectionFrom; + const targ = query.proposeCorrectionTo; + correctionOutput = "<h3 class=\"search-corrections\">" + + `Type "${orig}" not found and used as generic parameter. ` + + `Consider searching for "${targ}" instead.</h3>`; + } + if (firstResult.value) { + if (correctionOutput !== "") { + const h3 = document.createElement("h3"); + h3.innerHTML = correctionOutput; + output.appendChild(h3); + } + await addNextResultToOutput(firstResult.value); + } else { + output = document.createElement("div"); + if (correctionOutput !== "") { + const h3 = document.createElement("h3"); + h3.innerHTML = correctionOutput; + output.appendChild(h3); + } output.className = "search-failed" + extraClass; - output.innerHTML = "No results :(<br/>" + + const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`; + if (query.userQuery !== "") { + output.innerHTML += "No results :(<br/>" + "Try on <a href=\"https://duckduckgo.com/?q=" + encodeURIComponent("rust " + query.userQuery) + "\">DuckDuckGo</a>?<br/><br/>" + @@ -4922,192 +4807,198 @@ ${item.displayPath}<span class="${type}">${name}</span>\ "introductions to language features and the language itself.</li><li><a " + "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" + " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>"; + } + output.innerHTML += "Example searches:<ul>" + + "<li><a href=\"" + getNakedUrl() + "?search=std::vec\">std::vec</a></li>" + + "<li><a href=\"" + getNakedUrl() + "?search=u32+->+bool\">u32 -> bool</a></li>" + + "<li><a href=\"" + getNakedUrl() + "?search=Option<T>,+(T+->+U)+->+Option<U>\">" + + "Option<T>, (T -> U) -> Option<U></a></li>" + + "</ul>"; + // need to make sure the element is shown before + // running this callback + yieldToBrowser().then(() => finishedCallback(0, output)); } return output; } -// @ts-expect-error -function makeTabHeader(tabNb, text, nbElems) { - // https://blog.horizon-eda.org/misc/2020/02/19/ui.html - // - // CSS runs with `font-variant-numeric: tabular-nums` to ensure all - // digits are the same width. \u{2007} is a Unicode space character - // that is defined to be the same width as a digit. - const fmtNbElems = - nbElems < 10 ? `\u{2007}(${nbElems})\u{2007}\u{2007}` : - nbElems < 100 ? `\u{2007}(${nbElems})\u{2007}` : `\u{2007}(${nbElems})`; - if (searchState.currentTab === tabNb) { - return "<button class=\"selected\">" + text + - "<span class=\"count\">" + fmtNbElems + "</span></button>"; - } - return "<button>" + text + "<span class=\"count\">" + fmtNbElems + "</span></button>"; +/** + * returns [tab, output] + * @param {number} tabNb + * @param {string} text + * @param {AsyncGenerator<rustdoc.ResultObject>} results + * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query + * @param {boolean} isTypeSearch + * @param {boolean} goToFirst + * @returns {[HTMLElement, Promise<HTMLElement>]} + */ +function makeTab(tabNb, text, results, query, isTypeSearch, goToFirst) { + const isCurrentTab = window.searchState.currentTab === tabNb; + const tabButton = document.createElement("button"); + tabButton.appendChild(document.createTextNode(text)); + tabButton.className = isCurrentTab ? "selected" : ""; + const tabCount = document.createElement("span"); + tabCount.className = "count loading"; + tabCount.innerHTML = "\u{2007}(\u{2007})\u{2007}\u{2007}"; + tabButton.appendChild(tabCount); + return [ + tabButton, + addTab(results, query, isCurrentTab, (count, output) => { + const search = window.searchState.outputElement(); + const error = query.error; + if (count === 0 && error !== null && search) { + error.forEach((value, index) => { + value = value.split("<").join("<").split(">").join(">"); + if (index % 2 !== 0) { + error[index] = `<code>${value.replaceAll(" ", " ")}</code>`; + } else { + error[index] = value; + } + }); + const errorReport = document.createElement("h3"); + errorReport.className = "error"; + errorReport.innerHTML = `Query parser error: "${error.join("")}".`; + search.insertBefore(errorReport, search.firstElementChild); + } else if (goToFirst || + (count === 1 && getSettingValue("go-to-only-result") === "true") + ) { + // Needed to force re-execution of JS when coming back to a page. Let's take this + // scenario as example: + // + // 1. You have the "Directly go to item in search if there is only one result" + // option enabled. + // 2. You make a search which results only one result, leading you automatically to + // this result. + // 3. You go back to previous page. + // + // Now, without the call below, the JS will not be re-executed and the previous + // state will be used, starting search again since the search input is not empty, + // leading you back to the previous page again. + window.onunload = () => { }; + window.searchState.removeQueryParameters(); + const a = output.querySelector("a"); + if (a) { + a.click(); + return; + } + } + + // https://blog.horizon-eda.org/misc/2020/02/19/ui.html + // + // CSS runs with `font-variant-numeric: tabular-nums` to ensure all + // digits are the same width. \u{2007} is a Unicode space character + // that is defined to be the same width as a digit. + const fmtNbElems = + count < 10 ? `\u{2007}(${count})\u{2007}\u{2007}` : + count < 100 ? `\u{2007}(${count})\u{2007}` : `\u{2007}(${count})`; + tabCount.innerHTML = fmtNbElems; + tabCount.className = "count"; + }, isTypeSearch), + ]; } /** + * @param {DocSearch} docSearch * @param {rustdoc.ResultsTable} results - * @param {boolean} go_to_first + * @param {boolean} goToFirst * @param {string} filterCrates */ -async function showResults(results, go_to_first, filterCrates) { - const search = searchState.outputElement(); - if (go_to_first || (results.others.length === 1 - && getSettingValue("go-to-only-result") === "true") - ) { - // Needed to force re-execution of JS when coming back to a page. Let's take this - // scenario as example: - // - // 1. You have the "Directly go to item in search if there is only one result" option - // enabled. - // 2. You make a search which results only one result, leading you automatically to - // this result. - // 3. You go back to previous page. - // - // Now, without the call below, the JS will not be re-executed and the previous state - // will be used, starting search again since the search input is not empty, leading you - // back to the previous page again. - window.onunload = () => { }; - searchState.removeQueryParameters(); - const elem = document.createElement("a"); - elem.href = results.others[0].href; - removeClass(elem, "active"); - // For firefox, we need the element to be in the DOM so it can be clicked. - document.body.appendChild(elem); - elem.click(); - return; - } - if (results.query === undefined) { - // @ts-expect-error - results.query = DocSearch.parseQuery(searchState.input.value); - } +async function showResults(docSearch, results, goToFirst, filterCrates) { + const search = window.searchState.outputElement(); - currentResults = results.query.userQuery; - - // Navigate to the relevant tab if the current tab is empty, like in case users search - // for "-> String". If they had selected another tab previously, they have to click on - // it again. - let currentTab = searchState.currentTab; - if ((currentTab === 0 && results.others.length === 0) || - (currentTab === 1 && results.in_args.length === 0) || - (currentTab === 2 && results.returned.length === 0)) { - if (results.others.length !== 0) { - currentTab = 0; - } else if (results.in_args.length) { - currentTab = 1; - } else if (results.returned.length) { - currentTab = 2; - } + if (!search) { + return; } let crates = ""; - if (rawSearchIndex.size > 1) { - crates = "<div class=\"sub-heading\"> in <div id=\"crate-search-div\">" + + const crateNames = await docSearch.getCrateNameList(); + if (crateNames.length > 1) { + crates = " in <div id=\"crate-search-div\">" + "<select id=\"crate-search\"><option value=\"all crates\">all crates</option>"; - for (const c of rawSearchIndex.keys()) { + const l = crateNames.length; + for (let i = 0; i < l; i += 1) { + const c = crateNames[i]; crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`; } - crates += "</select></div></div>"; + crates += "</select></div>"; } + nonnull(document.querySelector(".search-switcher")).innerHTML = `Search results${crates}`; - let output = `<div class="main-heading">\ - <h1 class="search-results-title">Results</h1>${crates}</div>`; + /** @type {[HTMLElement, Promise<HTMLElement>][]} */ + const tabs = []; + searchState.currentTab = 0; if (results.query.error !== null) { - const error = results.query.error; - // @ts-expect-error - error.forEach((value, index) => { - value = value.split("<").join("<").split(">").join(">"); - if (index % 2 !== 0) { - error[index] = `<code>${value.replaceAll(" ", " ")}</code>`; - } else { - error[index] = value; - } - }); - output += `<h3 class="error">Query parser error: "${error.join("")}".</h3>`; - output += "<div id=\"search-tabs\">" + - makeTabHeader(0, "In Names", results.others.length) + - "</div>"; - currentTab = 0; - } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) { - output += "<div id=\"search-tabs\">" + - makeTabHeader(0, "In Names", results.others.length) + - makeTabHeader(1, "In Parameters", results.in_args.length) + - makeTabHeader(2, "In Return Types", results.returned.length) + - "</div>"; + tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst)); + } else if ( + results.query.foundElems <= 1 && + results.query.returned.length === 0 && + !results.query.hasReturnArrow + ) { + tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst)); + tabs.push(makeTab(1, "In Parameters", results.in_args, results.query, true, false)); + tabs.push(makeTab(2, "In Return Types", results.returned, results.query, true, false)); } else { const signatureTabTitle = results.query.elems.length === 0 ? "In Function Return Types" : results.query.returned.length === 0 ? "In Function Parameters" : "In Function Signatures"; - output += "<div id=\"search-tabs\">" + - makeTabHeader(0, signatureTabTitle, results.others.length) + - "</div>"; - currentTab = 0; - } - - if (results.query.correction !== null) { - const orig = results.query.returned.length > 0 - ? results.query.returned[0].name - : results.query.elems[0].name; - output += "<h3 class=\"search-corrections\">" + - `Type "${orig}" not found. ` + - "Showing results for closest type name " + - `"${results.query.correction}" instead.</h3>`; - } - if (results.query.proposeCorrectionFrom !== null) { - const orig = results.query.proposeCorrectionFrom; - const targ = results.query.proposeCorrectionTo; - output += "<h3 class=\"search-corrections\">" + - `Type "${orig}" not found and used as generic parameter. ` + - `Consider searching for "${targ}" instead.</h3>`; + tabs.push(makeTab(0, signatureTabTitle, results.others, results.query, true, goToFirst)); } - const [ret_others, ret_in_args, ret_returned] = await Promise.all([ - addTab(results.others, results.query, currentTab === 0), - addTab(results.in_args, results.query, currentTab === 1), - addTab(results.returned, results.query, currentTab === 2), - ]); + const tabsElem = document.createElement("div"); + tabsElem.id = "search-tabs"; const resultsElem = document.createElement("div"); resultsElem.id = "results"; - resultsElem.appendChild(ret_others); - resultsElem.appendChild(ret_in_args); - resultsElem.appendChild(ret_returned); - // @ts-expect-error - search.innerHTML = output; - if (searchState.rustdocToolbar) { - // @ts-expect-error - search.querySelector(".main-heading").appendChild(searchState.rustdocToolbar); + search.innerHTML = ""; + for (const [tab, output] of tabs) { + tabsElem.appendChild(tab); + const placeholder = document.createElement("div"); + output.then(output => { + if (placeholder.parentElement) { + placeholder.parentElement.replaceChild(output, placeholder); + } + }); + resultsElem.appendChild(placeholder); + } + + if (window.searchState.rustdocToolbar) { + nonnull( + nonnull(window.searchState.containerElement()) + .querySelector(".main-heading"), + ).appendChild(window.searchState.rustdocToolbar); } const crateSearch = document.getElementById("crate-search"); if (crateSearch) { crateSearch.addEventListener("input", updateCrate); } - // @ts-expect-error + search.appendChild(tabsElem); search.appendChild(resultsElem); // Reset focused elements. - searchState.showResults(search); - // @ts-expect-error - const elems = document.getElementById("search-tabs").childNodes; - // @ts-expect-error - searchState.focusedByTab = []; + window.searchState.showResults(); + window.searchState.focusedByTab = [null, null, null]; let i = 0; - for (const elem of elems) { + for (const elem of tabsElem.childNodes) { const j = i; // @ts-expect-error elem.onclick = () => printTab(j); - searchState.focusedByTab.push(null); + window.searchState.focusedByTab[i] = null; i += 1; } - printTab(currentTab); + printTab(0); } // @ts-expect-error function updateSearchHistory(url) { + const btn = document.querySelector("#search-button a"); + if (btn instanceof HTMLAnchorElement) { + btn.href = url; + } if (!browserSupportsHistoryApi()) { return; } const params = searchState.getQueryStringParams(); - if (!history.state && !params.search) { + if (!history.state && params.search === undefined) { history.pushState(null, "", url); } else { history.replaceState(null, "", url); @@ -5120,8 +5011,8 @@ function updateSearchHistory(url) { * @param {boolean} [forced] */ async function search(forced) { - // @ts-expect-error - const query = DocSearch.parseQuery(searchState.input.value.trim()); + const query = DocSearch.parseQuery(nonnull(window.searchState.inputElement()).value.trim()); + let filterCrates = getFilterCrates(); // @ts-expect-error @@ -5131,6 +5022,7 @@ async function search(forced) { } return; } + currentResults = query.userQuery; searchState.setLoadingSearch(); @@ -5142,6 +5034,12 @@ async function search(forced) { filterCrates = params["filter-crate"]; } + if (filterCrates !== null && + (await docSearch.getCrateNameList()).indexOf(filterCrates) === -1 + ) { + filterCrates = null; + } + // Update document title to maintain a meaningful browser history searchState.title = "\"" + query.userQuery + "\" Search - Rust"; @@ -5150,6 +5048,7 @@ async function search(forced) { updateSearchHistory(buildUrl(query.userQuery, filterCrates)); await showResults( + docSearch, // @ts-expect-error await docSearch.execQuery(query, filterCrates, window.currentCrate), params.go_to_first, @@ -5169,16 +5068,14 @@ function onSearchSubmit(e) { } function putBackSearch() { - const search_input = searchState.input; - if (!searchState.input) { + const search_input = window.searchState.inputElement(); + if (!search_input) { return; } - // @ts-expect-error if (search_input.value !== "" && !searchState.isDisplayed()) { searchState.showResults(); if (browserSupportsHistoryApi()) { history.replaceState(null, "", - // @ts-expect-error buildUrl(search_input.value, getFilterCrates())); } document.title = searchState.title; @@ -5192,30 +5089,21 @@ function registerSearchEvents() { // but only if the input bar is empty. This avoid the obnoxious issue // where you start trying to do a search, and the index loads, and // suddenly your search is gone! - // @ts-expect-error - if (searchState.input.value === "") { - // @ts-expect-error - searchState.input.value = params.search || ""; + const inputElement = nonnull(window.searchState.inputElement()); + if (inputElement.value === "") { + inputElement.value = params.search || ""; } const searchAfter500ms = () => { searchState.clearInputTimeout(); - // @ts-expect-error - if (searchState.input.value.length === 0) { - searchState.hideResults(); - } else { - // @ts-ignore - searchState.timeout = setTimeout(search, 500); - } + window.searchState.timeout = setTimeout(search, 500); }; - // @ts-expect-error - searchState.input.onkeyup = searchAfter500ms; - // @ts-expect-error - searchState.input.oninput = searchAfter500ms; - // @ts-expect-error - document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit; - // @ts-expect-error - searchState.input.onchange = e => { + inputElement.onkeyup = searchAfter500ms; + inputElement.oninput = searchAfter500ms; + if (inputElement.form) { + inputElement.form.onsubmit = onSearchSubmit; + } + inputElement.onchange = e => { if (e.target !== document.activeElement) { // To prevent doing anything when it's from a blur event. return; @@ -5227,11 +5115,13 @@ function registerSearchEvents() { // change, though. setTimeout(search, 0); }; - // @ts-expect-error - searchState.input.onpaste = searchState.input.onchange; + inputElement.onpaste = inputElement.onchange; // @ts-expect-error searchState.outputElement().addEventListener("keydown", e => { + if (!(e instanceof KeyboardEvent)) { + return; + } // We only handle unmodified keystrokes here. We don't want to interfere with, // for instance, alt-left and alt-right for history navigation. if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { @@ -5271,88 +5161,23 @@ function registerSearchEvents() { } }); - // @ts-expect-error - searchState.input.addEventListener("keydown", e => { + inputElement.addEventListener("keydown", e => { if (e.which === 40) { // down focusSearchResult(); e.preventDefault(); } }); - // @ts-expect-error - searchState.input.addEventListener("focus", () => { + inputElement.addEventListener("focus", () => { putBackSearch(); }); - - // @ts-expect-error - searchState.input.addEventListener("blur", () => { - if (window.searchState.input) { - window.searchState.input.placeholder = window.searchState.origPlaceholder; - } - }); - - // Push and pop states are used to add search results to the browser - // history. - if (browserSupportsHistoryApi()) { - // Store the previous <title> so we can revert back to it later. - const previousTitle = document.title; - - window.addEventListener("popstate", e => { - const params = searchState.getQueryStringParams(); - // Revert to the previous title manually since the History - // API ignores the title parameter. - document.title = previousTitle; - // When browsing forward to search results the previous - // search will be repeated, so the currentResults are - // cleared to ensure the search is successful. - currentResults = null; - // Synchronize search bar with query string state and - // perform the search. This will empty the bar if there's - // nothing there, which lets you really go back to a - // previous state with nothing in the bar. - if (params.search && params.search.length > 0) { - // @ts-expect-error - searchState.input.value = params.search; - // Some browsers fire "onpopstate" for every page load - // (Chrome), while others fire the event only when actually - // popping a state (Firefox), which is why search() is - // called both here and at the end of the startSearch() - // function. - e.preventDefault(); - search(); - } else { - // @ts-expect-error - searchState.input.value = ""; - // When browsing back from search results the main page - // visibility must be reset. - searchState.hideResults(); - } - }); - } - - // This is required in firefox to avoid this problem: Navigating to a search result - // with the keyboard, hitting enter, and then hitting back would take you back to - // the doc page, rather than the search that should overlay it. - // This was an interaction between the back-forward cache and our handlers - // that try to sync state between the URL and the search input. To work around it, - // do a small amount of re-init on page show. - window.onpageshow = () => { - const qSearch = searchState.getQueryStringParams().search; - // @ts-expect-error - if (searchState.input.value === "" && qSearch) { - // @ts-expect-error - searchState.input.value = qSearch; - } - search(); - }; } // @ts-expect-error function updateCrate(ev) { if (ev.target.value === "all crates") { // If we don't remove it from the URL, it'll be picked up again by the search. - // @ts-expect-error - const query = searchState.input.value.trim(); + const query = nonnull(window.searchState.inputElement()).value.trim(); updateSearchHistory(buildUrl(query, null)); } // In case you "cut" the entry from the search input, then change the crate filter @@ -5362,522 +5187,91 @@ function updateCrate(ev) { search(true); } -// Parts of this code are based on Lucene, which is licensed under the -// Apache/2.0 license. -// More information found here: -// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/ -// LevenshteinAutomata.java -class ParametricDescription { - // @ts-expect-error - constructor(w, n, minErrors) { - this.w = w; - this.n = n; - this.minErrors = minErrors; - } - // @ts-expect-error - isAccept(absState) { - const state = Math.floor(absState / (this.w + 1)); - const offset = absState % (this.w + 1); - return this.w - offset + this.minErrors[state] <= this.n; - } - // @ts-expect-error - getPosition(absState) { - return absState % (this.w + 1); - } - // @ts-expect-error - getVector(name, charCode, pos, end) { - let vector = 0; - for (let i = pos; i < end; i += 1) { - vector = vector << 1; - if (name.charCodeAt(i) === charCode) { - vector |= 1; - } - } - return vector; - } - // @ts-expect-error - unpack(data, index, bitsPerValue) { - const bitLoc = (bitsPerValue * index); - const dataLoc = bitLoc >> 5; - const bitStart = bitLoc & 31; - if (bitStart + bitsPerValue <= 32) { - // not split - return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]); - } else { - // split - const part = 32 - bitStart; - return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) + - ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part)); - } +// eslint-disable-next-line max-len +// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64 +/** + * @type {function(string): Uint8Array} base64 + */ +//@ts-expect-error +const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => { + const bytes_as_string = atob(string); + const l = bytes_as_string.length; + const bytes = new Uint8Array(l); + for (let i = 0; i < l; ++i) { + bytes[i] = bytes_as_string.charCodeAt(i); } -} -ParametricDescription.prototype.MASKS = new Int32Array([ - 0x1, 0x3, 0x7, 0xF, - 0x1F, 0x3F, 0x7F, 0xFF, - 0x1FF, 0x3F, 0x7FF, 0xFFF, - 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, - 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF, - 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF, - 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, - 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, -]); - -// The following code was generated with the moman/finenight pkg -// This package is available under the MIT License, see NOTICE.txt -// for more details. -// This class is auto-generated, Please do not modify it directly. -// You should modify the https://gitlab.com/notriddle/createAutomata.py instead. -// The following code was generated with the moman/finenight pkg -// This package is available under the MIT License, see NOTICE.txt -// for more details. -// This class is auto-generated, Please do not modify it directly. -// You should modify https://gitlab.com/notriddle/moman-rustdoc instead. - -class Lev2TParametricDescription extends ParametricDescription { - /** - * @param {number} absState - * @param {number} position - * @param {number} vector - * @returns {number} - */ - transition(absState, position, vector) { - let state = Math.floor(absState / (this.w + 1)); - let offset = absState % (this.w + 1); - - if (position === this.w) { - if (state < 3) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 3) + state; - offset += this.unpack(this.offsetIncrs0, loc, 1); - state = this.unpack(this.toStates0, loc, 2) - 1; - } - } else if (position === this.w - 1) { - if (state < 5) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 5) + state; - offset += this.unpack(this.offsetIncrs1, loc, 1); - state = this.unpack(this.toStates1, loc, 3) - 1; - } - } else if (position === this.w - 2) { - if (state < 13) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 13) + state; - offset += this.unpack(this.offsetIncrs2, loc, 2); - state = this.unpack(this.toStates2, loc, 4) - 1; - } - } else if (position === this.w - 3) { - if (state < 28) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 28) + state; - offset += this.unpack(this.offsetIncrs3, loc, 2); - state = this.unpack(this.toStates3, loc, 5) - 1; - } - } else if (position === this.w - 4) { - if (state < 45) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 45) + state; - offset += this.unpack(this.offsetIncrs4, loc, 3); - state = this.unpack(this.toStates4, loc, 6) - 1; - } - } else { - if (state < 45) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 45) + state; - offset += this.unpack(this.offsetIncrs5, loc, 3); - state = this.unpack(this.toStates5, loc, 6) - 1; - } - } + return bytes; +}); - if (state === -1) { - // null state - return -1; - } else { - // translate back to abs - return Math.imul(state, this.w + 1) + offset; - } - } - // state map - // 0 -> [(0, 0)] - // 1 -> [(0, 1)] - // 2 -> [(0, 2)] - // 3 -> [(0, 1), (1, 1)] - // 4 -> [(0, 2), (1, 2)] - // 5 -> [(0, 1), (1, 1), (2, 1)] - // 6 -> [(0, 2), (1, 2), (2, 2)] - // 7 -> [(0, 1), (2, 1)] - // 8 -> [(0, 1), (2, 2)] - // 9 -> [(0, 2), (2, 1)] - // 10 -> [(0, 2), (2, 2)] - // 11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] - // 12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)] - // 13 -> [(0, 2), (1, 2), (2, 2), (3, 2)] - // 14 -> [(0, 1), (1, 1), (3, 2)] - // 15 -> [(0, 1), (2, 2), (3, 2)] - // 16 -> [(0, 1), (3, 2)] - // 17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)] - // 18 -> [(0, 2), (1, 2), (3, 1)] - // 19 -> [(0, 2), (1, 2), (3, 2)] - // 20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)] - // 21 -> [(0, 2), (2, 1), (3, 1)] - // 22 -> [(0, 2), (2, 2), (3, 2)] - // 23 -> [(0, 2), (3, 1)] - // 24 -> [(0, 2), (3, 2)] - // 25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)] - // 26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)] - // 27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)] - // 28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] - // 29 -> [(0, 2), (1, 2), (2, 2), (4, 2)] - // 30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] - // 31 -> [(0, 2), (1, 2), (3, 2), (4, 2)] - // 32 -> [(0, 2), (1, 2), (4, 2)] - // 33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)] - // 34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] - // 35 -> [(0, 2), (2, 1), (4, 2)] - // 36 -> [(0, 2), (2, 2), (3, 2), (4, 2)] - // 37 -> [(0, 2), (2, 2), (4, 2)] - // 38 -> [(0, 2), (3, 2), (4, 2)] - // 39 -> [(0, 2), (4, 2)] - // 40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)] - // 41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] - // 42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] - // 43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)] - // 44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] - - - /** @param {number} w - length of word being checked */ - constructor(w) { - super(w, 2, new Int32Array([ - 0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2, - -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2, - ])); +if (ROOT_PATH === null) { + return; +} +const database = await Stringdex.loadDatabase(hooks); +if (typeof window !== "undefined") { + docSearch = new DocSearch(ROOT_PATH, database); + await docSearch.buildIndex(); + onEachLazy(document.querySelectorAll( + ".search-form.loading", + ), form => { + removeClass(form, "loading"); + }); + registerSearchEvents(); + // If there's a search term in the URL, execute the search now. + if (window.searchState.getQueryStringParams().search !== undefined) { + search(); } +} else if (typeof exports !== "undefined") { + docSearch = new DocSearch(ROOT_PATH, database); + await docSearch.buildIndex(); + return { docSearch, DocSearch }; } +}; -Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ - 0xe, -]); -Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ - 0x0, -]); - -Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([ - 0x1a688a2c, -]); -Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ - 0x3e0, -]); - -Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([ - 0x70707054,0xdc07035,0x3dd3a3a,0x2323213a, - 0x15435223,0x22545432,0x5435, -]); -Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ - 0x80000,0x55582088,0x55555555,0x55, -]); - -Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([ - 0x1c0380a4,0x700a570,0xca529c0,0x180a00, - 0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8, - 0xac18c601,0xd8d43501,0x863500ad,0x51976d6a, - 0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5, - 0x18c4519,0xc41294a,0xe248d231,0x1086520c, - 0xce31ac42,0x13946358,0x2d0348c4,0x6732d494, - 0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948, - 0x22110a52,0x58ce729d,0xc41394e3,0x941cc520, - 0x90e732d4,0x4729d224,0x39ce35ad, -]); -Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ - 0x80000,0xc0c830,0x300f3c30,0x2200fcff, - 0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555, - 0x55555555,0x55555555,0x55555555,0x55555555, - 0x55555555,0x55555555, -]); - -Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([ - 0x801c0144,0x1453803,0x14700038,0xc0005145, - 0x1401,0x14,0x140000,0x0, - 0x510000,0x6301f007,0x301f00d1,0xa186178, - 0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd, - 0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34, - 0x8300550,0x430c0143,0x50c31,0xc30850c, - 0xc3143000,0x50053c50,0x5130d301,0x850d30c2, - 0x30a08608,0xc214414,0x43142145,0x21450031, - 0x1400c314,0x4c143145,0x32832803,0x28014d6c, - 0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3, - 0x1431,0xc300500,0xca00d303,0xd36d0e40, - 0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c, - 0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280, - 0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401, - 0x1430c714,0xc3087,0x71451450,0xca00d30, - 0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144, - 0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51, - 0x24324308,0xc51031c2,0x70820820,0x5c33830d, - 0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3, - 0x20c20c20,0xda0920d,0x5145914f,0x36596114, - 0x51965865,0xd9643653,0x365a6590,0x51964364, - 0x43081505,0x920b2032,0x2c718b28,0xd7242249, - 0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2, - 0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c, - 0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7, - 0x73975c36,0x10308138,0xc2245105,0x41451031, - 0x14e24208,0xc35c3387,0x51453851,0x1c51c514, - 0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092, - 0x4513d41,0x6533944d,0x1350e658,0xe1545055, - 0x64365a50,0x5519383,0x51030815,0x28920718, - 0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387, - 0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440, - 0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65, - 0x8e154387,0x364b5ca3,0x38739738, -]); -Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([ - 0x10000000,0xc00000,0x60061,0x400, - 0x0,0x80010008,0x249248a4,0x8229048, - 0x2092,0x6c3603,0xb61b6c30,0x6db6036d, - 0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b, - 0x6db6236,0x1008200,0x12480012,0x24924906, - 0x48200049,0x80410002,0x24000900,0x4924a489, - 0x10822492,0x20800125,0x48360,0x9241b692, - 0x6da4924,0x40009268,0x241b010,0x291b4900, - 0x6d249249,0x49493423,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x2492, -]); - -Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([ - 0x801c0144,0x1453803,0x14700038,0xc0005145, - 0x1401,0x14,0x140000,0x0, - 0x510000,0x4e00e007,0xe0051,0x3451451c, - 0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4, - 0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07, - 0x2830c286,0x830c3083,0xc30030,0x33430c, - 0x30c3003,0x70051030,0x16301f00,0x8301f00d, - 0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51, - 0x14314315,0x4f143145,0x34c05401,0x4c30944c, - 0x55150c3,0x30830055,0x1430c014,0xc00050c3, - 0xc30850,0xc314300,0x150053c5,0x25130d30, - 0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c, - 0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540, - 0x34c30944,0x82182214,0x851050c2,0x50851430, - 0x1400c50c,0x30c5085,0x50c51450,0x150053c, - 0xc25130d3,0x8850d30,0x1430a086,0x450c2144, - 0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1, - 0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c, - 0xc31c3140,0x31430c30,0x14,0x30c3005, - 0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3, - 0xc500d01,0x5965965a,0x30d46546,0x6435030c, - 0x8034c659,0xdb439032,0x2c390034,0xcaaecb24, - 0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033, - 0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e, - 0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce, - 0x95c53831,0x28034c5d,0x9b705583,0xa1455830, - 0xc76cd6c,0x40143085,0x71451c51,0x871430c, - 0x450000c3,0xd3071451,0x1560ca00,0x560c26dc, - 0xb35b2851,0xc914369,0x1a14500d,0x46593945, - 0xcb2c939,0x94507503,0x328034c3,0x9b70558, - 0xe41c5583,0x72caaeca,0x1c308510,0xc7147287, - 0x50871c32,0x1470030c,0xd307147,0xc1560ca0, - 0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c, - 0x8e657394,0x314b1c93,0x39438738,0x43083081, - 0x31c22432,0x820c510,0x830d7082,0x50c35c33, - 0xc30c338,0xc31c30c3,0x50c30c30,0xc204514, - 0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3, - 0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a, - 0x48308308,0xc3682483,0x14516453,0x4d965845, - 0xd4659619,0x36590d94,0xd969964,0x546590d9, - 0x20c20541,0x920d20c,0x5914f0da,0x96114514, - 0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e, - 0x81821827,0xb2032430,0x18b28920,0x422492c7, - 0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972, - 0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c, - 0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28, - 0x59a8a269,0x8175e7a,0xb203243,0x718b2892, - 0x4114105c,0x17597658,0x74ce5d96,0x5c36572d, - 0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8, - 0x4171c62,0x5d961045,0x976585d6,0x79669533, - 0x964965a2,0x659689e6,0x308175e7,0x24510510, - 0x451031c2,0xe2420841,0x5c338714,0x453851c3, - 0x51c51451,0xc30c31c,0x451450c7,0x41440c20, - 0xc708914,0x82105144,0xf1c58c90,0x1470ea0d, - 0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861, - 0x24853c51,0x5053c368,0x1341144f,0x96194ce5, - 0x1544d439,0x94385514,0xe0d90d96,0x5415464, - 0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0, - 0x350e6586,0x86082181,0xe89e981d,0x18277689, - 0x10308182,0x89207185,0x41c718b2,0x14e24224, - 0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb, - 0x204e1c75,0x1c61440c,0xc62ca248,0x90891071, - 0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475, - 0x5e7a57a8,0x51030817,0x28920718,0xf38718b, - 0xe5134114,0x39961758,0xe1ce4ce,0x728e3855, - 0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24, - 0xd04503ce,0x85d63944,0x75338e65,0x5d86075e, - 0x89e69647,0x75e76576, -]); -Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([ - 0x10000000,0xc00000,0x60061,0x400, - 0x0,0x60000008,0x6b003080,0xdb6ab6db, - 0x2db6,0x800400,0x49245240,0x11482412, - 0x104904,0x40020000,0x92292000,0xa4b25924, - 0x9649658,0xd80c000,0xdb0c001b,0x80db6d86, - 0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d, - 0xddadb6ed,0x300001b6,0x6c360,0xe37236e4, - 0x46db6236,0xdb6c,0x361b018,0xb91b7200, - 0x6dbb1b71,0x6db763,0x20100820,0x61248001, - 0x92492490,0x24820004,0x8041000,0x92400090, - 0x24924830,0x555b6a49,0x2080012,0x20004804, - 0x49252449,0x84112492,0x4000928,0x240201, - 0x92922490,0x58924924,0x49456,0x120d8082, - 0x6da4800,0x69249249,0x249a01b,0x6c04100, - 0x6d240009,0x92492483,0x24d5adb4,0x60208001, - 0x92000483,0x24925236,0x6846da49,0x10400092, - 0x241b0,0x49291b49,0x636d2492,0x92494935, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924,0x49249249, - 0x92492492,0x24924924,0x49249249,0x92492492, - 0x24924924,0x49249249,0x92492492,0x24924924, - 0x49249249,0x92492492,0x24924924, -]); - -class Lev1TParametricDescription extends ParametricDescription { - /** - * @param {number} absState - * @param {number} position - * @param {number} vector - * @returns {number} - */ - transition(absState, position, vector) { - let state = Math.floor(absState / (this.w + 1)); - let offset = absState % (this.w + 1); - - if (position === this.w) { - if (state < 2) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 2) + state; - offset += this.unpack(this.offsetIncrs0, loc, 1); - state = this.unpack(this.toStates0, loc, 2) - 1; - } - } else if (position === this.w - 1) { - if (state < 3) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 3) + state; - offset += this.unpack(this.offsetIncrs1, loc, 1); - state = this.unpack(this.toStates1, loc, 2) - 1; - } - } else if (position === this.w - 2) { - if (state < 6) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 6) + state; - offset += this.unpack(this.offsetIncrs2, loc, 2); - state = this.unpack(this.toStates2, loc, 3) - 1; +if (typeof window !== "undefined") { + const ROOT_PATH = window.rootPath; + /** @type {stringdex.Callbacks|null} */ + let databaseCallbacks = null; + initSearch(window.Stringdex, window.RoaringBitmap, { + loadRoot: callbacks => { + for (const key in callbacks) { + if (Object.hasOwn(callbacks, key)) { + // @ts-ignore + window[key] = callbacks[key]; + } } - } else { - if (state < 6) { // eslint-disable-line no-lonely-if - const loc = Math.imul(vector, 6) + state; - offset += this.unpack(this.offsetIncrs3, loc, 2); - state = this.unpack(this.toStates3, loc, 3) - 1; + databaseCallbacks = callbacks; + // search.index/root is loaded by main.js, so + // this script doesn't need to launch it, but + // must pick it up + // @ts-ignore + if (window.searchIndex) { + // @ts-ignore + window.rr_(window.searchIndex); } - } - - if (state === -1) { - // null state - return -1; - } else { - // translate back to abs - return Math.imul(state, this.w + 1) + offset; - } - } - - // state map - // 0 -> [(0, 0)] - // 1 -> [(0, 1)] - // 2 -> [(0, 1), (1, 1)] - // 3 -> [(0, 1), (1, 1), (2, 1)] - // 4 -> [(0, 1), (2, 1)] - // 5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] - - - /** @param {number} w - length of word being checked */ - constructor(w) { - super(w, 1, new Int32Array([0,1,0,-1,-1,-1])); - } -} - -Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ - 0x2, -]); -Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ - 0x0, -]); - -Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([ - 0xa43, -]); -Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ - 0x38, -]); - -Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([ - 0x12180003,0xb45a4914,0x69, -]); -Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ - 0x558a0000,0x5555, -]); - -Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([ - 0x900c0003,0xa1904864,0x45a49169,0x5a6d196a, - 0x9634, -]); -Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ - 0xa0fc0000,0x5555ba08,0x55555555, -]); - -// ==================== -// WARNING: Nothing should be added below this comment: we need the `initSearch` function to -// be called ONLY when the whole file has been parsed and loaded. - -// @ts-expect-error -function initSearch(searchIndex) { - rawSearchIndex = searchIndex; - if (typeof window !== "undefined") { - // @ts-expect-error - docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState); - registerSearchEvents(); - // If there's a search term in the URL, execute the search now. - if (window.searchState.getQueryStringParams().search) { - search(); - } - } else if (typeof exports !== "undefined") { - // @ts-expect-error - docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState); - exports.docSearch = docSearch; - exports.parseQuery = DocSearch.parseQuery; - } -} - -if (typeof exports !== "undefined") { + }, + loadTreeByHash: hashHex => { + const script = document.createElement("script"); + script.src = `${ROOT_PATH}search.index/${hashHex}.js`; + script.onerror = e => { + if (databaseCallbacks) { + databaseCallbacks.err_rn_(hashHex, e); + } + }; + document.documentElement.appendChild(script); + }, + loadDataByNameAndHash: (name, hashHex) => { + const script = document.createElement("script"); + script.src = `${ROOT_PATH}search.index/${name}/${hashHex}.js`; + script.onerror = e => { + if (databaseCallbacks) { + databaseCallbacks.err_rd_(hashHex, e); + } + }; + document.documentElement.appendChild(script); + }, + }); +} else if (typeof exports !== "undefined") { + // eslint-disable-next-line no-undef exports.initSearch = initSearch; } - -if (typeof window !== "undefined") { - // @ts-expect-error - window.initSearch = initSearch; - // @ts-expect-error - if (window.searchIndex !== undefined) { - // @ts-expect-error - initSearch(window.searchIndex); - } -} else { - // Running in Node, not a browser. Run initSearch just to produce the - // exports. - initSearch(new Map()); -} diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 2430b5829b2b..347d3d0750ec 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -1,25 +1,13 @@ // Local js definitions: /* global getSettingValue, updateLocalStorage, updateTheme */ /* global addClass, removeClass, onEach, onEachLazy */ -/* global MAIN_ID, getVar, getSettingsButton, getHelpButton, nonnull */ +/* global MAIN_ID, getVar, nonnull */ "use strict"; (function() { const isSettingsPage = window.location.pathname.endsWith("/settings.html"); - /** - * @param {Element} elem - * @param {EventTarget|null} target - */ - function elemContainsTarget(elem, target) { - if (target instanceof Node) { - return elem.contains(target); - } else { - return false; - } - } - /** * @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"} * @param {string} settingName @@ -305,10 +293,12 @@ } } else { el.setAttribute("tabindex", "-1"); - const settingsBtn = getSettingsButton(); - if (settingsBtn !== null) { - settingsBtn.appendChild(el); - } + onEachLazy(document.querySelectorAll(".settings-menu"), menu => { + if (menu.offsetWidth !== 0) { + menu.appendChild(el); + return true; + } + }); } return el; } @@ -317,6 +307,15 @@ function displaySettings() { settingsMenu.style.display = ""; + onEachLazy(document.querySelectorAll(".settings-menu"), menu => { + if (menu.offsetWidth !== 0) { + if (!menu.contains(settingsMenu) && settingsMenu.parentElement) { + settingsMenu.parentElement.removeChild(settingsMenu); + menu.appendChild(settingsMenu); + } + return true; + } + }); onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => { const val = getSettingValue(el.id); const checked = val === "true"; @@ -330,40 +329,37 @@ * @param {FocusEvent} event */ function settingsBlurHandler(event) { - const helpBtn = getHelpButton(); - const settingsBtn = getSettingsButton(); - const helpUnfocused = helpBtn === null || - (!helpBtn.contains(document.activeElement) && - !elemContainsTarget(helpBtn, event.relatedTarget)); - const settingsUnfocused = settingsBtn === null || - (!settingsBtn.contains(document.activeElement) && - !elemContainsTarget(settingsBtn, event.relatedTarget)); - if (helpUnfocused && settingsUnfocused) { + const isInPopover = onEachLazy( + document.querySelectorAll(".settings-menu, .help-menu"), + menu => { + return menu.contains(document.activeElement) || menu.contains(event.relatedTarget); + }, + ); + if (!isInPopover) { window.hidePopoverMenus(); } } if (!isSettingsPage) { // We replace the existing "onclick" callback. - // These elements must exist, as (outside of the settings page) - // `settings.js` is only loaded after the settings button is clicked. - const settingsButton = nonnull(getSettingsButton()); const settingsMenu = nonnull(document.getElementById("settings")); - settingsButton.onclick = event => { - if (elemContainsTarget(settingsMenu, event.target)) { - return; - } - event.preventDefault(); - const shouldDisplaySettings = settingsMenu.style.display === "none"; + onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => { + /** @param {MouseEvent} event */ + settingsButton.querySelector("a").onclick = event => { + if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) { + return; + } + event.preventDefault(); + const shouldDisplaySettings = settingsMenu.style.display === "none"; - window.hideAllModals(false); - if (shouldDisplaySettings) { - displaySettings(); - } - }; - settingsButton.onblur = settingsBlurHandler; - // the settings button should always have a link in it - nonnull(settingsButton.querySelector("a")).onblur = settingsBlurHandler; + window.hideAllModals(false); + if (shouldDisplaySettings) { + displaySettings(); + } + }; + settingsButton.onblur = settingsBlurHandler; + settingsButton.querySelector("a").onblur = settingsBlurHandler; + }); onEachLazy(settingsMenu.querySelectorAll("input"), el => { el.onblur = settingsBlurHandler; }); @@ -377,6 +373,8 @@ if (!isSettingsPage) { displaySettings(); } - removeClass(getSettingsButton(), "rotate"); + onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => { + removeClass(settingsButton, "rotate"); + }); }, 0); })(); diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index ca13b891638f..c055eb0f8081 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -7,6 +7,7 @@ /** * @import * as rustdoc from "./rustdoc.d.ts"; + * @import * as stringdex from "./stringdex.d.ts"; */ const builtinThemes = ["light", "dark", "ayu"]; @@ -172,7 +173,7 @@ function updateLocalStorage(name, value) { } else { window.localStorage.setItem("rustdoc-" + name, value); } - } catch (e) { + } catch { // localStorage is not accessible, do nothing } } @@ -189,7 +190,7 @@ function updateLocalStorage(name, value) { function getCurrentValue(name) { try { return window.localStorage.getItem("rustdoc-" + name); - } catch (e) { + } catch { return null; } } @@ -375,32 +376,6 @@ window.addEventListener("pageshow", ev => { // That's also why this is in storage.js and not main.js. // // [parser]: https://html.spec.whatwg.org/multipage/parsing.html -class RustdocSearchElement extends HTMLElement { - constructor() { - super(); - } - connectedCallback() { - const rootPath = getVar("root-path"); - const currentCrate = getVar("current-crate"); - this.innerHTML = `<nav class="sub"> - <form class="search-form"> - <span></span> <!-- This empty span is a hacky fix for Safari - See #93184 --> - <div id="sidebar-button" tabindex="-1"> - <a href="${rootPath}${currentCrate}/all.html" title="show sidebar"></a> - </div> - <input - class="search-input" - name="search" - aria-label="Run search in the documentation" - autocomplete="off" - spellcheck="false" - placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…" - type="search"> - </form> - </nav>`; - } -} -window.customElements.define("rustdoc-search", RustdocSearchElement); class RustdocToolbarElement extends HTMLElement { constructor() { super(); @@ -411,11 +386,15 @@ class RustdocToolbarElement extends HTMLElement { return; } const rootPath = getVar("root-path"); + const currentUrl = window.location.href.split("?")[0].split("#")[0]; this.innerHTML = ` - <div id="settings-menu" tabindex="-1"> + <div id="search-button" tabindex="-1"> + <a href="${currentUrl}?search="><span class="label">Search</span></a> + </div> + <div class="settings-menu" tabindex="-1"> <a href="${rootPath}settings.html"><span class="label">Settings</span></a> </div> - <div id="help-button" tabindex="-1"> + <div class="help-menu" tabindex="-1"> <a href="${rootPath}help.html"><span class="label">Help</span></a> </div> <button id="toggle-all-docs" @@ -424,3 +403,31 @@ class="label">Summary</span></button>`; } } window.customElements.define("rustdoc-toolbar", RustdocToolbarElement); +class RustdocTopBarElement extends HTMLElement { + constructor() { + super(); + } + connectedCallback() { + const rootPath = getVar("root-path"); + const tmplt = document.createElement("template"); + tmplt.innerHTML = ` + <slot name="sidebar-menu-toggle"></slot> + <slot></slot> + <slot name="settings-menu"></slot> + <slot name="help-menu"></slot> + `; + const shadow = this.attachShadow({ mode: "open" }); + shadow.appendChild(tmplt.content.cloneNode(true)); + this.innerHTML += ` + <button class="sidebar-menu-toggle" slot="sidebar-menu-toggle" title="show sidebar"> + </button> + <div class="settings-menu" slot="settings-menu" tabindex="-1"> + <a href="${rootPath}settings.html"><span class="label">Settings</span></a> + </div> + <div class="help-menu" slot="help-menu" tabindex="-1"> + <a href="${rootPath}help.html"><span class="label">Help</span></a> + </div> + `; + } +} +window.customElements.define("rustdoc-topbar", RustdocTopBarElement); diff --git a/src/librustdoc/html/static/js/stringdex.d.ts b/src/librustdoc/html/static/js/stringdex.d.ts new file mode 100644 index 000000000000..cf9a8b6b5648 --- /dev/null +++ b/src/librustdoc/html/static/js/stringdex.d.ts @@ -0,0 +1,165 @@ +export = stringdex; + +declare namespace stringdex { + /** + * The client interface to Stringdex. + */ + interface Database { + getIndex(colname: string): SearchTree|undefined; + getData(colname: string): DataColumn|undefined; + } + /** + * A search index file. + */ + interface SearchTree { + trie(): Trie; + search(name: Uint8Array|string): Promise<Trie?>; + searchLev(name: Uint8Array|string): AsyncGenerator<Trie>; + } + /** + * A compressed node in the search tree. + * + * This object logically addresses two interleaved trees: + * a "prefix tree", and a "suffix tree". If you ask for + * generic matches, you get both, but if you ask for one + * that excludes suffix-only entries, you'll get prefixes + * alone. + */ + interface Trie { + matches(): RoaringBitmap; + substringMatches(): AsyncGenerator<RoaringBitmap>; + prefixMatches(): AsyncGenerator<RoaringBitmap>; + keys(): Uint8Array; + keysExcludeSuffixOnly(): Uint8Array; + children(): [number, Promise<Trie>][]; + childrenExcludeSuffixOnly(): [number, Promise<Trie>][]; + child(id: number): Promise<Trie>?; + } + /** + * The client interface to Stringdex. + */ + interface DataColumn { + isEmpty(id: number): boolean; + at(id: number): Promise<Uint8Array|undefined>; + length: number, + } + /** + * Callbacks for a host application and VFS backend. + * + * These functions are calleb with mostly-raw data, + * except the JSONP wrapper is removed. For example, + * a file with the contents `rr_('{"A":"B"}')` should, + * after being pulled in, result in the `rr_` callback + * being invoked. + * + * The success callbacks don't need to supply the name of + * the file that succeeded, but, if you want successful error + * reporting, you'll need to remember which files are + * in flight and report the filename as the first parameter. + */ + interface Callbacks { + /** + * Load the root of the search database + * @param {string} dataString + */ + rr_: function(string); + err_rr_: function(any); + /** + * Load a nodefile in the search tree. + * A node file may contain multiple nodes; + * each node has five fields, separated by newlines. + * @param {string} inputBase64 + */ + rn_: function(string); + err_rn_: function(string, any); + /** + * Load a database column partition from a string + * @param {string} dataString + */ + rd_: function(string); + err_rd_: function(string, any); + /** + * Load a database column partition from base64 + * @param {string} dataString + */ + rb_: function(string); + err_rb_: function(string, any); + }; + /** + * Hooks that a VFS layer must provide for stringdex to load data. + * + * When the root is loaded, the Callbacks object is provided. These + * functions should result in callback functions being called with + * the contents of the file, or in error callbacks being invoked with + * the failed-to-load filename. + */ + interface Hooks { + /** + * The first function invoked as part of loading a search database. + * This function must, eventually, invoke `rr_` with the string + * representation of the root file (the function call wrapper, + * `rr_('` and `')`, must be removed). + * + * The supplied callbacks object is used to feed search data back + * to the search engine core. You have to store it, so that + * loadTreeByHash and loadDataByNameAndHash can use it. + * + * If this fails, either throw an exception, or call `err_rr_` + * with the error object. + */ + loadRoot: function(Callbacks); + /** + * Load a subtree file from the search index. + * + * If this function succeeds, call `rn_` on the callbacks + * object. If it fails, call `err_rn_(hashHex, error)`. + * + * @param {string} hashHex + */ + loadTreeByHash: function(string); + /** + * Load a column partition from the search database. + * + * If this function succeeds, call `rd_` or `rb_` on the callbacks + * object. If it fails, call `err_rd_(hashHex, error)`. or `err_rb_`. + * To determine which one, the wrapping function call in the js file + * specifies it. + * + * @param {string} columnName + * @param {string} hashHex + */ + loadDataByNameAndHash: function(string, string); + }; + class RoaringBitmap { + constructor(array: Uint8Array|null, start?: number); + static makeSingleton(number: number); + static everything(): RoaringBitmap; + static empty(): RoaringBitmap; + isEmpty(): boolean; + union(that: RoaringBitmap): RoaringBitmap; + intersection(that: RoaringBitmap): RoaringBitmap; + contains(number: number): boolean; + entries(): Generator<number>; + first(): number|null; + consumed_len_bytes: number; + }; + + type Stringdex = { + /** + * Initialize Stringdex with VFS hooks. + * Returns a database that you can use. + */ + loadDatabase: function(Hooks): Promise<Database>, + }; + + const Stringdex: Stringdex; + const RoaringBitmap: Class<stringdex.RoaringBitmap>; +} + +declare global { + interface Window { + Stringdex: stringdex.Stringdex; + RoaringBitmap: Class<stringdex.RoaringBitmap>; + StringdexOnload: Array<function(stringdex.Stringdex): any>?; + }; +} \ No newline at end of file diff --git a/src/librustdoc/html/static/js/stringdex.js b/src/librustdoc/html/static/js/stringdex.js new file mode 100644 index 000000000000..cb956d926db9 --- /dev/null +++ b/src/librustdoc/html/static/js/stringdex.js @@ -0,0 +1,3217 @@ +/** + * @import * as stringdex from "./stringdex.d.ts" + */ + +const EMPTY_UINT8 = new Uint8Array(); + +/** + * @property {Uint8Array} keysAndCardinalities + * @property {Uint8Array[]} containers + */ +class RoaringBitmap { + /** + * @param {Uint8Array|null} u8array + * @param {number} [startingOffset] + */ + constructor(u8array, startingOffset) { + const start = startingOffset ? startingOffset : 0; + let i = start; + /** @type {Uint8Array} */ + this.keysAndCardinalities = EMPTY_UINT8; + /** @type {(RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun)[]} */ + this.containers = []; + /** @type {number} */ + this.consumed_len_bytes = 0; + if (u8array === null || u8array.length === i || u8array[i] === 0) { + return this; + } else if (u8array[i] > 0xf0) { + // Special representation of tiny sets that are close together + const lspecial = u8array[i] & 0x0f; + this.keysAndCardinalities = new Uint8Array(lspecial * 4); + let pspecial = i + 1; + let key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8); + let value = u8array[pspecial] | (u8array[pspecial + 1] << 8); + let entry = (key << 16) | value; + let container; + container = new RoaringBitmapArray(1, new Uint8Array(4)); + container.array[0] = value & 0xFF; + container.array[1] = (value >> 8) & 0xFF; + this.containers.push(container); + this.keysAndCardinalities[0] = key; + this.keysAndCardinalities[1] = key >> 8; + pspecial += 4; + for (let ispecial = 1; ispecial < lspecial; ispecial += 1) { + entry += u8array[pspecial] | (u8array[pspecial + 1] << 8); + value = entry & 0xffff; + key = entry >> 16; + container = this.addToArrayAt(key); + const cardinalityOld = container.cardinality; + container.array[cardinalityOld * 2] = value & 0xFF; + container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF; + container.cardinality = cardinalityOld + 1; + pspecial += 2; + } + this.consumed_len_bytes = pspecial - i; + return this; + } else if (u8array[i] < 0x3a) { + // Special representation of tiny sets with arbitrary 32-bit integers + const lspecial = u8array[i]; + this.keysAndCardinalities = new Uint8Array(lspecial * 4); + let pspecial = i + 1; + for (let ispecial = 0; ispecial < lspecial; ispecial += 1) { + const key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8); + const value = u8array[pspecial] | (u8array[pspecial + 1] << 8); + const container = this.addToArrayAt(key); + const cardinalityOld = container.cardinality; + container.array[cardinalityOld * 2] = value & 0xFF; + container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF; + container.cardinality = cardinalityOld + 1; + pspecial += 4; + } + this.consumed_len_bytes = pspecial - i; + return this; + } + // https://github.com/RoaringBitmap/RoaringFormatSpec + // + // Roaring bitmaps are used for flags that can be kept in their + // compressed form, even when loaded into memory. This decoder + // turns the containers into objects, but uses byte array + // slices of the original format for the data payload. + const has_runs = u8array[i] === 0x3b; + if (u8array[i] !== 0x3a && u8array[i] !== 0x3b) { + throw new Error("not a roaring bitmap: " + u8array[i]); + } + const size = has_runs ? + ((u8array[i + 2] | (u8array[i + 3] << 8)) + 1) : + ((u8array[i + 4] | (u8array[i + 5] << 8) | + (u8array[i + 6] << 16) | (u8array[i + 7] << 24))); + i += has_runs ? 4 : 8; + let is_run; + if (has_runs) { + const is_run_len = (size + 7) >> 3; + is_run = new Uint8Array(u8array.buffer, i + u8array.byteOffset, is_run_len); + i += is_run_len; + } else { + is_run = EMPTY_UINT8; + } + this.keysAndCardinalities = u8array.subarray(i, i + (size * 4)); + i += size * 4; + let offsets = null; + if (!has_runs || size >= 4) { + offsets = []; + for (let j = 0; j < size; ++j) { + offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) | + (u8array[i + 3] << 24)); + i += 4; + } + } + for (let j = 0; j < size; ++j) { + if (offsets && offsets[j] !== i - start) { + throw new Error(`corrupt bitmap ${j}: ${i - start} / ${offsets[j]}`); + } + const cardinality = (this.keysAndCardinalities[(j * 4) + 2] | + (this.keysAndCardinalities[(j * 4) + 3] << 8)) + 1; + if (is_run[j >> 3] & (1 << (j & 0x7))) { + const runcount = (u8array[i] | (u8array[i + 1] << 8)); + i += 2; + this.containers.push(new RoaringBitmapRun( + runcount, + new Uint8Array(u8array.buffer, i + u8array.byteOffset, runcount * 4), + )); + i += runcount * 4; + } else if (cardinality >= 4096) { + this.containers.push(new RoaringBitmapBits(new Uint8Array( + u8array.buffer, + i + u8array.byteOffset, 8192, + ))); + i += 8192; + } else { + const end = cardinality * 2; + this.containers.push(new RoaringBitmapArray( + cardinality, + new Uint8Array(u8array.buffer, i + u8array.byteOffset, end), + )); + i += end; + } + } + this.consumed_len_bytes = i - start; + } + /** + * @param {number} number + * @returns {RoaringBitmap} + */ + static makeSingleton(number) { + const result = new RoaringBitmap(null, 0); + result.keysAndCardinalities = Uint8Array.of( + (number >> 16), (number >> 24), + 0, 0, // keysAndCardinalities stores the true cardinality minus 1 + ); + result.containers.push(new RoaringBitmapArray( + 1, + Uint8Array.of(number, number >> 8), + )); + return result; + } + /** @returns {RoaringBitmap} */ + static everything() { + if (EVERYTHING_BITMAP.isEmpty()) { + let i = 0; + const l = 1 << 16; + const everything_range = new RoaringBitmapRun(1, Uint8Array.of(0, 0, 0xff, 0xff)); + EVERYTHING_BITMAP.keysAndCardinalities = new Uint8Array(l * 4); + while (i < l) { + EVERYTHING_BITMAP.containers.push(everything_range); + // key + EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 0] = i; + EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 1] = i >> 8; + // cardinality (minus one) + EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 2] = 0xff; + EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 3] = 0xff; + i += 1; + } + } + return EVERYTHING_BITMAP; + } + /** @returns {RoaringBitmap} */ + static empty() { + return EMPTY_BITMAP; + } + /** @returns {boolean} */ + isEmpty() { + return this.containers.length === 0; + } + /** + * Helper function used when constructing bitmaps from lists. + * Returns an array container with at least two free byte slots + * and bumps `this.cardinalities`. + * @param {number} key + * @returns {RoaringBitmapArray} + */ + addToArrayAt(key) { + let mid = this.getContainerId(key); + /** @type {RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun} */ + let container; + if (mid === -1) { + container = new RoaringBitmapArray(0, new Uint8Array(2)); + mid = this.containers.length; + this.containers.push(container); + if (mid * 4 > this.keysAndCardinalities.length) { + const keysAndContainers = new Uint8Array(mid * 8); + keysAndContainers.set(this.keysAndCardinalities); + this.keysAndCardinalities = keysAndContainers; + } + this.keysAndCardinalities[(mid * 4) + 0] = key; + this.keysAndCardinalities[(mid * 4) + 1] = key >> 8; + } else { + container = this.containers[mid]; + const cardinalityOld = + this.keysAndCardinalities[(mid * 4) + 2] | + (this.keysAndCardinalities[(mid * 4) + 3] << 8); + const cardinality = cardinalityOld + 1; + this.keysAndCardinalities[(mid * 4) + 2] = cardinality; + this.keysAndCardinalities[(mid * 4) + 3] = cardinality >> 8; + } + // the logic for handing this number is annoying, because keysAndCardinalities stores + // the cardinality *minus one*, so that it can count up to 65536 with only two bytes + // (because empty containers are never stored). + // + // So, if this is a new container, the stored cardinality contains `0 0`, which is + // the proper value of the old cardinality (an imaginary empty container existed). + // If this is adding to an existing container, then the above `else` branch bumps it + // by one, leaving us with a proper value of `cardinality - 1`. + const cardinalityOld = + this.keysAndCardinalities[(mid * 4) + 2] | + (this.keysAndCardinalities[(mid * 4) + 3] << 8); + if (!(container instanceof RoaringBitmapArray) || + container.array.byteLength < ((cardinalityOld + 1) * 2) + ) { + const newBuf = new Uint8Array((cardinalityOld + 1) * 4); + let idx = 0; + for (const cvalue of container.values()) { + newBuf[idx] = cvalue & 0xFF; + newBuf[idx + 1] = (cvalue >> 8) & 0xFF; + idx += 2; + } + if (container instanceof RoaringBitmapArray) { + container.cardinality = cardinalityOld; + container.array = newBuf; + return container; + } + const newcontainer = new RoaringBitmapArray(cardinalityOld, newBuf); + this.containers[mid] = newcontainer; + return newcontainer; + } else { + return container; + } + } + /** + * @param {RoaringBitmap} that + * @returns {RoaringBitmap} + */ + union(that) { + if (this.isEmpty()) { + return that; + } + if (that.isEmpty()) { + return this; + } + if (this === RoaringBitmap.everything() || that === RoaringBitmap.everything()) { + return RoaringBitmap.everything(); + } + let i = 0; + const il = this.containers.length; + let j = 0; + const jl = that.containers.length; + const result = new RoaringBitmap(null, 0); + result.keysAndCardinalities = new Uint8Array((il + jl) * 4); + while (i < il || j < jl) { + const ik = i * 4; + const jk = j * 4; + const k = result.containers.length * 4; + if (j >= jl || (i < il && ( + (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) || + (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] && + this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk]) + ))) { + result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; + result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; + result.keysAndCardinalities[k + 2] = this.keysAndCardinalities[ik + 2]; + result.keysAndCardinalities[k + 3] = this.keysAndCardinalities[ik + 3]; + result.containers.push(this.containers[i]); + i += 1; + } else if (i >= il || (j < jl && ( + (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) || + (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] && + that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik]) + ))) { + result.keysAndCardinalities[k + 0] = that.keysAndCardinalities[jk + 0]; + result.keysAndCardinalities[k + 1] = that.keysAndCardinalities[jk + 1]; + result.keysAndCardinalities[k + 2] = that.keysAndCardinalities[jk + 2]; + result.keysAndCardinalities[k + 3] = that.keysAndCardinalities[jk + 3]; + result.containers.push(that.containers[j]); + j += 1; + } else { + // this key is not smaller than that key + // that key is not smaller than this key + // they must be equal + const thisContainer = this.containers[i]; + const thatContainer = that.containers[j]; + let card = 0; + if (thisContainer instanceof RoaringBitmapBits && + thatContainer instanceof RoaringBitmapBits + ) { + const resultArray = new Uint8Array( + thisContainer.array.length > thatContainer.array.length ? + thisContainer.array.length : + thatContainer.array.length, + ); + let k = 0; + const kl = resultArray.length; + while (k < kl) { + const c = thisContainer.array[k] | thatContainer.array[k]; + resultArray[k] = c; + card += bitCount(c); + k += 1; + } + result.containers.push(new RoaringBitmapBits(resultArray)); + } else { + const thisValues = thisContainer.values(); + const thatValues = thatContainer.values(); + let thisResult = thisValues.next(); + let thatResult = thatValues.next(); + /** @type {Array<number>} */ + const resultValues = []; + while (!thatResult.done || !thisResult.done) { + // generator will definitely implement the iterator protocol correctly + /** @type {number} */ + const thisValue = thisResult.value; + /** @type {number} */ + const thatValue = thatResult.value; + if (thatResult.done || thisValue < thatValue) { + resultValues.push(thisValue); + thisResult = thisValues.next(); + } else if (thisResult.done || thatValue < thisValue) { + resultValues.push(thatValue); + thatResult = thatValues.next(); + } else { + // this value is not smaller than that value + // that value is not smaller than this value + // they must be equal + resultValues.push(thisValue); + thisResult = thisValues.next(); + thatResult = thatValues.next(); + } + } + const resultArray = new Uint8Array(resultValues.length * 2); + let k = 0; + for (const value of resultValues) { + // roaring bitmap is little endian + resultArray[k] = value & 0xFF; + resultArray[k + 1] = (value >> 8) & 0xFF; + k += 2; + } + result.containers.push(new RoaringBitmapArray( + resultValues.length, + resultArray, + )); + card = resultValues.length; + } + result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; + result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; + card -= 1; + result.keysAndCardinalities[k + 2] = card; + result.keysAndCardinalities[k + 3] = card >> 8; + i += 1; + j += 1; + } + } + return result; + } + /** + * @param {RoaringBitmap} that + * @returns {RoaringBitmap} + */ + intersection(that) { + if (this.isEmpty() || that.isEmpty()) { + return EMPTY_BITMAP; + } + if (this === RoaringBitmap.everything()) { + return that; + } + if (that === RoaringBitmap.everything()) { + return this; + } + let i = 0; + const il = this.containers.length; + let j = 0; + const jl = that.containers.length; + const result = new RoaringBitmap(null, 0); + result.keysAndCardinalities = new Uint8Array((il > jl ? il : jl) * 4); + while (i < il && j < jl) { + const ik = i * 4; + const jk = j * 4; + const k = result.containers.length * 4; + if (j >= jl || (i < il && ( + (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) || + (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] && + this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk]) + ))) { + i += 1; + } else if (i >= il || (j < jl && ( + (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) || + (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] && + that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik]) + ))) { + j += 1; + } else { + // this key is not smaller than that key + // that key is not smaller than this key + // they must be equal + const thisContainer = this.containers[i]; + const thatContainer = that.containers[j]; + let card = 0; + if (thisContainer instanceof RoaringBitmapBits && + thatContainer instanceof RoaringBitmapBits + ) { + const resultArray = new Uint8Array( + thisContainer.array.length > thatContainer.array.length ? + thisContainer.array.length : + thatContainer.array.length, + ); + let k = 0; + const kl = resultArray.length; + while (k < kl) { + const c = thisContainer.array[k] & thatContainer.array[k]; + resultArray[k] = c; + card += bitCount(c); + k += 1; + } + if (card !== 0) { + result.containers.push(new RoaringBitmapBits(resultArray)); + } + } else { + const thisValues = thisContainer.values(); + const thatValues = thatContainer.values(); + let thisValue = thisValues.next(); + let thatValue = thatValues.next(); + const resultValues = []; + while (!thatValue.done && !thisValue.done) { + if (thisValue.value < thatValue.value) { + thisValue = thisValues.next(); + } else if (thatValue.value < thisValue.value) { + thatValue = thatValues.next(); + } else { + // this value is not smaller than that value + // that value is not smaller than this value + // they must be equal + resultValues.push(thisValue.value); + thisValue = thisValues.next(); + thatValue = thatValues.next(); + } + } + card = resultValues.length; + if (card !== 0) { + const resultArray = new Uint8Array(resultValues.length * 2); + let k = 0; + for (const value of resultValues) { + // roaring bitmap is little endian + resultArray[k] = value & 0xFF; + resultArray[k + 1] = (value >> 8) & 0xFF; + k += 2; + } + result.containers.push(new RoaringBitmapArray( + resultValues.length, + resultArray, + )); + } + } + if (card !== 0) { + result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0]; + result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1]; + card -= 1; + result.keysAndCardinalities[k + 2] = card; + result.keysAndCardinalities[k + 3] = card >> 8; + } + i += 1; + j += 1; + } + } + return result; + } + /** @param {number} keyvalue */ + contains(keyvalue) { + const key = keyvalue >> 16; + const value = keyvalue & 0xFFFF; + const mid = this.getContainerId(key); + return mid === -1 ? false : this.containers[mid].contains(value); + } + /** + * @param {number} key + * @returns {number} + */ + getContainerId(key) { + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Format is required by specification to be sorted. + // Because keys are 16 bits and unique, length can't be + // bigger than 2**16, and because we have 32 bits of safe int, + // left + right can't overflow. + let left = 0; + let right = this.containers.length - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const x = this.keysAndCardinalities[(mid * 4)] | + (this.keysAndCardinalities[(mid * 4) + 1] << 8); + if (x < key) { + left = mid + 1; + } else if (x > key) { + right = mid - 1; + } else { + return mid; + } + } + return -1; + } + * entries() { + const l = this.containers.length; + for (let i = 0; i < l; ++i) { + const key = this.keysAndCardinalities[i * 4] | + (this.keysAndCardinalities[(i * 4) + 1] << 8); + for (const value of this.containers[i].values()) { + yield (key << 16) | value; + } + } + } + /** + * @returns {number|null} + */ + first() { + for (const entry of this.entries()) { + return entry; + } + return null; + } + /** + * @returns {number} + */ + cardinality() { + let result = 0; + const l = this.containers.length; + for (let i = 0; i < l; ++i) { + const card = this.keysAndCardinalities[(i * 4) + 2] | + (this.keysAndCardinalities[(i * 4) + 3] << 8); + result += card + 1; + } + return result; + } +} + +class RoaringBitmapRun { + /** + * @param {number} runcount + * @param {Uint8Array} array + */ + constructor(runcount, array) { + this.runcount = runcount; + this.array = array; + } + /** @param {number} value */ + contains(value) { + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Since runcount is stored as 16 bits, left + right + // can't overflow. + let left = 0; + let right = this.runcount - 1; + while (left <= right) { + const mid = (left + right) >> 1; + const i = mid * 4; + const start = this.array[i] | (this.array[i + 1] << 8); + const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8); + if ((start + lenm1) < value) { + left = mid + 1; + } else if (start > value) { + right = mid - 1; + } else { + return true; + } + } + return false; + } + * values() { + let i = 0; + while (i < this.runcount) { + const start = this.array[i * 4] | (this.array[(i * 4) + 1] << 8); + const lenm1 = this.array[(i * 4) + 2] | (this.array[(i * 4) + 3] << 8); + let value = start; + let j = 0; + while (j <= lenm1) { + yield value; + value += 1; + j += 1; + } + i += 1; + } + } +} +class RoaringBitmapArray { + /** + * @param {number} cardinality + * @param {Uint8Array} array + */ + constructor(cardinality, array) { + this.cardinality = cardinality; + this.array = array; + } + /** @param {number} value */ + contains(value) { + // Binary search algorithm copied from + // https://en.wikipedia.org/wiki/Binary_search#Procedure + // + // Since cardinality can't be higher than 4096, left + right + // cannot overflow. + let left = 0; + let right = this.cardinality - 1; + while (left <= right) { + const mid = (left + right) >> 1; + const i = mid * 2; + const x = this.array[i] | (this.array[i + 1] << 8); + if (x < value) { + left = mid + 1; + } else if (x > value) { + right = mid - 1; + } else { + return true; + } + } + return false; + } + /** @returns {Generator<number>} */ + * values() { + let i = 0; + const l = this.cardinality * 2; + while (i < l) { + yield this.array[i] | (this.array[i + 1] << 8); + i += 2; + } + } +} +class RoaringBitmapBits { + /** + * @param {Uint8Array} array + */ + constructor(array) { + this.array = array; + } + /** @param {number} value */ + contains(value) { + return !!(this.array[value >> 3] & (1 << (value & 7))); + } + * values() { + let i = 0; + const l = this.array.length << 3; + while (i < l) { + if (this.contains(i)) { + yield i; + } + i += 1; + } + } +} + +const EMPTY_BITMAP = new RoaringBitmap(null, 0); +EMPTY_BITMAP.consumed_len_bytes = 0; +const EMPTY_BITMAP1 = new RoaringBitmap(null, 0); +EMPTY_BITMAP1.consumed_len_bytes = 1; +const EVERYTHING_BITMAP = new RoaringBitmap(null, 0); + +/** + * A mapping from six byte nodeids to an arbitrary value. + * We don't just use `Map` because that requires double hashing. + * @template T + * @property {Uint8Array} keys + * @property {T[]} values + * @property {number} size + * @property {number} capacityClass + */ +class HashTable { + /** + * Construct an empty hash table. + */ + constructor() { + this.keys = EMPTY_UINT8; + /** @type {(T|undefined)[]} */ + this.values = []; + this.size = 0; + this.capacityClass = 0; + } + /** + * @returns {Generator<[Uint8Array, T]>} + */ + * entries() { + const keys = this.keys; + const values = this.values; + const l = this.values.length; + for (let i = 0; i < l; i += 1) { + const value = values[i]; + if (value !== undefined) { + yield [keys.subarray(i * 6, (i + 1) * 6), value]; + } + } + } + /** + * Add a value to the hash table. + * @param {Uint8Array} key + * @param {T} value + */ + set(key, value) { + // 90 % load factor + if (this.size * 10 >= this.values.length * 9) { + const keys = this.keys; + const values = this.values; + const l = values.length; + this.capacityClass += 1; + const capacity = 1 << this.capacityClass; + this.keys = new Uint8Array(capacity * 6); + this.values = []; + for (let i = 0; i < capacity; i += 1) { + this.values.push(undefined); + } + this.size = 0; + for (let i = 0; i < l; i += 1) { + const oldValue = values[i]; + if (oldValue !== undefined) { + this.setNoGrow(keys, i * 6, oldValue); + } + } + } + this.setNoGrow(key, 0, value); + } + /** + * @param {Uint8Array} key + * @param {number} start + * @param {T} value + */ + setNoGrow(key, start, value) { + const mask = ~(0xffffffff << this.capacityClass); + const keys = this.keys; + const values = this.values; + const l = 1 << this.capacityClass; + // because we know that our values are already hashed, + // just chop off the lower four bytes + let slot = ( + (key[start + 2] << 24) | + (key[start + 3] << 16) | + (key[start + 4] << 8) | + key[start + 5] + ) & mask; + for (let distance = 0; distance < l; ) { + const j = slot * 6; + const otherValue = values[slot]; + if (otherValue === undefined) { + values[slot] = value; + const keysStart = slot * 6; + keys[keysStart + 0] = key[start + 0]; + keys[keysStart + 1] = key[start + 1]; + keys[keysStart + 2] = key[start + 2]; + keys[keysStart + 3] = key[start + 3]; + keys[keysStart + 4] = key[start + 4]; + keys[keysStart + 5] = key[start + 5]; + this.size += 1; + break; + } else if ( + key[start + 0] === keys[j + 0] && + key[start + 1] === keys[j + 1] && + key[start + 2] === keys[j + 2] && + key[start + 3] === keys[j + 3] && + key[start + 4] === keys[j + 4] && + key[start + 5] === keys[j + 5] + ) { + values[slot] = value; + break; + } else { + const otherPreferredSlot = ( + (keys[j + 2] << 24) | (keys[j + 3] << 16) | + (keys[j + 4] << 8) | keys[j + 5] + ) & mask; + const otherDistance = otherPreferredSlot <= slot ? + slot - otherPreferredSlot : + (l - otherPreferredSlot) + slot; + if (distance > otherDistance) { + // if the other key is closer to its preferred slot than this one, + // then insert our node in its place and swap + // + // https://cglab.ca/~abeinges/blah/robinhood-part-1/ + const otherKey = keys.slice(j, j + 6); + values[slot] = value; + value = otherValue; + keys[j + 0] = key[start + 0]; + keys[j + 1] = key[start + 1]; + keys[j + 2] = key[start + 2]; + keys[j + 3] = key[start + 3]; + keys[j + 4] = key[start + 4]; + keys[j + 5] = key[start + 5]; + key = otherKey; + start = 0; + distance = otherDistance; + } + distance += 1; + slot = (slot + 1) & mask; + } + } + } + /** + * Retrieve a value + * @param {Uint8Array} key + * @returns {T|undefined} + */ + get(key) { + if (key.length !== 6) { + throw "invalid key"; + } + return this.getWithOffsetKey(key, 0); + } + /** + * Retrieve a value + * @param {Uint8Array} key + * @param {number} start + * @returns {T|undefined} + */ + getWithOffsetKey(key, start) { + const mask = ~(0xffffffff << this.capacityClass); + const keys = this.keys; + const values = this.values; + const l = 1 << this.capacityClass; + // because we know that our values are already hashed, + // just chop off the lower four bytes + let slot = ( + (key[start + 2] << 24) | + (key[start + 3] << 16) | + (key[start + 4] << 8) | + key[start + 5] + ) & mask; + for (let distance = 0; distance < l; distance += 1) { + const j = slot * 6; + const value = values[slot]; + if (value === undefined) { + break; + } else if ( + key[start + 0] === keys[j + 0] && + key[start + 1] === keys[j + 1] && + key[start + 2] === keys[j + 2] && + key[start + 3] === keys[j + 3] && + key[start + 4] === keys[j + 4] && + key[start + 5] === keys[j + 5] + ) { + return value; + } else { + const otherPreferredSlot = ( + (keys[j + 2] << 24) | (keys[j + 3] << 16) | + (keys[j + 4] << 8) | keys[j + 5] + ) & mask; + const otherDistance = otherPreferredSlot <= slot ? + slot - otherPreferredSlot : + (l - otherPreferredSlot) + slot; + if (distance > otherDistance) { + break; + } + } + slot = (slot + 1) & mask; + } + return undefined; + } +} + +/*eslint-disable */ +// ignore-tidy-linelength +/** <https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript> + * @param {number} n + * @returns {number} + */ +function bitCount(n) { + n = (~~n) - ((n >> 1) & 0x55555555); + n = (n & 0x33333333) + ((n >> 2) & 0x33333333); + return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; +} +/*eslint-enable */ + +/** + * @param {stringdex.Hooks} hooks + * @returns {Promise<stringdex.Database>} + */ +function loadDatabase(hooks) { + /** @type {stringdex.Callbacks} */ + const callbacks = { + rr_: function(data) { + const dataObj = JSON.parse(data); + for (const colName of Object.keys(dataObj)) { + if (Object.hasOwn(dataObj[colName], "I")) { + registry.searchTreeRoots.set( + colName, + makeSearchTreeFromBase64(dataObj[colName].I)[1], + ); + } + if (Object.hasOwn(dataObj[colName], "N")) { + const counts = []; + const countsstring = dataObj[colName]["N"]; + let i = 0; + const l = countsstring.length; + while (i < l) { + let n = 0; + let c = countsstring.charCodeAt(i); + while (c < 96) { // 96 = "`" + n = (n << 4) | (c & 0xF); + i += 1; + c = countsstring.charCodeAt(i); + } + n = (n << 4) | (c & 0xF); + counts.push(n); + i += 1; + } + registry.dataColumns.set(colName, new DataColumn( + counts, + makeUint8ArrayFromBase64(dataObj[colName]["H"]), + new RoaringBitmap(makeUint8ArrayFromBase64(dataObj[colName]["E"]), 0), + colName, + )); + } + } + const cb = registry.searchTreeRootCallback; + if (cb) { + cb(null, new Database(registry.searchTreeRoots, registry.dataColumns)); + } + }, + err_rr_: function(err) { + const cb = registry.searchTreeRootCallback; + if (cb) { + cb(err, null); + } + }, + rd_: function(dataString) { + const l = dataString.length; + const data = new Uint8Array(l); + for (let i = 0; i < l; ++i) { + data[i] = dataString.charCodeAt(i); + } + loadColumnFromBytes(data); + }, + err_rd_: function(filename, err) { + const nodeid = makeUint8ArrayFromHex(filename); + const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid); + if (cb) { + cb(err, null); + } + }, + rb_: function(dataString64) { + loadColumnFromBytes(makeUint8ArrayFromBase64(dataString64)); + }, + err_rb_: function(filename, err) { + const nodeid = makeUint8ArrayFromHex(filename); + const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid); + if (cb) { + cb(err, null); + } + }, + rn_: function(inputBase64) { + const [nodeid, tree] = makeSearchTreeFromBase64(inputBase64); + const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); + if (cb) { + cb(null, tree); + registry.searchTreeLoadPromiseCallbacks.set(nodeid, null); + } + }, + err_rn_: function(filename, err) { + const nodeid = makeUint8ArrayFromHex(filename); + const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); + if (cb) { + cb(err, null); + } + }, + }; + + /** + * @type {{ + * searchTreeRoots: Map<string, SearchTree>; + * searchTreeLoadPromiseCallbacks: HashTable<(function(any, SearchTree?): any)|null>; + * searchTreePromises: HashTable<Promise<SearchTree>>; + * dataColumnLoadPromiseCallbacks: HashTable<function(any, Uint8Array[]?): any>; + * dataColumns: Map<string, DataColumn>; + * dataColumnsBuckets: Map<string, HashTable<Promise<Uint8Array[]>>>; + * searchTreeLoadByNodeID: function(Uint8Array): Promise<SearchTree>; + * searchTreeRootCallback?: function(any, Database?): any; + * dataLoadByNameAndHash: function(string, Uint8Array): Promise<Uint8Array[]>; + * }} + */ + const registry = { + searchTreeRoots: new Map(), + searchTreeLoadPromiseCallbacks: new HashTable(), + searchTreePromises: new HashTable(), + dataColumnLoadPromiseCallbacks: new HashTable(), + dataColumns: new Map(), + dataColumnsBuckets: new Map(), + searchTreeLoadByNodeID: function(nodeid) { + const existingPromise = registry.searchTreePromises.get(nodeid); + if (existingPromise) { + return existingPromise; + } + /** @type {Promise<SearchTree>} */ + let newPromise; + if ((nodeid[0] & 0x80) !== 0) { + const isWhole = (nodeid[0] & 0x40) !== 0; + let leaves; + if ((nodeid[0] & 0x10) !== 0) { + let id1 = (nodeid[2] << 8) | nodeid[3]; + if ((nodeid[0] & 0x20) !== 0) { + // when data is present, id1 can be up to 20 bits + id1 |= ((nodeid[1] & 0x0f) << 16); + } else { + // otherwise, we fit in 28 + id1 |= ((nodeid[0] & 0x0f) << 24) | (nodeid[1] << 16); + } + const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]); + leaves = RoaringBitmap.makeSingleton(id1) + .union(RoaringBitmap.makeSingleton(id2)); + } else { + leaves = RoaringBitmap.makeSingleton( + (nodeid[2] << 24) | (nodeid[3] << 16) | + (nodeid[4] << 8) | nodeid[5], + ); + } + const data = (nodeid[0] & 0x20) !== 0 ? + Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) : + EMPTY_UINT8; + newPromise = Promise.resolve(new SearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_SEARCH_TREE_BRANCHES, + data, + isWhole ? leaves : EMPTY_BITMAP, + isWhole ? EMPTY_BITMAP : leaves, + )); + } else { + const hashHex = makeHexFromUint8Array(nodeid); + newPromise = new Promise((resolve, reject) => { + const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid); + if (cb) { + registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => { + cb(err, data); + if (data) { + resolve(data); + } else { + reject(err); + } + }); + } else { + registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => { + if (data) { + resolve(data); + } else { + reject(err); + } + }); + hooks.loadTreeByHash(hashHex); + } + }); + } + registry.searchTreePromises.set(nodeid, newPromise); + return newPromise; + }, + dataLoadByNameAndHash: function(name, hash) { + let dataColumnBuckets = registry.dataColumnsBuckets.get(name); + if (dataColumnBuckets === undefined) { + dataColumnBuckets = new HashTable(); + registry.dataColumnsBuckets.set(name, dataColumnBuckets); + } + const existingBucket = dataColumnBuckets.get(hash); + if (existingBucket) { + return existingBucket; + } + const hashHex = makeHexFromUint8Array(hash); + /** @type {Promise<Uint8Array[]>} */ + const newBucket = new Promise((resolve, reject) => { + const cb = registry.dataColumnLoadPromiseCallbacks.get(hash); + if (cb) { + registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => { + cb(err, data); + if (data) { + resolve(data); + } else { + reject(err); + } + }); + } else { + registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => { + if (data) { + resolve(data); + } else { + reject(err); + } + }); + hooks.loadDataByNameAndHash(name, hashHex); + } + }); + dataColumnBuckets.set(hash, newBucket); + return newBucket; + }, + }; + + /** + * The set of child subtrees. + * @type {{ + * nodeids: Uint8Array, + * subtrees: Array<Promise<SearchTree>|null>, + * }} + */ + class SearchTreeBranches { + /** + * Construct the subtree list with `length` nulls + * @param {number} length + * @param {Uint8Array} nodeids + */ + constructor(length, nodeids) { + this.nodeids = nodeids; + this.subtrees = []; + for (let i = 0; i < length; ++i) { + this.subtrees.push(null); + } + } + /** + * @param {number} i + * @returns {Uint8Array} + */ + getNodeID(i) { + return new Uint8Array( + this.nodeids.buffer, + this.nodeids.byteOffset + (i * 6), + 6, + ); + } + // https://github.com/microsoft/TypeScript/issues/17227 + /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + entries() { + throw new Error(); + } + /** + * @param {number} _k + * @returns {number} + */ + getIndex(_k) { + throw new Error(); + } + /** + * @param {number} _i + * @returns {number} + */ + getKey(_i) { + throw new Error(); + } + /** + * @returns {Uint8Array} + */ + getKeys() { + throw new Error(); + } + } + + /** + * A sorted array of search tree branches. + * + * @type {{ + * keys: Uint8Array, + * nodeids: Uint8Array, + * subtrees: Array<Promise<SearchTree>|null>, + * }} + */ + class SearchTreeBranchesArray extends SearchTreeBranches { + /** + * @param {Uint8Array} keys + * @param {Uint8Array} nodeids + */ + constructor(keys, nodeids) { + super(keys.length, nodeids); + this.keys = keys; + let i = 1; + while (i < this.keys.length) { + if (this.keys[i - 1] >= this.keys[i]) { + throw new Error("HERE"); + } + i += 1; + } + } + /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + * entries() { + let i = 0; + const l = this.keys.length; + while (i < l) { + yield [this.keys[i], this.subtrees[i]]; + i += 1; + } + } + /** + * @param {number} k + * @returns {number} + */ + getIndex(k) { + // Since length can't be bigger than 256, + // left + right can't overflow. + let left = 0; + let right = this.keys.length - 1; + while (left <= right) { + const mid = (left + right) >> 1; + if (this.keys[mid] < k) { + left = mid + 1; + } else if (this.keys[mid] > k) { + right = mid - 1; + } else { + return mid; + } + } + return -1; + } + /** + * @param {number} i + * @returns {number} + */ + getKey(i) { + return this.keys[i]; + } + /** + * @returns {Uint8Array} + */ + getKeys() { + return this.keys; + } + } + + const EMPTY_SEARCH_TREE_BRANCHES = new SearchTreeBranchesArray( + EMPTY_UINT8, + EMPTY_UINT8, + ); + + /** @type {number[]} */ + const SHORT_ALPHABITMAP_CHARS = []; + for (let i = 0x61; i <= 0x7A; ++i) { + if (i === 0x76 || i === 0x71) { + // 24 entries, 26 letters, so we skip q and v + continue; + } + SHORT_ALPHABITMAP_CHARS.push(i); + } + + /** @type {number[]} */ + const LONG_ALPHABITMAP_CHARS = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36]; + for (let i = 0x61; i <= 0x7A; ++i) { + LONG_ALPHABITMAP_CHARS.push(i); + } + + /** + * @param {number[]} alphabitmap_chars + * @param {number} width + * @return {(typeof SearchTreeBranches)&{"ALPHABITMAP_CHARS": number[], "width": number}} + */ + function makeSearchTreeBranchesAlphaBitmapClass(alphabitmap_chars, width) { + const bitwidth = width * 8; + const cls = class SearchTreeBranchesAlphaBitmap extends SearchTreeBranches { + /** + * @param {number} bitmap + * @param {Uint8Array} nodeids + */ + constructor(bitmap, nodeids) { + super(nodeids.length / 6, nodeids); + if (nodeids.length / 6 !== bitCount(bitmap)) { + throw new Error(`mismatch ${bitmap} ${nodeids}`); + } + this.bitmap = bitmap; + this.nodeids = nodeids; + } + /** @returns {Generator<[number, Promise<SearchTree>|null]>} */ + * entries() { + let i = 0; + let j = 0; + while (i < bitwidth) { + if (this.bitmap & (1 << i)) { + yield [alphabitmap_chars[i], this.subtrees[j]]; + j += 1; + } + i += 1; + } + } + /** + * @param {number} k + * @returns {number} + */ + getIndex(k) { + //return this.getKeys().indexOf(k); + const ix = alphabitmap_chars.indexOf(k); + if (ix < 0) { + return ix; + } + const result = bitCount(~(0xffffffff << ix) & this.bitmap); + return result >= this.subtrees.length ? -1 : result; + } + /** + * @param {number} branch_index + * @returns {number} + */ + getKey(branch_index) { + //return this.getKeys()[branch_index]; + let alpha_index = 0; + while (branch_index >= 0) { + if (this.bitmap & (1 << alpha_index)) { + branch_index -= 1; + } + alpha_index += 1; + } + return alphabitmap_chars[alpha_index]; + } + /** + * @returns {Uint8Array} + */ + getKeys() { + const length = bitCount(this.bitmap); + const result = new Uint8Array(length); + let result_index = 0; + for (let alpha_index = 0; alpha_index < bitwidth; ++alpha_index) { + if (this.bitmap & (1 << alpha_index)) { + result[result_index] = alphabitmap_chars[alpha_index]; + result_index += 1; + } + } + return result; + } + }; + cls.ALPHABITMAP_CHARS = alphabitmap_chars; + cls.width = width; + return cls; + } + + const SearchTreeBranchesShortAlphaBitmap = + makeSearchTreeBranchesAlphaBitmapClass(SHORT_ALPHABITMAP_CHARS, 3); + + const SearchTreeBranchesLongAlphaBitmap = + makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4); + + /** + * A [suffix tree], used for name-based search. + * + * This data structure is used to drive substring matches, + * such as matching the query "link" to `LinkedList`, + * and Lev-distance matches, such as matching the + * query "hahsmap" to `HashMap`. + * + * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree + * + * branches + * : A sorted-array map of subtrees. + * + * data + * : The substring represented by this node. The root node + * is always empty. + * + * leaves_suffix + * : The IDs of every entry that matches. Levenshtein matches + * won't include these. + * + * leaves_whole + * : The IDs of every entry that matches exactly. Levenshtein matches + * will include these. + * + * @type {{ + * might_have_prefix_branches: SearchTreeBranches, + * branches: SearchTreeBranches, + * data: Uint8Array, + * leaves_suffix: RoaringBitmap, + * leaves_whole: RoaringBitmap, + * }} + */ + class SearchTree { + /** + * @param {SearchTreeBranches} branches + * @param {SearchTreeBranches} might_have_prefix_branches + * @param {Uint8Array} data + * @param {RoaringBitmap} leaves_whole + * @param {RoaringBitmap} leaves_suffix + */ + constructor( + branches, + might_have_prefix_branches, + data, + leaves_whole, + leaves_suffix, + ) { + this.might_have_prefix_branches = might_have_prefix_branches; + this.branches = branches; + this.data = data; + this.leaves_suffix = leaves_suffix; + this.leaves_whole = leaves_whole; + } + /** + * Returns the Trie for the root node. + * + * A Trie pointer refers to a single node in a logical decompressed search tree + * (the real search tree is compressed). + * + * @return {Trie} + */ + trie() { + return new Trie(this, 0); + } + + /** + * Return the trie representing `name` + * @param {Uint8Array|string} name + * @returns {Promise<Trie?>} + */ + async search(name) { + if (typeof name === "string") { + const utf8encoder = new TextEncoder(); + name = utf8encoder.encode(name); + } + let trie = this.trie(); + for (const datum of name) { + // code point definitely exists + const newTrie = trie.child(datum); + if (newTrie) { + trie = await newTrie; + } else { + return null; + } + } + return trie; + } + + /** + * @param {Uint8Array|string} name + * @returns {AsyncGenerator<Trie>} + */ + async* searchLev(name) { + if (typeof name === "string") { + const utf8encoder = new TextEncoder(); + name = utf8encoder.encode(name); + } + const w = name.length; + if (w < 3) { + const trie = await this.search(name); + if (trie !== null) { + yield trie; + } + return; + } + const levParams = w >= 6 ? + new Lev2TParametricDescription(w) : + new Lev1TParametricDescription(w); + /** @type {Array<[Promise<Trie>, number]>} */ + const stack = [[Promise.resolve(this.trie()), 0]]; + const n = levParams.n; + while (stack.length !== 0) { + // It's not empty + /** @type {[Promise<Trie>, number]} */ + //@ts-expect-error + const [triePromise, levState] = stack.pop(); + const trie = await triePromise; + for (const byte of trie.keysExcludeSuffixOnly()) { + const levPos = levParams.getPosition(levState); + const vector = levParams.getVector( + name, + byte, + levPos, + Math.min(w, levPos + (2 * n) + 1), + ); + const newLevState = levParams.transition( + levState, + levPos, + vector, + ); + if (newLevState >= 0) { + const child = trie.child(byte); + if (child) { + stack.push([child, newLevState]); + if (levParams.isAccept(newLevState)) { + yield child; + } + } + } + } + } + } + } + + /** + * A representation of a set of strings in the search index, + * as a subset of the entire tree. + */ + class Trie { + /** + * @param {SearchTree} tree + * @param {number} offset + */ + constructor(tree, offset) { + this.tree = tree; + this.offset = offset; + } + + /** + * All exact matches for the string represented by this node. + * @returns {RoaringBitmap} + */ + matches() { + if (this.offset === this.tree.data.length) { + return this.tree.leaves_whole; + } else { + return EMPTY_BITMAP; + } + } + + /** + * All matches for strings that contain the string represented by this node. + * @returns {AsyncGenerator<RoaringBitmap>} + */ + async* substringMatches() { + /** @type {Promise<SearchTree>[]} */ + let layer = [Promise.resolve(this.tree)]; + while (layer.length) { + const current_layer = layer; + layer = []; + for await (const tree of current_layer) { + yield tree.leaves_whole.union(tree.leaves_suffix); + } + /** @type {HashTable<[number, SearchTree][]>} */ + const subnodes = new HashTable(); + for await (const node of current_layer) { + const branches = node.branches; + const l = branches.subtrees.length; + for (let i = 0; i < l; ++i) { + const subtree = branches.subtrees[i]; + if (subtree) { + layer.push(subtree); + } else if (subtree === null) { + const byte = branches.getKey(i); + const newnode = branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for key ${byte}`); + } else { + let subnode_list = subnodes.get(newnode); + if (!subnode_list) { + subnode_list = [[byte, node]]; + subnodes.set(newnode, subnode_list); + } else { + subnode_list.push([byte, node]); + } + } + } else { + throw new Error(`malformed tree; index ${i} does not exist`); + } + } + } + for (const [newnode, subnode_list] of subnodes.entries()) { + const res = registry.searchTreeLoadByNodeID(newnode); + for (const [byte, node] of subnode_list) { + const branches = node.branches; + const might_have_prefix_branches = node.might_have_prefix_branches; + const i = branches.getIndex(byte); + branches.subtrees[i] = res; + const mhpI = might_have_prefix_branches.getIndex(byte); + if (mhpI !== -1) { + might_have_prefix_branches.subtrees[mhpI] = res; + } + } + layer.push(res); + } + } + } + + /** + * All matches for strings that start with the string represented by this node. + * @returns {AsyncGenerator<RoaringBitmap>} + */ + async* prefixMatches() { + /** @type {{node: Promise<SearchTree>, len: number}[]} */ + let layer = [{node: Promise.resolve(this.tree), len: 0}]; + // https://en.wikipedia.org/wiki/Heap_(data_structure)#Implementation_using_arrays + /** @type {{bitmap: RoaringBitmap, length: number}[]} */ + const backlog = []; + while (layer.length !== 0 || backlog.length !== 0) { + const current_layer = layer; + layer = []; + let minLength = null; + // push every entry in the current layer into the backlog, + // a min-heap of result entries + // we then yield the smallest ones (can't yield bigger ones + // if we want to do them in order) + for (const {node, len} of current_layer) { + const tree = await node; + const length = len + tree.data.length; + if (minLength === null || length < minLength) { + minLength = length; + } + let backlogSlot = backlog.length; + backlog.push({bitmap: tree.leaves_whole, length}); + while (backlogSlot > 0 && + backlog[backlogSlot].length < backlog[(backlogSlot - 1) >> 1].length + ) { + const parentSlot = (backlogSlot - 1) >> 1; + const parent = backlog[parentSlot]; + backlog[parentSlot] = backlog[backlogSlot]; + backlog[backlogSlot] = parent; + backlogSlot = parentSlot; + } + } + // yield nodes in length order, smallest one first + // we know that, whatever the smallest item is + // every child will be bigger than that + while (backlog.length !== 0) { + const backlogEntry = backlog[0]; + if (minLength !== null && backlogEntry.length > minLength) { + break; + } + if (!backlogEntry.bitmap.isEmpty()) { + yield backlogEntry.bitmap; + } + backlog[0] = backlog[backlog.length - 1]; + backlog.length -= 1; + let backlogSlot = 0; + const backlogLength = backlog.length; + while (backlogSlot < backlogLength) { + const leftSlot = (backlogSlot << 1) + 1; + const rightSlot = (backlogSlot << 1) + 2; + let smallest = backlogSlot; + if (leftSlot < backlogLength && + backlog[leftSlot].length < backlog[smallest].length + ) { + smallest = leftSlot; + } + if (rightSlot < backlogLength && + backlog[rightSlot].length < backlog[smallest].length + ) { + smallest = rightSlot; + } + if (smallest === backlogSlot) { + break; + } else { + const tmp = backlog[backlogSlot]; + backlog[backlogSlot] = backlog[smallest]; + backlog[smallest] = tmp; + backlogSlot = smallest; + } + } + } + // if we still have more subtrees to walk, then keep going + /** @type {HashTable<{byte: number, tree: SearchTree, len: number}[]>} */ + const subnodes = new HashTable(); + for await (const {node, len} of current_layer) { + const tree = await node; + const length = len + tree.data.length; + const mhp_branches = tree.might_have_prefix_branches; + const l = mhp_branches.subtrees.length; + for (let i = 0; i < l; ++i) { + const len = length + 1; + const subtree = mhp_branches.subtrees[i]; + if (subtree) { + layer.push({node: subtree, len}); + } else if (subtree === null) { + const byte = mhp_branches.getKey(i); + const newnode = mhp_branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for key ${byte}`); + } else { + let subnode_list = subnodes.get(newnode); + if (!subnode_list) { + subnode_list = [{byte, tree, len}]; + subnodes.set(newnode, subnode_list); + } else { + subnode_list.push({byte, tree, len}); + } + } + } else { + throw new Error(`malformed tree; index ${i} does not exist`); + } + } + } + for (const [newnode, subnode_list] of subnodes.entries()) { + const res = registry.searchTreeLoadByNodeID(newnode); + let len = Number.MAX_SAFE_INTEGER; + for (const {byte, tree, len: subtreelen} of subnode_list) { + if (subtreelen < len) { + len = subtreelen; + } + const mhp_branches = tree.might_have_prefix_branches; + const i = mhp_branches.getIndex(byte); + mhp_branches.subtrees[i] = res; + const branches = tree.branches; + const bi = branches.getIndex(byte); + branches.subtrees[bi] = res; + } + layer.push({node: res, len}); + } + } + } + + /** + * Returns all keys that are children of this node. + * @returns {Uint8Array} + */ + keys() { + const data = this.tree.data; + if (this.offset === data.length) { + return this.tree.branches.getKeys(); + } else { + return Uint8Array.of(data[this.offset]); + } + } + + /** + * Returns all nodes that are direct children of this node. + * @returns {[number, Promise<Trie>][]} + */ + children() { + const data = this.tree.data; + if (this.offset === data.length) { + /** @type {[number, Promise<Trie>][]} */ + const nodes = []; + let i = 0; + for (const [k, v] of this.tree.branches.entries()) { + /** @type {Promise<SearchTree>} */ + let node; + if (v) { + node = v; + } else { + const newnode = this.tree.branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no hash for key ${k}: ${newnode} \ + ${this.tree.branches.nodeids} ${this.tree.branches.getKeys()}`); + } + node = registry.searchTreeLoadByNodeID(newnode); + this.tree.branches.subtrees[i] = node; + const mhpI = this.tree.might_have_prefix_branches.getIndex(k); + if (mhpI !== -1) { + this.tree.might_have_prefix_branches.subtrees[mhpI] = node; + } + } + nodes.push([k, node.then(node => node.trie())]); + i += 1; + } + return nodes; + } else { + /** @type {number} */ + const codePoint = data[this.offset]; + const trie = new Trie(this.tree, this.offset + 1); + return [[codePoint, Promise.resolve(trie)]]; + } + } + + /** + * Returns all keys that are children of this node. + * @returns {Uint8Array} + */ + keysExcludeSuffixOnly() { + const data = this.tree.data; + if (this.offset === data.length) { + return this.tree.might_have_prefix_branches.getKeys(); + } else { + return Uint8Array.of(data[this.offset]); + } + } + + /** + * Returns all nodes that are direct children of this node. + * @returns {[number, Promise<Trie>][]} + */ + childrenExcludeSuffixOnly() { + const data = this.tree.data; + if (this.offset === data.length) { + /** @type {[number, Promise<Trie>][]} */ + const nodes = []; + let i = 0; + for (const [k, v] of this.tree.might_have_prefix_branches.entries()) { + /** @type {Promise<SearchTree>} */ + let node; + if (v) { + node = v; + } else { + const newnode = this.tree.might_have_prefix_branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for key ${k}`); + } + node = registry.searchTreeLoadByNodeID(newnode); + this.tree.might_have_prefix_branches.subtrees[i] = node; + this.tree.branches.subtrees[this.tree.branches.getIndex(k)] = node; + } + nodes.push([k, node.then(node => node.trie())]); + i += 1; + } + return nodes; + } else { + /** @type {number} */ + const codePoint = data[this.offset]; + const trie = new Trie(this.tree, this.offset + 1); + return [[codePoint, Promise.resolve(trie)]]; + } + } + + /** + * Returns a single node that is a direct child of this node. + * @param {number} byte + * @returns {Promise<Trie>?} + */ + child(byte) { + if (this.offset === this.tree.data.length) { + const i = this.tree.branches.getIndex(byte); + if (i !== -1) { + let branch = this.tree.branches.subtrees[i]; + if (branch === null) { + const newnode = this.tree.branches.getNodeID(i); + if (!newnode) { + throw new Error(`malformed tree; no node for key ${byte}`); + } + branch = registry.searchTreeLoadByNodeID(newnode); + this.tree.branches.subtrees[i] = branch; + const mhpI = this.tree.might_have_prefix_branches.getIndex(byte); + if (mhpI !== -1) { + this.tree.might_have_prefix_branches.subtrees[mhpI] = branch; + } + } + return branch.then(branch => branch.trie()); + } + } else if (this.tree.data[this.offset] === byte) { + return Promise.resolve(new Trie(this.tree, this.offset + 1)); + } + return null; + } + } + + class DataColumn { + /** + * Construct the wrapper object for a data column. + * @param {number[]} counts + * @param {Uint8Array} hashes + * @param {RoaringBitmap} emptyset + * @param {string} name + */ + constructor(counts, hashes, emptyset, name) { + this.hashes = hashes; + this.emptyset = emptyset; + this.name = name; + /** @type {{"hash": Uint8Array, "data": Promise<Uint8Array[]>?, "end": number}[]} */ + this.buckets = []; + this.bucket_keys = []; + const l = counts.length; + let k = 0; + let totalLength = 0; + for (let i = 0; i < l; ++i) { + const count = counts[i]; + totalLength += count; + const start = k; + for (let j = 0; j < count; ++j) { + if (emptyset.contains(k)) { + j -= 1; + } + k += 1; + } + const end = k; + const bucket = {hash: hashes.subarray(i * 6, (i + 1) * 6), data: null, end, count}; + this.buckets.push(bucket); + this.bucket_keys.push(start); + } + this.length = totalLength; + } + /** + * Check if a cell contains the empty string. + * @param {number} id + * @returns {boolean} + */ + isEmpty(id) { + return this.emptyset.contains(id); + } + /** + * Look up a cell by row ID. + * @param {number} id + * @returns {Promise<Uint8Array|undefined>} + */ + async at(id) { + if (this.emptyset.contains(id)) { + return Promise.resolve(EMPTY_UINT8); + } else { + let idx = -1; + while (this.bucket_keys[idx + 1] <= id) { + idx += 1; + } + if (idx === -1 || idx >= this.bucket_keys.length) { + return Promise.resolve(undefined); + } else { + const start = this.bucket_keys[idx]; + const {hash, end} = this.buckets[idx]; + let data = this.buckets[idx].data; + if (data === null) { + const dataSansEmptyset = await registry.dataLoadByNameAndHash( + this.name, + hash, + ); + // After the `await` resolves, another task might fill + // in the data. If so, we should use that. + data = this.buckets[idx].data; + if (data !== null) { + return (await data)[id - start]; + } + /** @type {(Uint8Array[])|null} */ + let dataWithEmptyset = null; + let pos = start; + let insertCount = 0; + while (pos < end) { + if (this.emptyset.contains(pos)) { + if (dataWithEmptyset === null) { + dataWithEmptyset = dataSansEmptyset.splice(0, insertCount); + } else if (insertCount !== 0) { + dataWithEmptyset.push( + ...dataSansEmptyset.splice(0, insertCount), + ); + } + insertCount = 0; + dataWithEmptyset.push(EMPTY_UINT8); + } else { + insertCount += 1; + } + pos += 1; + } + data = Promise.resolve( + dataWithEmptyset === null ? + dataSansEmptyset : + dataWithEmptyset.concat(dataSansEmptyset), + ); + this.buckets[idx].data = data; + } + return (await data)[id - start]; + } + } + } + } + + class Database { + /** + * The primary frontend for accessing data in this index. + * + * @param {Map<string, SearchTree>} searchTreeRoots + * @param {Map<string, DataColumn>} dataColumns + */ + constructor(searchTreeRoots, dataColumns) { + this.searchTreeRoots = searchTreeRoots; + this.dataColumns = dataColumns; + } + /** + * Search a column by name, returning verbatim matched IDs. + * @param {string} colname + * @returns {SearchTree|undefined} + */ + getIndex(colname) { + return this.searchTreeRoots.get(colname); + } + /** + * Look up a cell by column ID and row ID. + * @param {string} colname + * @returns {DataColumn|undefined} + */ + getData(colname) { + return this.dataColumns.get(colname); + } + } + + /** + * Load a data column. + * @param {Uint8Array} data + */ + function loadColumnFromBytes(data) { + const hashBuf = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0); + const truncatedHash = hashBuf.subarray(2, 8); + siphashOfBytes(data, 0, 0, 0, 0, hashBuf); + const cb = registry.dataColumnLoadPromiseCallbacks.get(truncatedHash); + if (cb) { + const backrefs = []; + const dataSansEmptyset = []; + let i = 0; + const l = data.length; + while (i < l) { + let c = data[i]; + if (c >= 48 && c <= 63) { // 48 = "0", 63 = "?" + dataSansEmptyset.push(backrefs[c - 48]); + i += 1; + } else { + let n = 0; + while (c < 96) { // 96 = "`" + n = (n << 4) | (c & 0xF); + i += 1; + c = data[i]; + } + n = (n << 4) | (c & 0xF); + i += 1; + const item = data.subarray(i, i + n); + dataSansEmptyset.push(item); + i += n; + backrefs.unshift(item); + if (backrefs.length > 16) { + backrefs.pop(); + } + } + } + cb(null, dataSansEmptyset); + } + } + + /** + * @param {string} inputBase64 + * @returns {[Uint8Array, SearchTree]} + */ + function makeSearchTreeFromBase64(inputBase64) { + const input = makeUint8ArrayFromBase64(inputBase64); + let i = 0; + const l = input.length; + /** @type {HashTable<SearchTree>} */ + const stash = new HashTable(); + const hash = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0); + const truncatedHash = new Uint8Array(hash.buffer, 2, 6); + // used for handling compressed (that is, relative-offset) nodes + /** @type {{hash: Uint8Array, used: boolean}[]} */ + const hash_history = []; + /** @type {Uint8Array[]} */ + const data_history = []; + let canonical = EMPTY_UINT8; + /** @type {SearchTree} */ + let tree = new SearchTree( + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_SEARCH_TREE_BRANCHES, + EMPTY_UINT8, + EMPTY_BITMAP, + EMPTY_BITMAP, + ); + /** + * @param {Uint8Array} input + * @param {number} i + * @param {number} compression_tag + * @returns {{ + * "cpbranches": Uint8Array, + * "csbranches": Uint8Array, + * "might_have_prefix_branches": SearchTreeBranches, + * "branches": SearchTreeBranches, + * "cpnodes": Uint8Array, + * "csnodes": Uint8Array, + * "consumed_len_bytes": number, + * }} + */ + function makeBranchesFromBinaryData( + input, + i, + compression_tag, + ) { + const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0x00; + const is_stack_compressed = (compression_tag & 0x02) !== 0; + const is_long_compressed = (compression_tag & 0x04) !== 0; + const all_children_are_compressed = + (compression_tag & 0xF0) === 0xF0 && !is_long_compressed; + const any_children_are_compressed = + (compression_tag & 0xF0) !== 0x00 || is_long_compressed; + const start_point = i; + let cplen; + let cslen; + let alphabitmap = null; + if (is_pure_suffixes_only_node) { + cplen = 0; + cslen = input[i]; + i += 1; + if (cslen >= 0xc0) { + alphabitmap = SearchTreeBranchesLongAlphaBitmap; + cslen = cslen & 0x3F; + } else if (cslen >= 0x80) { + alphabitmap = SearchTreeBranchesShortAlphaBitmap; + cslen = cslen & 0x7F; + } + } else { + cplen = input[i]; + i += 1; + cslen = input[i]; + i += 1; + if (cplen === 0xff && cslen === 0xff) { + cplen = 0x100; + cslen = 0; + } else if (cplen >= 0xc0 && cslen >= 0xc0) { + alphabitmap = SearchTreeBranchesLongAlphaBitmap; + cplen = cplen & 0x3F; + cslen = cslen & 0x3F; + } else if (cplen >= 0x80 && cslen >= 0x80) { + alphabitmap = SearchTreeBranchesShortAlphaBitmap; + cplen = cplen & 0x7F; + cslen = cslen & 0x7F; + } + } + let j = 0; + /** @type {Uint8Array} */ + let cpnodes; + if (any_children_are_compressed) { + cpnodes = cplen === 0 ? EMPTY_UINT8 : new Uint8Array(cplen * 6); + while (j < cplen) { + const is_compressed = all_children_are_compressed || + ((0x10 << j) & compression_tag) !== 0; + if (is_compressed) { + let slot = hash_history.length - 1; + if (is_stack_compressed) { + while (hash_history[slot].used) { + slot -= 1; + } + } else { + slot -= input[i]; + i += 1; + } + hash_history[slot].used = true; + cpnodes.set( + hash_history[slot].hash, + j * 6, + ); + } else { + const joff = j * 6; + cpnodes[joff + 0] = input[i + 0]; + cpnodes[joff + 1] = input[i + 1]; + cpnodes[joff + 2] = input[i + 2]; + cpnodes[joff + 3] = input[i + 3]; + cpnodes[joff + 4] = input[i + 4]; + cpnodes[joff + 5] = input[i + 5]; + i += 6; + } + j += 1; + } + } else { + cpnodes = cplen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cplen * 6)); + i += cplen * 6; + } + j = 0; + /** @type {Uint8Array} */ + let csnodes; + if (any_children_are_compressed) { + csnodes = cslen === 0 ? EMPTY_UINT8 : new Uint8Array(cslen * 6); + while (j < cslen) { + const is_compressed = all_children_are_compressed || + ((0x10 << (cplen + j)) & compression_tag) !== 0; + if (is_compressed) { + let slot = hash_history.length - 1; + if (is_stack_compressed) { + while (hash_history[slot].used) { + slot -= 1; + } + } else { + slot -= input[i]; + i += 1; + } + hash_history[slot].used = true; + csnodes.set( + hash_history[slot].hash, + j * 6, + ); + } else { + const joff = j * 6; + csnodes[joff + 0] = input[i + 0]; + csnodes[joff + 1] = input[i + 1]; + csnodes[joff + 2] = input[i + 2]; + csnodes[joff + 3] = input[i + 3]; + csnodes[joff + 4] = input[i + 4]; + csnodes[joff + 5] = input[i + 5]; + i += 6; + } + j += 1; + } + } else { + csnodes = cslen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cslen * 6)); + i += cslen * 6; + } + let cpbranches; + let might_have_prefix_branches; + if (cplen === 0) { + cpbranches = EMPTY_UINT8; + might_have_prefix_branches = EMPTY_SEARCH_TREE_BRANCHES; + } else if (alphabitmap) { + cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width); + const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) | + (input[i + 2] << 16) | + (input[i + 1] << 8) | + input[i]; + might_have_prefix_branches = new alphabitmap(branchset, cpnodes); + i += alphabitmap.width; + } else { + cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, cplen); + might_have_prefix_branches = new SearchTreeBranchesArray(cpbranches, cpnodes); + i += cplen; + } + let csbranches; + let branches; + if (cslen === 0) { + csbranches = EMPTY_UINT8; + branches = might_have_prefix_branches; + } else if (alphabitmap) { + csbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width); + const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) | + (input[i + 2] << 16) | + (input[i + 1] << 8) | + input[i]; + if (cplen === 0) { + branches = new alphabitmap(branchset, csnodes); + } else { + const cpoffset = i - alphabitmap.width; + const cpbranchset = + (alphabitmap.width === 4 ? (input[cpoffset + 3] << 24) : 0) | + (input[cpoffset + 2] << 16) | + (input[cpoffset + 1] << 8) | + input[cpoffset]; + const hashes = new Uint8Array((cplen + cslen) * 6); + let cpi = 0; + let csi = 0; + let j = 0; + for (let k = 0; k < alphabitmap.ALPHABITMAP_CHARS.length; k += 1) { + if (branchset & (1 << k)) { + hashes[j + 0] = csnodes[csi + 0]; + hashes[j + 1] = csnodes[csi + 1]; + hashes[j + 2] = csnodes[csi + 2]; + hashes[j + 3] = csnodes[csi + 3]; + hashes[j + 4] = csnodes[csi + 4]; + hashes[j + 5] = csnodes[csi + 5]; + j += 6; + csi += 6; + } else if (cpbranchset & (1 << k)) { + hashes[j + 0] = cpnodes[cpi + 0]; + hashes[j + 1] = cpnodes[cpi + 1]; + hashes[j + 2] = cpnodes[cpi + 2]; + hashes[j + 3] = cpnodes[cpi + 3]; + hashes[j + 4] = cpnodes[cpi + 4]; + hashes[j + 5] = cpnodes[cpi + 5]; + j += 6; + cpi += 6; + } + } + branches = new alphabitmap(branchset | cpbranchset, hashes); + } + i += alphabitmap.width; + } else { + csbranches = new Uint8Array(input.buffer, i + input.byteOffset, cslen); + if (cplen === 0) { + branches = new SearchTreeBranchesArray(csbranches, csnodes); + } else { + const branchset = new Uint8Array(cplen + cslen); + const hashes = new Uint8Array((cplen + cslen) * 6); + let cpi = 0; + let csi = 0; + let j = 0; + while (cpi < cplen || csi < cslen) { + if (cpi >= cplen || (csi < cslen && cpbranches[cpi] > csbranches[csi])) { + branchset[j] = csbranches[csi]; + const joff = j * 6; + const csioff = csi * 6; + hashes[joff + 0] = csnodes[csioff + 0]; + hashes[joff + 1] = csnodes[csioff + 1]; + hashes[joff + 2] = csnodes[csioff + 2]; + hashes[joff + 3] = csnodes[csioff + 3]; + hashes[joff + 4] = csnodes[csioff + 4]; + hashes[joff + 5] = csnodes[csioff + 5]; + csi += 1; + } else { + branchset[j] = cpbranches[cpi]; + const joff = j * 6; + const cpioff = cpi * 6; + hashes[joff + 0] = cpnodes[cpioff + 0]; + hashes[joff + 1] = cpnodes[cpioff + 1]; + hashes[joff + 2] = cpnodes[cpioff + 2]; + hashes[joff + 3] = cpnodes[cpioff + 3]; + hashes[joff + 4] = cpnodes[cpioff + 4]; + hashes[joff + 5] = cpnodes[cpioff + 5]; + cpi += 1; + } + j += 1; + } + branches = new SearchTreeBranchesArray(branchset, hashes); + } + i += cslen; + } + return { + consumed_len_bytes: i - start_point, + cpbranches, + csbranches, + cpnodes, + csnodes, + branches, + might_have_prefix_branches, + }; + } + while (i < l) { + const start = i; + let data; + // compression_tag = 1 means pure-suffixes-only, + // which is not considered "compressed" for the purposes of this loop + // because that's the canonical, hashed version of the data + let compression_tag = input[i]; + const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0; + if (compression_tag > 1) { + // compressed node + const is_long_compressed = (compression_tag & 0x04) !== 0; + const is_data_compressed = (compression_tag & 0x08) !== 0; + i += 1; + if (is_long_compressed) { + compression_tag |= input[i] << 8; + i += 1; + compression_tag |= input[i] << 16; + i += 1; + } + let dlen = input[i]; + i += 1; + if (is_data_compressed) { + data = data_history[data_history.length - dlen - 1]; + dlen = data.length; + } else { + data = dlen === 0 ? + EMPTY_UINT8 : + new Uint8Array(input.buffer, i + input.byteOffset, dlen); + i += dlen; + } + const coffset = i; + const { + cpbranches, + csbranches, + cpnodes, + csnodes, + consumed_len_bytes: branches_consumed_len_bytes, + branches, + might_have_prefix_branches, + } = makeBranchesFromBinaryData(input, i, compression_tag); + i += branches_consumed_len_bytes; + let whole; + let suffix; + if (is_pure_suffixes_only_node) { + whole = EMPTY_BITMAP; + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } else if (input[i] === 0xff) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP1; + i += 1; + } else { + whole = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += whole.consumed_len_bytes; + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } + tree = new SearchTree( + branches, + might_have_prefix_branches, + data, + whole, + suffix, + ); + const clen = ( + (is_pure_suffixes_only_node ? 3 : 4) + // lengths of children and data + dlen + + cpnodes.length + csnodes.length + + cpbranches.length + csbranches.length + + whole.consumed_len_bytes + + suffix.consumed_len_bytes + ); + if (canonical.length < clen) { + canonical = new Uint8Array(clen); + } + let ci = 0; + canonical[ci] = is_pure_suffixes_only_node ? 1 : 0; + ci += 1; + canonical[ci] = dlen; + ci += 1; + canonical.set(data, ci); + ci += dlen; + canonical[ci] = input[coffset]; + ci += 1; + if (!is_pure_suffixes_only_node) { + canonical[ci] = input[coffset + 1]; + ci += 1; + } + canonical.set(cpnodes, ci); + ci += cpnodes.length; + canonical.set(csnodes, ci); + ci += csnodes.length; + canonical.set(cpbranches, ci); + ci += cpbranches.length; + canonical.set(csbranches, ci); + ci += csbranches.length; + const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes; + for (let j = leavesOffset; j < i; j += 1) { + canonical[ci + j - leavesOffset] = input[j]; + } + siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash); + hash[2] &= 0x7f; + } else { + // uncompressed node + const dlen = input [i + 1]; + i += 2; + if (dlen === 0) { + data = EMPTY_UINT8; + } else { + data = new Uint8Array(input.buffer, i + input.byteOffset, dlen); + } + i += dlen; + const { + consumed_len_bytes: branches_consumed_len_bytes, + branches, + might_have_prefix_branches, + } = makeBranchesFromBinaryData(input, i, compression_tag); + i += branches_consumed_len_bytes; + let whole; + let suffix; + if (is_pure_suffixes_only_node) { + whole = EMPTY_BITMAP; + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } else if (input[i] === 0xff) { + whole = EMPTY_BITMAP; + suffix = EMPTY_BITMAP; + i += 1; + } else { + whole = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += whole.consumed_len_bytes; + suffix = input[i] === 0 ? + EMPTY_BITMAP1 : + new RoaringBitmap(input, i); + i += suffix.consumed_len_bytes; + } + siphashOfBytes(new Uint8Array( + input.buffer, + start + input.byteOffset, + i - start, + ), 0, 0, 0, 0, hash); + hash[2] &= 0x7f; + tree = new SearchTree( + branches, + might_have_prefix_branches, + data, + whole, + suffix, + ); + } + hash_history.push({hash: truncatedHash.slice(), used: false}); + if (data.length !== 0) { + data_history.push(data); + } + const tree_branch_nodeids = tree.branches.nodeids; + const tree_branch_subtrees = tree.branches.subtrees; + let j = 0; + let lb = tree.branches.subtrees.length; + while (j < lb) { + // node id with a 1 in its most significant bit is inlined, and, so + // it won't be in the stash + if ((tree_branch_nodeids[j * 6] & 0x80) === 0) { + const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6); + if (subtree !== undefined) { + tree_branch_subtrees[j] = Promise.resolve(subtree); + } + } + j += 1; + } + const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids; + const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees; + j = 0; + lb = tree.might_have_prefix_branches.subtrees.length; + while (j < lb) { + // node id with a 1 in its most significant bit is inlined, and, so + // it won't be in the stash + if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) { + const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6); + if (subtree !== undefined) { + tree_mhp_branch_subtrees[j] = Promise.resolve(subtree); + } + } + j += 1; + } + if (i !== l) { + stash.set(truncatedHash, tree); + } + } + return [truncatedHash, tree]; + } + + return new Promise((resolve, reject) => { + registry.searchTreeRootCallback = (error, data) => { + if (data) { + resolve(data); + } else { + reject(error); + } + }; + hooks.loadRoot(callbacks); + }); +} + +if (typeof window !== "undefined") { + window.Stringdex = { + loadDatabase, + }; + window.RoaringBitmap = RoaringBitmap; + if (window.StringdexOnload) { + window.StringdexOnload.forEach(cb => cb(window.Stringdex)); + } +} else { + /** @type {stringdex.Stringdex} */ + // eslint-disable-next-line no-undef + module.exports.Stringdex = { + loadDatabase, + }; + /** @type {stringdex.RoaringBitmap} */ + // eslint-disable-next-line no-undef + module.exports.RoaringBitmap = RoaringBitmap; +} + +// eslint-disable-next-line max-len +// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64 +/** + * @type {function(string): Uint8Array} base64 + */ +//@ts-expect-error +const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => { + const bytes_as_string = atob(string); + const l = bytes_as_string.length; + const bytes = new Uint8Array(l); + for (let i = 0; i < l; ++i) { + bytes[i] = bytes_as_string.charCodeAt(i); + } + return bytes; +}); +/** + * @type {function(string): Uint8Array} base64 + */ +//@ts-expect-error +const makeUint8ArrayFromHex = Uint8Array.fromHex ? Uint8Array.fromHex : (string => { + /** @type {Object<string, number>} */ + const alpha = { + "0": 0, "1": 1, + "2": 2, "3": 3, + "4": 4, "5": 5, + "6": 6, "7": 7, + "8": 8, "9": 9, + "a": 10, "b": 11, + "A": 10, "B": 11, + "c": 12, "d": 13, + "C": 12, "D": 13, + "e": 14, "f": 15, + "E": 14, "F": 15, + }; + const l = string.length >> 1; + const bytes = new Uint8Array(l); + for (let i = 0; i < l; i += 1) { + const top = string[i << 1]; + const bottom = string[(i << 1) + 1]; + bytes[i] = (alpha[top] << 4) | alpha[bottom]; + } + return bytes; +}); + +/** + * @type {function(Uint8Array): string} base64 + */ +//@ts-expect-error +const makeHexFromUint8Array = Uint8Array.prototype.toHex ? (array => array.toHex()) : (array => { + /** @type {string[]} */ + const alpha = [ + "0", "1", + "2", "3", + "4", "5", + "6", "7", + "8", "9", + "a", "b", + "c", "d", + "e", "f", + ]; + const l = array.length; + const v = []; + for (let i = 0; i < l; ++i) { + v.push(alpha[array[i] >> 4]); + v.push(alpha[array[i] & 0xf]); + } + return v.join(""); +}); + +////////////// + +/** + * SipHash 1-3 + * @param {Uint8Array} input data to be hashed; all codepoints in string should be less than 256 + * @param {number} k0lo first word of key + * @param {number} k0hi second word of key + * @param {number} k1lo third word of key + * @param {number} k1hi fourth word of key + * @param {Uint8Array} output the data to write (clobber the first eight bytes) + */ +function siphashOfBytes(input, k0lo, k0hi, k1lo, k1hi, output) { + // hash state + // While siphash uses 64 bit state, js only has native support + // for 32 bit numbers. BigInt, unfortunately, doesn't count. + // It's too slow. + let v0lo = k0lo ^ 0x70736575; + let v0hi = k0hi ^ 0x736f6d65; + let v1lo = k1lo ^ 0x6e646f6d; + let v1hi = k1hi ^ 0x646f7261; + let v2lo = k0lo ^ 0x6e657261; + let v2hi = k0hi ^ 0x6c796765; + let v3lo = k1lo ^ 0x79746573; + let v3hi = k1hi ^ 0x74656462; + const inputLength = input.length; + let inputI = 0; + // main hash loop + const left = inputLength & 0x7; + let milo = 0; + let mihi = 0; + while (inputI < inputLength - left) { + u8ToU64le(inputI, inputI + 8); + v3lo ^= milo; + v3hi ^= mihi; + siphashCompress(); + v0lo ^= milo; + v0hi ^= mihi; + inputI += 8; + } + u8ToU64le(inputI, inputI + left); + // finish + const blo = milo; + const bhi = ((inputLength & 0xff) << 24) | mihi; + v3lo ^= blo; + v3hi ^= bhi; + siphashCompress(); + v0lo ^= blo; + v0hi ^= bhi; + v2lo ^= 0xff; + siphashCompress(); + siphashCompress(); + siphashCompress(); + output[7] = (v0lo ^ v1lo ^ v2lo ^ v3lo) & 0xff; + output[6] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 8; + output[5] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 16; + output[4] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 24; + output[3] = (v0hi ^ v1hi ^ v2hi ^ v3hi) & 0xff; + output[2] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 8; + output[1] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 16; + output[0] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 24; + /** + * Convert eight bytes to a single 64-bit number + * @param {number} offset + * @param {number} length + */ + function u8ToU64le(offset, length) { + const n0 = offset < length ? input[offset] & 0xff : 0; + const n1 = offset + 1 < length ? input[offset + 1] & 0xff : 0; + const n2 = offset + 2 < length ? input[offset + 2] & 0xff : 0; + const n3 = offset + 3 < length ? input[offset + 3] & 0xff : 0; + const n4 = offset + 4 < length ? input[offset + 4] & 0xff : 0; + const n5 = offset + 5 < length ? input[offset + 5] & 0xff : 0; + const n6 = offset + 6 < length ? input[offset + 6] & 0xff : 0; + const n7 = offset + 7 < length ? input[offset + 7] & 0xff : 0; + milo = n0 | (n1 << 8) | (n2 << 16) | (n3 << 24); + mihi = n4 | (n5 << 8) | (n6 << 16) | (n7 << 24); + } + function siphashCompress() { + // v0 += v1; + v0hi = (v0hi + v1hi + (((v0lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; + v0lo = (v0lo + v1lo) | 0; + // rotl(v1, 13) + let v1lo_ = v1lo; + let v1hi_ = v1hi; + v1lo = (v1lo_ << 13) | (v1hi_ >>> 19); + v1hi = (v1hi_ << 13) | (v1lo_ >>> 19); + // v1 ^= v0 + v1lo ^= v0lo; + v1hi ^= v0hi; + // rotl(v0, 32) + const v0lo_ = v0lo; + const v0hi_ = v0hi; + v0lo = v0hi_; + v0hi = v0lo_; + // v2 += v3 + v2hi = (v2hi + v3hi + (((v2lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; + v2lo = (v2lo + v3lo) | 0; + // rotl(v3, 16) + let v3lo_ = v3lo; + let v3hi_ = v3hi; + v3lo = (v3lo_ << 16) | (v3hi_ >>> 16); + v3hi = (v3hi_ << 16) | (v3lo_ >>> 16); + // v3 ^= v2 + v3lo ^= v2lo; + v3hi ^= v2hi; + // v0 += v3 + v0hi = (v0hi + v3hi + (((v0lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; + v0lo = (v0lo + v3lo) | 0; + // rotl(v3, 21) + v3lo_ = v3lo; + v3hi_ = v3hi; + v3lo = (v3lo_ << 21) | (v3hi_ >>> 11); + v3hi = (v3hi_ << 21) | (v3lo_ >>> 11); + // v3 ^= v0 + v3lo ^= v0lo; + v3hi ^= v0hi; + // v2 += v1 + v2hi = (v2hi + v1hi + (((v2lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0; + v2lo = (v2lo + v1lo) | 0; + // rotl(v1, 17) + v1lo_ = v1lo; + v1hi_ = v1hi; + v1lo = (v1lo_ << 17) | (v1hi_ >>> 15); + v1hi = (v1hi_ << 17) | (v1lo_ >>> 15); + // v1 ^= v2 + v1lo ^= v2lo; + v1hi ^= v2hi; + // rotl(v2, 32) + const v2lo_ = v2lo; + const v2hi_ = v2hi; + v2lo = v2hi_; + v2hi = v2lo_; + } +} + +////////////// + + +// Parts of this code are based on Lucene, which is licensed under the +// Apache/2.0 license. +// More information found here: +// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/ +// LevenshteinAutomata.java +class ParametricDescription { + /** + * @param {number} w + * @param {number} n + * @param {Int32Array} minErrors + */ + constructor(w, n, minErrors) { + this.w = w; + this.n = n; + this.minErrors = minErrors; + } + /** + * @param {number} absState + * @returns {boolean} + */ + isAccept(absState) { + const state = Math.floor(absState / (this.w + 1)); + const offset = absState % (this.w + 1); + return this.w - offset + this.minErrors[state] <= this.n; + } + /** + * @param {number} absState + * @returns {number} + */ + getPosition(absState) { + return absState % (this.w + 1); + } + /** + * @param {Uint8Array} name + * @param {number} charCode + * @param {number} pos + * @param {number} end + * @returns {number} + */ + getVector(name, charCode, pos, end) { + let vector = 0; + for (let i = pos; i < end; i += 1) { + vector = vector << 1; + if (name[i] === charCode) { + vector |= 1; + } + } + return vector; + } + /** + * @param {Int32Array} data + * @param {number} index + * @param {number} bitsPerValue + * @returns {number} + */ + unpack(data, index, bitsPerValue) { + const bitLoc = (bitsPerValue * index); + const dataLoc = bitLoc >> 5; + const bitStart = bitLoc & 31; + if (bitStart + bitsPerValue <= 32) { + // not split + return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]); + } else { + // split + const part = 32 - bitStart; + return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) + + ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part)); + } + } +} +ParametricDescription.prototype.MASKS = new Int32Array([ + 0x1, 0x3, 0x7, 0xF, + 0x1F, 0x3F, 0x7F, 0xFF, + 0x1FF, 0x3F, 0x7FF, 0xFFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, + 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF, + 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF, + 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +]); + +// The following code was generated with the moman/finenight pkg +// This package is available under the MIT License, see NOTICE.txt +// for more details. +// This class is auto-generated, Please do not modify it directly. +// You should modify the https://gitlab.com/notriddle/createAutomata.py instead. +// The following code was generated with the moman/finenight pkg +// This package is available under the MIT License, see NOTICE.txt +// for more details. +// This class is auto-generated, Please do not modify it directly. +// You should modify https://gitlab.com/notriddle/moman-rustdoc instead. + +class Lev2TParametricDescription extends ParametricDescription { + /** + * @param {number} absState + * @param {number} position + * @param {number} vector + * @returns {number} + */ + transition(absState, position, vector) { + let state = Math.floor(absState / (this.w + 1)); + let offset = absState % (this.w + 1); + + if (position === this.w) { + if (state < 3) { + const loc = Math.imul(vector, 3) + state; + offset += this.unpack(this.offsetIncrs0, loc, 1); + state = this.unpack(this.toStates0, loc, 2) - 1; + } + } else if (position === this.w - 1) { + if (state < 5) { + const loc = Math.imul(vector, 5) + state; + offset += this.unpack(this.offsetIncrs1, loc, 1); + state = this.unpack(this.toStates1, loc, 3) - 1; + } + } else if (position === this.w - 2) { + if (state < 13) { + const loc = Math.imul(vector, 13) + state; + offset += this.unpack(this.offsetIncrs2, loc, 2); + state = this.unpack(this.toStates2, loc, 4) - 1; + } + } else if (position === this.w - 3) { + if (state < 28) { + const loc = Math.imul(vector, 28) + state; + offset += this.unpack(this.offsetIncrs3, loc, 2); + state = this.unpack(this.toStates3, loc, 5) - 1; + } + } else if (position === this.w - 4) { + if (state < 45) { + const loc = Math.imul(vector, 45) + state; + offset += this.unpack(this.offsetIncrs4, loc, 3); + state = this.unpack(this.toStates4, loc, 6) - 1; + } + } else { + // eslint-disable-next-line no-lonely-if + if (state < 45) { + const loc = Math.imul(vector, 45) + state; + offset += this.unpack(this.offsetIncrs5, loc, 3); + state = this.unpack(this.toStates5, loc, 6) - 1; + } + } + + if (state === -1) { + // null state + return -1; + } else { + // translate back to abs + return Math.imul(state, this.w + 1) + offset; + } + } + + // state map + // 0 -> [(0, 0)] + // 1 -> [(0, 1)] + // 2 -> [(0, 2)] + // 3 -> [(0, 1), (1, 1)] + // 4 -> [(0, 2), (1, 2)] + // 5 -> [(0, 1), (1, 1), (2, 1)] + // 6 -> [(0, 2), (1, 2), (2, 2)] + // 7 -> [(0, 1), (2, 1)] + // 8 -> [(0, 1), (2, 2)] + // 9 -> [(0, 2), (2, 1)] + // 10 -> [(0, 2), (2, 2)] + // 11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] + // 12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)] + // 13 -> [(0, 2), (1, 2), (2, 2), (3, 2)] + // 14 -> [(0, 1), (1, 1), (3, 2)] + // 15 -> [(0, 1), (2, 2), (3, 2)] + // 16 -> [(0, 1), (3, 2)] + // 17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)] + // 18 -> [(0, 2), (1, 2), (3, 1)] + // 19 -> [(0, 2), (1, 2), (3, 2)] + // 20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)] + // 21 -> [(0, 2), (2, 1), (3, 1)] + // 22 -> [(0, 2), (2, 2), (3, 2)] + // 23 -> [(0, 2), (3, 1)] + // 24 -> [(0, 2), (3, 2)] + // 25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)] + // 26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)] + // 27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)] + // 28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] + // 29 -> [(0, 2), (1, 2), (2, 2), (4, 2)] + // 30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] + // 31 -> [(0, 2), (1, 2), (3, 2), (4, 2)] + // 32 -> [(0, 2), (1, 2), (4, 2)] + // 33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)] + // 34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] + // 35 -> [(0, 2), (2, 1), (4, 2)] + // 36 -> [(0, 2), (2, 2), (3, 2), (4, 2)] + // 37 -> [(0, 2), (2, 2), (4, 2)] + // 38 -> [(0, 2), (3, 2), (4, 2)] + // 39 -> [(0, 2), (4, 2)] + // 40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)] + // 41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)] + // 42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)] + // 43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)] + // 44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)] + + + /** @param {number} w - length of word being checked */ + constructor(w) { + super(w, 2, new Int32Array([ + 0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2, + -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2, + ])); + } +} + +Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ + 0xe, +]); +Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ + 0x0, +]); + +Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([ + 0x1a688a2c, +]); +Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ + 0x3e0, +]); + +Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([ + 0x70707054,0xdc07035,0x3dd3a3a,0x2323213a, + 0x15435223,0x22545432,0x5435, +]); +Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ + 0x80000,0x55582088,0x55555555,0x55, +]); + +Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([ + 0x1c0380a4,0x700a570,0xca529c0,0x180a00, + 0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8, + 0xac18c601,0xd8d43501,0x863500ad,0x51976d6a, + 0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5, + 0x18c4519,0xc41294a,0xe248d231,0x1086520c, + 0xce31ac42,0x13946358,0x2d0348c4,0x6732d494, + 0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948, + 0x22110a52,0x58ce729d,0xc41394e3,0x941cc520, + 0x90e732d4,0x4729d224,0x39ce35ad, +]); +Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ + 0x80000,0xc0c830,0x300f3c30,0x2200fcff, + 0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555, + 0x55555555,0x55555555,0x55555555,0x55555555, + 0x55555555,0x55555555, +]); + +Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([ + 0x801c0144,0x1453803,0x14700038,0xc0005145, + 0x1401,0x14,0x140000,0x0, + 0x510000,0x6301f007,0x301f00d1,0xa186178, + 0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd, + 0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34, + 0x8300550,0x430c0143,0x50c31,0xc30850c, + 0xc3143000,0x50053c50,0x5130d301,0x850d30c2, + 0x30a08608,0xc214414,0x43142145,0x21450031, + 0x1400c314,0x4c143145,0x32832803,0x28014d6c, + 0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3, + 0x1431,0xc300500,0xca00d303,0xd36d0e40, + 0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c, + 0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280, + 0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401, + 0x1430c714,0xc3087,0x71451450,0xca00d30, + 0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144, + 0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51, + 0x24324308,0xc51031c2,0x70820820,0x5c33830d, + 0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3, + 0x20c20c20,0xda0920d,0x5145914f,0x36596114, + 0x51965865,0xd9643653,0x365a6590,0x51964364, + 0x43081505,0x920b2032,0x2c718b28,0xd7242249, + 0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2, + 0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c, + 0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7, + 0x73975c36,0x10308138,0xc2245105,0x41451031, + 0x14e24208,0xc35c3387,0x51453851,0x1c51c514, + 0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092, + 0x4513d41,0x6533944d,0x1350e658,0xe1545055, + 0x64365a50,0x5519383,0x51030815,0x28920718, + 0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387, + 0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440, + 0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65, + 0x8e154387,0x364b5ca3,0x38739738, +]); +Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([ + 0x10000000,0xc00000,0x60061,0x400, + 0x0,0x80010008,0x249248a4,0x8229048, + 0x2092,0x6c3603,0xb61b6c30,0x6db6036d, + 0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b, + 0x6db6236,0x1008200,0x12480012,0x24924906, + 0x48200049,0x80410002,0x24000900,0x4924a489, + 0x10822492,0x20800125,0x48360,0x9241b692, + 0x6da4924,0x40009268,0x241b010,0x291b4900, + 0x6d249249,0x49493423,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x2492, +]); + +Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([ + 0x801c0144,0x1453803,0x14700038,0xc0005145, + 0x1401,0x14,0x140000,0x0, + 0x510000,0x4e00e007,0xe0051,0x3451451c, + 0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4, + 0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07, + 0x2830c286,0x830c3083,0xc30030,0x33430c, + 0x30c3003,0x70051030,0x16301f00,0x8301f00d, + 0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51, + 0x14314315,0x4f143145,0x34c05401,0x4c30944c, + 0x55150c3,0x30830055,0x1430c014,0xc00050c3, + 0xc30850,0xc314300,0x150053c5,0x25130d30, + 0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c, + 0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540, + 0x34c30944,0x82182214,0x851050c2,0x50851430, + 0x1400c50c,0x30c5085,0x50c51450,0x150053c, + 0xc25130d3,0x8850d30,0x1430a086,0x450c2144, + 0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1, + 0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c, + 0xc31c3140,0x31430c30,0x14,0x30c3005, + 0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3, + 0xc500d01,0x5965965a,0x30d46546,0x6435030c, + 0x8034c659,0xdb439032,0x2c390034,0xcaaecb24, + 0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033, + 0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e, + 0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce, + 0x95c53831,0x28034c5d,0x9b705583,0xa1455830, + 0xc76cd6c,0x40143085,0x71451c51,0x871430c, + 0x450000c3,0xd3071451,0x1560ca00,0x560c26dc, + 0xb35b2851,0xc914369,0x1a14500d,0x46593945, + 0xcb2c939,0x94507503,0x328034c3,0x9b70558, + 0xe41c5583,0x72caaeca,0x1c308510,0xc7147287, + 0x50871c32,0x1470030c,0xd307147,0xc1560ca0, + 0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c, + 0x8e657394,0x314b1c93,0x39438738,0x43083081, + 0x31c22432,0x820c510,0x830d7082,0x50c35c33, + 0xc30c338,0xc31c30c3,0x50c30c30,0xc204514, + 0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3, + 0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a, + 0x48308308,0xc3682483,0x14516453,0x4d965845, + 0xd4659619,0x36590d94,0xd969964,0x546590d9, + 0x20c20541,0x920d20c,0x5914f0da,0x96114514, + 0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e, + 0x81821827,0xb2032430,0x18b28920,0x422492c7, + 0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972, + 0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c, + 0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28, + 0x59a8a269,0x8175e7a,0xb203243,0x718b2892, + 0x4114105c,0x17597658,0x74ce5d96,0x5c36572d, + 0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8, + 0x4171c62,0x5d961045,0x976585d6,0x79669533, + 0x964965a2,0x659689e6,0x308175e7,0x24510510, + 0x451031c2,0xe2420841,0x5c338714,0x453851c3, + 0x51c51451,0xc30c31c,0x451450c7,0x41440c20, + 0xc708914,0x82105144,0xf1c58c90,0x1470ea0d, + 0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861, + 0x24853c51,0x5053c368,0x1341144f,0x96194ce5, + 0x1544d439,0x94385514,0xe0d90d96,0x5415464, + 0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0, + 0x350e6586,0x86082181,0xe89e981d,0x18277689, + 0x10308182,0x89207185,0x41c718b2,0x14e24224, + 0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb, + 0x204e1c75,0x1c61440c,0xc62ca248,0x90891071, + 0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475, + 0x5e7a57a8,0x51030817,0x28920718,0xf38718b, + 0xe5134114,0x39961758,0xe1ce4ce,0x728e3855, + 0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24, + 0xd04503ce,0x85d63944,0x75338e65,0x5d86075e, + 0x89e69647,0x75e76576, +]); +Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([ + 0x10000000,0xc00000,0x60061,0x400, + 0x0,0x60000008,0x6b003080,0xdb6ab6db, + 0x2db6,0x800400,0x49245240,0x11482412, + 0x104904,0x40020000,0x92292000,0xa4b25924, + 0x9649658,0xd80c000,0xdb0c001b,0x80db6d86, + 0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d, + 0xddadb6ed,0x300001b6,0x6c360,0xe37236e4, + 0x46db6236,0xdb6c,0x361b018,0xb91b7200, + 0x6dbb1b71,0x6db763,0x20100820,0x61248001, + 0x92492490,0x24820004,0x8041000,0x92400090, + 0x24924830,0x555b6a49,0x2080012,0x20004804, + 0x49252449,0x84112492,0x4000928,0x240201, + 0x92922490,0x58924924,0x49456,0x120d8082, + 0x6da4800,0x69249249,0x249a01b,0x6c04100, + 0x6d240009,0x92492483,0x24d5adb4,0x60208001, + 0x92000483,0x24925236,0x6846da49,0x10400092, + 0x241b0,0x49291b49,0x636d2492,0x92494935, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924,0x49249249, + 0x92492492,0x24924924,0x49249249,0x92492492, + 0x24924924,0x49249249,0x92492492,0x24924924, + 0x49249249,0x92492492,0x24924924, +]); + +class Lev1TParametricDescription extends ParametricDescription { + /** + * @param {number} absState + * @param {number} position + * @param {number} vector + * @returns {number} + */ + transition(absState, position, vector) { + let state = Math.floor(absState / (this.w + 1)); + let offset = absState % (this.w + 1); + + if (position === this.w) { + if (state < 2) { + const loc = Math.imul(vector, 2) + state; + offset += this.unpack(this.offsetIncrs0, loc, 1); + state = this.unpack(this.toStates0, loc, 2) - 1; + } + } else if (position === this.w - 1) { + if (state < 3) { + const loc = Math.imul(vector, 3) + state; + offset += this.unpack(this.offsetIncrs1, loc, 1); + state = this.unpack(this.toStates1, loc, 2) - 1; + } + } else if (position === this.w - 2) { + if (state < 6) { + const loc = Math.imul(vector, 6) + state; + offset += this.unpack(this.offsetIncrs2, loc, 2); + state = this.unpack(this.toStates2, loc, 3) - 1; + } + } else { + // eslint-disable-next-line no-lonely-if + if (state < 6) { + const loc = Math.imul(vector, 6) + state; + offset += this.unpack(this.offsetIncrs3, loc, 2); + state = this.unpack(this.toStates3, loc, 3) - 1; + } + } + + if (state === -1) { + // null state + return -1; + } else { + // translate back to abs + return Math.imul(state, this.w + 1) + offset; + } + } + + // state map + // 0 -> [(0, 0)] + // 1 -> [(0, 1)] + // 2 -> [(0, 1), (1, 1)] + // 3 -> [(0, 1), (1, 1), (2, 1)] + // 4 -> [(0, 1), (2, 1)] + // 5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)] + + + /** @param {number} w - length of word being checked */ + constructor(w) { + super(w, 1, new Int32Array([0,1,0,-1,-1,-1])); + } +} + +Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([ + 0x2, +]); +Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([ + 0x0, +]); + +Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([ + 0xa43, +]); +Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([ + 0x38, +]); + +Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([ + 0x12180003,0xb45a4914,0x69, +]); +Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([ + 0x558a0000,0x5555, +]); + +Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([ + 0x900c0003,0xa1904864,0x45a49169,0x5a6d196a, + 0x9634, +]); +Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([ + 0xa0fc0000,0x5555ba08,0x55555555, +]); diff --git a/src/librustdoc/html/static/js/tsconfig.json b/src/librustdoc/html/static/js/tsconfig.json index b81099bb9dfd..42993cb0f2ac 100644 --- a/src/librustdoc/html/static/js/tsconfig.json +++ b/src/librustdoc/html/static/js/tsconfig.json @@ -10,6 +10,6 @@ "skipLibCheck": true }, "typeAcquisition": { - "include": ["./rustdoc.d.ts"] + "include": ["./rustdoc.d.ts", "./stringdex.d.ts"] } } diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 45589a370698..e670c2f39e72 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -80,6 +80,7 @@ static_files! { normalize_css => "static/css/normalize.css", main_js => "static/js/main.js", search_js => "static/js/search.js", + stringdex_js => "static/js/stringdex.js", settings_js => "static/js/settings.js", src_script_js => "static/js/src-script.js", storage_js => "static/js/storage.js", diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index 7af99e7097c3..1f8ec9f30c53 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -7,7 +7,7 @@ <meta name="description" content="{{page.description}}"> {# #} <title>{{page.title}} {# #} {# #} {# #} @@ -29,6 +29,7 @@ data-rustdoc-version="{{rustdoc_version}}" {#+ #} data-channel="{{rust_channel}}" {#+ #} data-search-js="{{files.search_js}}" {#+ #} + data-stringdex-js="{{files.stringdex_js}}" {#+ #} data-settings-js="{{files.settings_js}}" {#+ #} > {# #} @@ -72,18 +73,9 @@ {{ layout.external_html.before_content|safe }} {% if page.css_class != "src" %} - + {# #} +

{{page.short_title}}

{# #} +
{% endif %}