From 6f815063a3715c8efdee6227fa441b81bdccf3b4 Mon Sep 17 00:00:00 2001 From: erfanio Date: Tue, 31 Mar 2026 22:28:45 +1100 Subject: [PATCH 01/55] Update neovim instructions for neovim 0.11+ --- .../rustc-dev-guide/src/building/suggested.md | 110 +++++++++--------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 959d0687b7fb..1f0630978f9a 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -144,12 +144,19 @@ If running `./x check` on save is inconvenient, in VS Code you can use a [Build ### Neovim -For Neovim users, there are a few options. -The easiest way is by using [neoconf.nvim](https://github.com/folke/neoconf.nvim/), -which allows for project-local configuration files with the native LSP. -The steps for how to use it are below. -Note that they require rust-analyzer to already be configured with Neovim. -Steps for this can be [found here](https://rust-analyzer.github.io/manual.html#nvim-lsp). +For Neovim users, there are a few options: + +1. The easiest way is using [neoconf.nvim](https://github.com/folke/neoconf.nvim/) but it uses the + deprecated `require('lspconfig')` API which displays a warning on neovim 0.11+. +2. Using `coc.nvim` is another option but it requires node.js to be installed. +3. Using a custom script to load rust-analyzer settings. + +#### neoconf.nvim + +[neoconf.nvim](https://github.com/folke/neoconf.nvim/) allows for project-local configuration +files with the native LSP. The steps for how to use it are below. Note that they require +rust-analyzer to already be configured with Neovim. Steps for this can be +[found here](https://rust-analyzer.github.io/book/other_editors.html#nvim-lsp). 1. First install the plugin. This can be done by following the steps in the README. @@ -157,64 +164,61 @@ Steps for this can be [found here](https://rust-analyzer.github.io/manual.html#n `neoconf` is able to read and update rust-analyzer settings automatically when the project is opened when this file is detected. +#### coc.nvim + If you're using `coc.nvim`, you can run `./x setup editor` and select `vim` to create a `.vim/coc-settings.json`. The settings can be edited with `:CocLocalConfig`. The recommended settings live at [`src/etc/rust_analyzer_settings.json`]. -Another way is without a plugin, and creating your own logic in your configuration. -The following code will work for any checkout of rust-lang/rust (newer than February 2025): +#### Custom LSP settings + +If you're running neovim 0.11+, you can configure rust-analyzer with just +[nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) and a custom script. + +1. Make sure rust-analyzer LSP is set up + +2. Create `$HOME/.config/nvim/after/plugged/rust_analyzer.lua` with the following content: ```lua -local function expand_config_variables(option) - local var_placeholders = { - ['${workspaceFolder}'] = function(_) - return vim.lsp.buf.list_workspace_folders()[1] - end, - } +-- Capture the default functions from nvim-lspconfig/lsp/rust_analyzer.lua before overriding it. +-- This file is in after/plugin to guarantee nvim-lspconfig has been initialised already. +local default_root_dir = vim.lsp.config['rust_analyzer'].root_dir +local default_before_init = vim.lsp.config['rust_analyzer'].before_init - if type(option) == "table" then - local mt = getmetatable(option) - local result = {} - for k, v in pairs(option) do - result[expand_config_variables(k)] = expand_config_variables(v) +vim.lsp.config('rust_analyzer', { + cmd = { 'rust-analyzer' }, + filetypes = { 'rust' }, + -- To support rust_lang/rust, we need to detect when we're in the rust repo and use the git root + -- instead of cargo project root. + root_dir = function(bufnr, on_dir) + local git_root = vim.fs.root(bufnr, { '.git' }) + if git_root then + if vim.uv.fs_stat(vim.fs.joinpath(git_root, "src/etc/rust_analyzer_zed.json")) then + on_dir(git_root) + return + end end - return setmetatable(result, mt) - end - if type(option) ~= "string" then - return option - end - local ret = option - for key, fn in pairs(var_placeholders) do - ret = ret:gsub(key, fn) - end - return ret -end -lspconfig.rust_analyzer.setup { - root_dir = function() - local default = lspconfig.rust_analyzer.config_def.default_config.root_dir() - -- the default root detection uses the cargo workspace root. - -- but for rust-lang/rust, the standard library is in its own workspace. - -- use the git root instead. - local compiler_config = vim.fs.joinpath(default, "../src/bootstrap/defaults/config.compiler.toml") - if vim.fs.basename(default) == "library" and vim.uv.fs_stat(compiler_config) then - return vim.fs.dirname(default) - end - return default + -- For anything that doesn't match rust-lang/rust, fallback to default root_dir + default_root_dir(bufnr, on_dir) end, - on_init = function(client) - local path = client.workspace_folders[1].name - local config = vim.fs.joinpath(path, "src/etc/rust_analyzer_zed.json") - if vim.uv.fs_stat(config) then - -- load rust-lang/rust settings - local file = io.open(config) - local json = vim.json.decode(file:read("*a")) - client.config.settings["rust-analyzer"] = expand_config_variables(json.lsp["rust-analyzer"].initialization_options) - client.notify("workspace/didChangeConfiguration", { settings = client.config.settings }) + before_init = function(init_params, config) + -- When inside rust_lang/rust, we need to use the special rust_analyzer settings. + local settings = vim.fs.joinpath(config.root_dir, "src/etc/rust_analyzer_zed.json") + if vim.uv.fs_stat(settings) then + local file = io.open(settings) + local content = file:read("*a") + file:close() + -- vim.json.decode doesn't support JSONC so we need strip out comments. + content = content:gsub("//[^\n]*", "") + local json = vim.json.decode(content) + config.settings["rust-analyzer"] = json.lsp["rust-analyzer"].initialization_options end - return true - end -} + default_before_init(init_params, config) + end, +}) + +vim.lsp.enable('rust_analyzer') ``` If you would like to use the build task that is described above, you may either From 69345f6feb37c5b490359f755582ce3b6532657d Mon Sep 17 00:00:00 2001 From: apiraino Date: Fri, 3 Apr 2026 11:22:17 +0200 Subject: [PATCH 02/55] Fix GPU target notification group links --- src/doc/rustc-dev-guide/src/notification-groups/gpu-target.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/notification-groups/gpu-target.md b/src/doc/rustc-dev-guide/src/notification-groups/gpu-target.md index b46233e98b41..101c79352524 100644 --- a/src/doc/rustc-dev-guide/src/notification-groups/gpu-target.md +++ b/src/doc/rustc-dev-guide/src/notification-groups/gpu-target.md @@ -6,13 +6,13 @@ This notification group deals with linker related issues and their integration within the compiler. -The group also has an associated Zulip stream ([`#t-compiler/linker`]) +The group also has an associated Zulip stream ([`#t-compiler/gpgpu-backend`]) where people can go to ask questions and discuss GPU-related topics and issues. if you're interested in participating, feel free to sign up for this group! To do so, open a PR against the [rust-lang/team] repository and add your GitHub user to [this file][gpu-target-team]. -[`#t-compiler/linker`]: https://rust-lang.zulipchat.com/#narrow/channel/585172-t-compiler.2Flinker +[`#t-compiler/gpgpu-backend`]: https://rust-lang.zulipchat.com/#narrow/channel/422870-t-compiler.2Fgpgpu-backend [rust-lang/team]: https://github.com/rust-lang/team [gpu-target-team]: https://github.com/rust-lang/team/blob/main/teams/gpu-target.toml From 314c14a9a6e7c9cbc3ff4c0153fa4086daa1acf1 Mon Sep 17 00:00:00 2001 From: erfanio Date: Mon, 6 Apr 2026 11:45:05 +1000 Subject: [PATCH 03/55] Address code review comments --- .../rustc-dev-guide/src/building/suggested.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 1f0630978f9a..f56f05adc64c 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -146,17 +146,17 @@ If running `./x check` on save is inconvenient, in VS Code you can use a [Build For Neovim users, there are a few options: -1. The easiest way is using [neoconf.nvim](https://github.com/folke/neoconf.nvim/) but it uses the +1. The easiest way is using [neoconf.nvim][neoconf.nvim] but it uses the deprecated `require('lspconfig')` API which displays a warning on neovim 0.11+. 2. Using `coc.nvim` is another option but it requires node.js to be installed. 3. Using a custom script to load rust-analyzer settings. #### neoconf.nvim -[neoconf.nvim](https://github.com/folke/neoconf.nvim/) allows for project-local configuration +[neoconf.nvim][neoconf.nvim] allows for project-local configuration files with the native LSP. The steps for how to use it are below. Note that they require rust-analyzer to already be configured with Neovim. Steps for this can be -[found here](https://rust-analyzer.github.io/book/other_editors.html#nvim-lsp). +[found here][r-a nvim lsp]. 1. First install the plugin. This can be done by following the steps in the README. @@ -176,8 +176,7 @@ The recommended settings live at [`src/etc/rust_analyzer_settings.json`]. If you're running neovim 0.11+, you can configure rust-analyzer with just [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) and a custom script. -1. Make sure rust-analyzer LSP is set up - +1. Make sure [rust-analyzer LSP][r-a nvim lsp] is set up 2. Create `$HOME/.config/nvim/after/plugged/rust_analyzer.lua` with the following content: ```lua @@ -189,7 +188,7 @@ local default_before_init = vim.lsp.config['rust_analyzer'].before_init vim.lsp.config('rust_analyzer', { cmd = { 'rust-analyzer' }, filetypes = { 'rust' }, - -- To support rust_lang/rust, we need to detect when we're in the rust repo and use the git root + -- To support rust-lang/rust, we need to detect when we're in the rust repo and use the git root -- instead of cargo project root. root_dir = function(bufnr, on_dir) local git_root = vim.fs.root(bufnr, { '.git' }) @@ -203,7 +202,7 @@ vim.lsp.config('rust_analyzer', { default_root_dir(bufnr, on_dir) end, before_init = function(init_params, config) - -- When inside rust_lang/rust, we need to use the special rust_analyzer settings. + -- When inside rust-lang/rust, we need to use the special rust_analyzer settings. local settings = vim.fs.joinpath(config.root_dir, "src/etc/rust_analyzer_zed.json") if vim.uv.fs_stat(settings) then local file = io.open(settings) @@ -227,6 +226,9 @@ make your own command in your config, or you can install a plugin such as files](https://github.com/stevearc/overseer.nvim/blob/master/doc/guides.md#vs-code-tasks), and follow the same instructions as above. +[neoconf.nvim]: https://github.com/folke/neoconf.nvim/ +[r-a nvim lsp]: https://rust-analyzer.github.io/book/other_editors.html#nvim-lsp + ### Emacs Emacs provides support for rust-analyzer with project-local configuration From 002634043fdd7a7e7370a817edc438374f0b8027 Mon Sep 17 00:00:00 2001 From: erfanio Date: Mon, 6 Apr 2026 11:55:39 +1000 Subject: [PATCH 04/55] Use skip_comments to strip comments --- src/doc/rustc-dev-guide/src/building/suggested.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index f56f05adc64c..54de2a06f293 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -206,11 +206,9 @@ vim.lsp.config('rust_analyzer', { local settings = vim.fs.joinpath(config.root_dir, "src/etc/rust_analyzer_zed.json") if vim.uv.fs_stat(settings) then local file = io.open(settings) - local content = file:read("*a") + -- nvim 0.12+ supports comments otherwise you'll need content:gsub("//[^\n]*", ""). + local json = vim.json.decode(file:read("*a"), { skip_comments = true }) file:close() - -- vim.json.decode doesn't support JSONC so we need strip out comments. - content = content:gsub("//[^\n]*", "") - local json = vim.json.decode(content) config.settings["rust-analyzer"] = json.lsp["rust-analyzer"].initialization_options end default_before_init(init_params, config) From d4773683a6484aeecc0aa20a7bad6ccd5a2e3dee Mon Sep 17 00:00:00 2001 From: erfanio Date: Mon, 6 Apr 2026 12:11:50 +1000 Subject: [PATCH 05/55] Use double quotes consistently --- src/doc/rustc-dev-guide/src/building/suggested.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 54de2a06f293..87f8dc4ef732 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -182,16 +182,16 @@ If you're running neovim 0.11+, you can configure rust-analyzer with just ```lua -- Capture the default functions from nvim-lspconfig/lsp/rust_analyzer.lua before overriding it. -- This file is in after/plugin to guarantee nvim-lspconfig has been initialised already. -local default_root_dir = vim.lsp.config['rust_analyzer'].root_dir -local default_before_init = vim.lsp.config['rust_analyzer'].before_init +local default_root_dir = vim.lsp.config["rust_analyzer"].root_dir +local default_before_init = vim.lsp.config["rust_analyzer"].before_init -vim.lsp.config('rust_analyzer', { - cmd = { 'rust-analyzer' }, - filetypes = { 'rust' }, +vim.lsp.config("rust_analyzer", { + cmd = { "rust-analyzer" }, + filetypes = { "rust" }, -- To support rust-lang/rust, we need to detect when we're in the rust repo and use the git root -- instead of cargo project root. root_dir = function(bufnr, on_dir) - local git_root = vim.fs.root(bufnr, { '.git' }) + local git_root = vim.fs.root(bufnr, { ".git" }) if git_root then if vim.uv.fs_stat(vim.fs.joinpath(git_root, "src/etc/rust_analyzer_zed.json")) then on_dir(git_root) @@ -215,7 +215,7 @@ vim.lsp.config('rust_analyzer', { end, }) -vim.lsp.enable('rust_analyzer') +vim.lsp.enable("rust_analyzer") ``` If you would like to use the build task that is described above, you may either From 8d96fa05570e19c8ddef001f5f87d58a387fe171 Mon Sep 17 00:00:00 2001 From: erfanio Date: Mon, 6 Apr 2026 12:38:23 +1000 Subject: [PATCH 06/55] Merge rust-analyzer settings instead of overwriting --- src/doc/rustc-dev-guide/src/building/suggested.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 87f8dc4ef732..d9921cbb133e 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -202,14 +202,18 @@ vim.lsp.config("rust_analyzer", { default_root_dir(bufnr, on_dir) end, before_init = function(init_params, config) - -- When inside rust-lang/rust, we need to use the special rust_analyzer settings. + -- When inside rust-lang/rust, we need to use the special rust-analyzer settings. local settings = vim.fs.joinpath(config.root_dir, "src/etc/rust_analyzer_zed.json") if vim.uv.fs_stat(settings) then local file = io.open(settings) -- nvim 0.12+ supports comments otherwise you'll need content:gsub("//[^\n]*", ""). local json = vim.json.decode(file:read("*a"), { skip_comments = true }) file:close() - config.settings["rust-analyzer"] = json.lsp["rust-analyzer"].initialization_options + config.settings["rust-analyzer"] = vim.tbl_deep_extend( + "force", -- Overwrite with the special settings when there is a conflict. + config.settings["rust-analyzer"] or {}, + json.lsp["rust-analyzer"].initialization_options + ) end default_before_init(init_params, config) end, From 24559317919bc0ed1642b3163f73a0b37b369296 Mon Sep 17 00:00:00 2001 From: Redddy Date: Fri, 10 Apr 2026 16:57:27 +0900 Subject: [PATCH 07/55] Fix link to hir::Crate in lowering.md --- src/doc/rustc-dev-guide/src/hir/lowering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/hir/lowering.md b/src/doc/rustc-dev-guide/src/hir/lowering.md index b7d0293bede9..7f9ac325a06e 100644 --- a/src/doc/rustc-dev-guide/src/hir/lowering.md +++ b/src/doc/rustc-dev-guide/src/hir/lowering.md @@ -54,7 +54,7 @@ sanity checks in [`compiler/rustc_passes/src/hir_id_validator.rs`][hir_id_valida [`rustc_ast_lowering`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/index.html [`lower_to_hir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/fn.lower_to_hir.html [`TyCtxt`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html -[`hir::Crate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/hir/struct.Crate.html +[`hir::Crate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/hir/struct.Crate.html [`ItemLowerer::lower_node`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/item/struct.ItemLowerer.html [`LoweringContext`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/struct.LoweringContext.html [`with_hir_id_owner`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/struct.LoweringContext.html#method.with_hir_id_owner From 20c9f54404453c96e31d83f6587c999f21cbc6dc Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 10 Apr 2026 16:25:55 -0700 Subject: [PATCH 08/55] Document precision considerations of `Duration`-float methods A `Duration` is essentially a 94-bit value (64-bit sec and ~30-bit ns), so there's some inherent loss when converting to floating-point for `mul_f64` and `div_f64`. We could go to greater lengths to compute these with more accuracy, like rust-lang/rust#150933 or rust-lang/rust#154107, but it's not clear that it's worth the effort. The least we can do is document that some rounding is to be expected, which this commit does with simple examples that only multiply or divide by `1.0`. This also changes the `f32` methods to just forward to `f64`, so we keep more of that duration precision, as the range is otherwise much more limited there. --- library/core/src/time.rs | 88 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index a5b654033ba1..9073131b35b6 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1006,6 +1006,7 @@ pub fn from_secs_f32(secs: f32) -> Duration { /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples + /// /// ``` /// use std::time::Duration; /// @@ -1013,6 +1014,37 @@ pub fn from_secs_f32(secs: f32) -> Duration { /// assert_eq!(dur.mul_f64(3.14), Duration::new(8, 478_000_000)); /// assert_eq!(dur.mul_f64(3.14e5), Duration::new(847_800, 0)); /// ``` + /// + /// Note that `f64` does not have enough bits ([`f64::MANTISSA_DIGITS`]) to represent the full + /// range of possible `Duration` with nanosecond precision, so rounding may occur even for + /// trivial operations like multiplying by 1. + /// + /// ``` + /// # #![feature(float_exact_integer_constants)] + /// use std::time::Duration; + /// + /// // This is about 14.9 weeks, remaining precise to the nanosecond: + /// let weeks = Duration::from_nanos(f64::MAX_EXACT_INTEGER as u64); + /// assert_eq!(weeks, weeks.mul_f64(1.0)); + /// + /// // A larger value incurs rounding in the floating-point operation: + /// let weeks = Duration::from_nanos(u64::MAX); + /// assert_ne!(weeks, weeks.mul_f64(1.0)); + /// + /// // This is over 285 million years, remaining precise to the second: + /// let years = Duration::from_secs(f64::MAX_EXACT_INTEGER as u64); + /// assert_eq!(years, years.mul_f64(1.0)); + /// + /// // And again larger values incur rounding: + /// let years = Duration::from_secs(u64::MAX / 2); + /// assert_ne!(years, years.mul_f64(1.0)); + /// ``` + /// + /// ```should_panic + /// # use std::time::Duration; + /// // In the extreme, rounding can even overflow `Duration`, which panics. + /// let _ = Duration::from_secs(u64::MAX).mul_f64(1.0); + /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] @@ -1023,6 +1055,10 @@ pub fn mul_f64(self, rhs: f64) -> Duration { /// Multiplies `Duration` by `f32`. /// + /// Since the significand of `f32` is quite limited compared to the range of `Duration` + /// -- only about 16.8ms of exact nanosecond precision -- this method currently forwards + /// to [`mul_f64`][Self::mul_f64] for greater accuracy. + /// /// # Panics /// This method will panic if result is negative, overflows `Duration` or not finite. /// @@ -1031,7 +1067,10 @@ pub fn mul_f64(self, rhs: f64) -> Duration { /// use std::time::Duration; /// /// let dur = Duration::new(2, 700_000_000); - /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_641)); + /// // Note that this `3.14_f32` argument already has more floating-point + /// // representation error than a direct `3.14_f64` would, so the result + /// // is slightly different from the ideal 8.478s. + /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_283)); /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847_800, 0)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] @@ -1039,7 +1078,7 @@ pub fn mul_f64(self, rhs: f64) -> Duration { without modifying the original"] #[inline] pub fn mul_f32(self, rhs: f32) -> Duration { - Duration::from_secs_f32(rhs * self.as_secs_f32()) + self.mul_f64(rhs.into()) } /// Divides `Duration` by `f64`. @@ -1048,6 +1087,7 @@ pub fn mul_f32(self, rhs: f32) -> Duration { /// This method will panic if result is negative, overflows `Duration` or not finite. /// /// # Examples + /// /// ``` /// use std::time::Duration; /// @@ -1055,6 +1095,37 @@ pub fn mul_f32(self, rhs: f32) -> Duration { /// assert_eq!(dur.div_f64(3.14), Duration::new(0, 859_872_611)); /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_599)); /// ``` + /// + /// Note that `f64` does not have enough bits ([`f64::MANTISSA_DIGITS`]) to represent the full + /// range of possible `Duration` with nanosecond precision, so rounding may occur even for + /// trivial operations like dividing by 1. + /// + /// ``` + /// # #![feature(float_exact_integer_constants)] + /// use std::time::Duration; + /// + /// // This is about 14.9 weeks, remaining precise to the nanosecond: + /// let weeks = Duration::from_nanos(f64::MAX_EXACT_INTEGER as u64); + /// assert_eq!(weeks, weeks.div_f64(1.0)); + /// + /// // A larger value incurs rounding in the floating-point operation: + /// let weeks = Duration::from_nanos(u64::MAX); + /// assert_ne!(weeks, weeks.div_f64(1.0)); + /// + /// // This is over 285 million years, remaining precise to the second: + /// let years = Duration::from_secs(f64::MAX_EXACT_INTEGER as u64); + /// assert_eq!(years, years.div_f64(1.0)); + /// + /// // And again larger values incur rounding: + /// let years = Duration::from_secs(u64::MAX / 2); + /// assert_ne!(years, years.div_f64(1.0)); + /// ``` + /// + /// ```should_panic + /// # use std::time::Duration; + /// // In the extreme, rounding can even overflow `Duration`, which panics. + /// let _ = Duration::from_secs(u64::MAX).div_f64(1.0); + /// ``` #[stable(feature = "duration_float", since = "1.38.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] @@ -1065,6 +1136,10 @@ pub fn div_f64(self, rhs: f64) -> Duration { /// Divides `Duration` by `f32`. /// + /// Since the significand of `f32` is quite limited compared to the range of `Duration` + /// -- only about 16.8ms of exact nanosecond precision -- this method currently forwards + /// to [`div_f64`][Self::div_f64] for greater accuracy. + /// /// # Panics /// This method will panic if result is negative, overflows `Duration` or not finite. /// @@ -1073,9 +1148,10 @@ pub fn div_f64(self, rhs: f64) -> Duration { /// use std::time::Duration; /// /// let dur = Duration::new(2, 700_000_000); - /// // note that due to rounding errors result is slightly - /// // different from 0.859_872_611 - /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_580)); + /// // Note that this `3.14_f32` argument already has more floating-point + /// // representation error than a direct `3.14_f64` would, so the result + /// // is slightly different from the ideally rounded 0.859_872_611. + /// assert_eq!(dur.div_f32(3.14), Duration::new(0, 859_872_583)); /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599)); /// ``` #[stable(feature = "duration_float", since = "1.38.0")] @@ -1083,7 +1159,7 @@ pub fn div_f64(self, rhs: f64) -> Duration { without modifying the original"] #[inline] pub fn div_f32(self, rhs: f32) -> Duration { - Duration::from_secs_f32(self.as_secs_f32() / rhs) + self.div_f64(rhs.into()) } /// Divides `Duration` by `Duration` and returns `f64`. From d72b0e73a49b93ba9fabaa67835046a2d6321046 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 11 Apr 2026 19:06:05 -0700 Subject: [PATCH 09/55] implementing-new-features.md: update parameters to `declare_features!` The edition parameter was removed --- src/doc/rustc-dev-guide/src/implementing-new-features.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 bb5c0bbba6e3..b288cb4d9ff9 100644 --- a/src/doc/rustc-dev-guide/src/implementing-new-features.md +++ b/src/doc/rustc-dev-guide/src/implementing-new-features.md @@ -162,7 +162,7 @@ The below steps needs to be followed in order to implement a new unstable featur ```rust ignore /// Allows defining identifiers beyond ASCII. - (unstable, non_ascii_idents, "CURRENT_RUSTC_VERSION", Some(55467), None), + (unstable, non_ascii_idents, "CURRENT_RUSTC_VERSION", Some(55467)), ``` Features can be marked as incomplete, @@ -173,7 +173,7 @@ The below steps needs to be followed in order to implement a new unstable featur ```rust ignore /// Allows deref patterns. - (incomplete, deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121), None), + (incomplete, deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121)), ``` Feature flags related to a lang experiment must be marked as `incomplete` From 269feca4ca103c99cd5bae65a896287ecb3156d4 Mon Sep 17 00:00:00 2001 From: Redddy Date: Mon, 13 Apr 2026 11:39:17 +0900 Subject: [PATCH 10/55] Document quoting for `@bors squash msg=` --- src/doc/rustc-dev-guide/src/git.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/git.md b/src/doc/rustc-dev-guide/src/git.md index faa2d8f2a3a2..9e4ad2d81b9f 100644 --- a/src/doc/rustc-dev-guide/src/git.md +++ b/src/doc/rustc-dev-guide/src/git.md @@ -386,7 +386,8 @@ the history becomes easier to work with. The easiest way to squash your commits in a PR on the `rust-lang/rust` repository is to use the `@bors squash` command in a comment on the PR. By default, [bors] combines all commit messages of the PR into the squashed commit message. To customize the commit message, use `@bors squash msg=`. - +If you want to use multiple words in the commit message, wrap it in double quotes. +For example, `@bors squash msg="Improve diagnostics for missing lifetime parameter"`. If you want to squash commits using local git operations, read on below. From 9ddab4d30b40297af74b2bb899fb3022b0cde110 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 13 Apr 2026 05:11:29 +0000 Subject: [PATCH 11/55] Prepare for merging from rust-lang/rust This updates the rust-version file to 14196dbfa3eb7c30195251eac092b1b86c8a2d84. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index ac3099d84593..c8b808149b45 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -30d0309fa821f7a0984a9629e0d227ca3c0d2eda +14196dbfa3eb7c30195251eac092b1b86c8a2d84 From c315ef2a1d4d1ade9cc1a694ae9c758cd389663a Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 13 Apr 2026 07:29:58 +0200 Subject: [PATCH 12/55] show instead of explain This should be clear with less words r? @reddevilmidzy --- src/doc/rustc-dev-guide/src/git.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/git.md b/src/doc/rustc-dev-guide/src/git.md index 9e4ad2d81b9f..5974e0e92cad 100644 --- a/src/doc/rustc-dev-guide/src/git.md +++ b/src/doc/rustc-dev-guide/src/git.md @@ -385,8 +385,7 @@ the history becomes easier to work with. The easiest way to squash your commits in a PR on the `rust-lang/rust` repository is to use the `@bors squash` command in a comment on the PR. By default, [bors] combines all commit messages of the PR into the squashed commit message. -To customize the commit message, use `@bors squash msg=`. -If you want to use multiple words in the commit message, wrap it in double quotes. +To customize the commit message, use `@bors squash msg=""`. For example, `@bors squash msg="Improve diagnostics for missing lifetime parameter"`. If you want to squash commits using local git operations, read on below. From 2a152e14541d6ca322799fb854f6d12b42499b70 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 13 Apr 2026 22:12:50 +0000 Subject: [PATCH 13/55] Prepare for merging from rust-lang/rust This updates the rust-version file to 17584a181979f04f2aaad867332c22db1caa511a. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index c8b808149b45..290bd67d2cc3 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -14196dbfa3eb7c30195251eac092b1b86c8a2d84 +17584a181979f04f2aaad867332c22db1caa511a From 72b21f483522209b5f4fa87f2aae106515608325 Mon Sep 17 00:00:00 2001 From: Redddy Date: Sat, 18 Apr 2026 00:04:45 +0900 Subject: [PATCH 14/55] Update link for Rust reference documentation --- src/doc/rustc-dev-guide/src/about-this-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/about-this-guide.md b/src/doc/rustc-dev-guide/src/about-this-guide.md index 4dc453a2042d..56fbc1f6b7a8 100644 --- a/src/doc/rustc-dev-guide/src/about-this-guide.md +++ b/src/doc/rustc-dev-guide/src/about-this-guide.md @@ -98,7 +98,7 @@ You might also find the following sites useful: [gsearchdocs]: https://www.google.com/search?q=site:doc.rust-lang.org+your+query+here [stddocs]: https://doc.rust-lang.org/std [rif]: http://internals.rust-lang.org -[rr]: https://doc.rust-lang.org/book/ +[rr]: https://doc.rust-lang.org/reference/ [rustforge]: https://forge.rust-lang.org/ [tlgba]: https://tomlee.co/2014/04/a-more-detailed-tour-of-the-rust-compiler/ [ro]: https://www.rustaceans.org/ From 873d2f6534c0fa0a570ed116ac24ea4e026e89f9 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 17 Apr 2026 16:04:49 -0700 Subject: [PATCH 15/55] effects.md: update const trait syntax `~const` was replaced with `[const]`, making uses of the former confusing --- src/doc/rustc-dev-guide/src/effects.md | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/effects.md b/src/doc/rustc-dev-guide/src/effects.md index 26530a30d066..edbb1688e3da 100644 --- a/src/doc/rustc-dev-guide/src/effects.md +++ b/src/doc/rustc-dev-guide/src/effects.md @@ -2,16 +2,16 @@ ## The `HostEffect` predicate -[`HostEffectPredicate`]s are a kind of predicate from `~const Tr` or `const Tr` bounds. +[`HostEffectPredicate`]s are a kind of predicate from `[const] Tr` or `const Tr` bounds. It has a trait reference, and a `constness` which could be `Maybe` or `Const` depending on the bound. -Because `~const Tr`, or rather `Maybe` bounds +Because `[const] Tr`, or rather `Maybe` bounds apply differently based on whichever contexts they are in, they have different behavior than normal bounds. Where normal trait bounds on a function such as `T: Tr` are collected within the [`predicates_of`] query to be proven when a function is called and to be assumed within the function, bounds such as -`T: ~const Tr` will behave as a normal trait bound and add `T: Tr` to the result +`T: [const] Tr` will behave as a normal trait bound and add `T: Tr` to the result from `predicates_of`, but also adds a `HostEffectPredicate` to the [`const_conditions`] query. On the other hand, `T: const Tr` bounds do not change meaning across contexts, @@ -37,7 +37,7 @@ In a similar vein, an item *in const contexts*. If we adjust the example above to use `const` trait bounds: ```rust -const fn foo() where T: ~const Default {} +const fn foo() where T: [const] Default {} ``` Then `foo` would get a `HostEffect(T: Default, maybe)` in the `const_conditions` @@ -55,7 +55,7 @@ Note that we don't check if the function is only referred to but not called, as the following code needs to compile: ```rust -const fn hi() -> T { +const fn hi() -> T { T::default() } const X: fn() -> u32 = hi::; @@ -69,7 +69,7 @@ Here's an example: ```rust const trait Bar {} -const trait Foo: ~const Bar {} +const trait Foo: [const] Bar {} // `const_conditions` contains `HostEffect(Self: Bar, maybe)` impl const Bar for () {} @@ -86,13 +86,13 @@ We do the same for `const_conditions`: ```rust const trait Foo { - fn hi(); + fn hi(); } -impl Foo for Vec { - fn hi(); - // ^ we can't prove `T: ~const PartialEq` given `T: ~const Clone` and - // `T: ~const Default`, therefore we know that the method on the impl +impl Foo for Vec { + fn hi(); + // ^ we can't prove `T: [const] PartialEq` given `T: [const] Clone` and + // `T: [const] Default`, therefore we know that the method on the impl // is stricter than the method on the trait. } ``` @@ -117,11 +117,11 @@ Bounds on associated types, opaque types, and supertraits such as the following have their bounds represented differently: ```rust -trait Foo: ~const PartialEq { - type X: ~const PartialEq; +trait Foo: [const] PartialEq { + type X: [const] PartialEq; } -fn foo() -> impl ~const PartialEq { +fn foo() -> impl [const] PartialEq { // ^ unimplemented syntax } ``` From de48fc6a5620f804a5c7facf3b0274a1d807686e Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 15 Apr 2026 20:27:12 -0700 Subject: [PATCH 16/55] rustdoc: fix a few spots where emit isn't respected --- src/librustdoc/html/render/write_shared.rs | 52 +++++++++--------- src/librustdoc/lib.rs | 7 ++- .../{theme.css => custom_theme.css} | 0 tests/run-make/rustdoc-dep-info/rmake.rs | 53 +++++++++++++++---- 4 files changed, 77 insertions(+), 35 deletions(-) rename tests/run-make/rustdoc-dep-info/{theme.css => custom_theme.css} (100%) diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8cb43e002870..8de899ea0eef 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -153,7 +153,7 @@ pub(crate) fn write_not_crate_specific( include_sources: bool, ) -> Result<(), Error> { write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?; - write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?; + write_resources(dst, opt, style_files, css_file_extension, resource_suffix)?; Ok(()) } @@ -183,43 +183,45 @@ fn write_rendered_cross_crate_info( /// Writes the static files, the style files, and the css extensions. /// Have to be careful about these, because they write to the root out dir. -fn write_static_files( +fn write_resources( dst: &Path, opt: &RenderOptions, style_files: &[StylePath], css_file_extension: Option<&Path>, resource_suffix: &str, ) -> Result<(), Error> { - let static_dir = dst.join("static.files"); - try_err!(fs::create_dir_all(&static_dir), &static_dir); + if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlNonStaticFiles) { + // Handle added third-party themes + for entry in style_files { + let theme = entry.basename()?; + let extension = + try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); - // Handle added third-party themes - for entry in style_files { - let theme = entry.basename()?; - let extension = - try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path); + // Skip the official themes. They are written below as part of STATIC_FILES_LIST. + if matches!(theme.as_str(), "light" | "dark" | "ayu") { + continue; + } - // Skip the official themes. They are written below as part of STATIC_FILES_LIST. - if matches!(theme.as_str(), "light" | "dark" | "ayu") { - continue; + let bytes = try_err!(fs::read(&entry.path), &entry.path); + let filename = format!("{theme}{resource_suffix}.{extension}"); + let dst_filename = dst.join(filename); + try_err!(fs::write(&dst_filename, bytes), &dst_filename); } - let bytes = try_err!(fs::read(&entry.path), &entry.path); - let filename = format!("{theme}{resource_suffix}.{extension}"); - let dst_filename = dst.join(filename); - try_err!(fs::write(&dst_filename, bytes), &dst_filename); - } - - // When the user adds their own CSS files with --extend-css, we write that as an - // invocation-specific file (that is, with a resource suffix). - if let Some(css) = css_file_extension { - let buffer = try_err!(fs::read_to_string(css), css); - let path = static_files::suffix_path("theme.css", resource_suffix); - let dst_path = dst.join(path); - try_err!(fs::write(&dst_path, buffer), &dst_path); + // When the user adds their own CSS files with --extend-css, we write that as an + // invocation-specific file (that is, with a resource suffix). + if let Some(css) = css_file_extension { + let buffer = try_err!(fs::read_to_string(css), css); + let path = static_files::suffix_path("theme.css", resource_suffix); + let dst_path = dst.join(path); + try_err!(fs::write(&dst_path, buffer), &dst_path); + } } if opt.emit.is_empty() || opt.emit.contains(&EmitType::HtmlStaticFiles) { + let static_dir = dst.join("static.files"); + try_err!(fs::create_dir_all(&static_dir), &static_dir); + static_files::for_each(|f: &static_files::StaticFile| { let filename = static_dir.join(f.output_filename()); let contents: &[u8] = diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 750ce27ea796..87de4244b5c8 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -81,6 +81,7 @@ use tracing::info; use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION; +use crate::config::EmitType; use crate::error::Error; use crate::formats::cache::Cache; @@ -868,7 +869,11 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { }; rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { let has_dep_info = render_options.dep_info().is_some(); - markdown::render_and_write(file, render_options, edition)?; + if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) + || render_options.emit.is_empty() + { + markdown::render_and_write(file, render_options, edition)?; + } if has_dep_info { // Register the loaded external files in the source map so they show up in depinfo. // We can't load them via the source map because it gets created after we process the options. diff --git a/tests/run-make/rustdoc-dep-info/theme.css b/tests/run-make/rustdoc-dep-info/custom_theme.css similarity index 100% rename from tests/run-make/rustdoc-dep-info/theme.css rename to tests/run-make/rustdoc-dep-info/custom_theme.css diff --git a/tests/run-make/rustdoc-dep-info/rmake.rs b/tests/run-make/rustdoc-dep-info/rmake.rs index 233225fde869..fbeac251f5f9 100644 --- a/tests/run-make/rustdoc-dep-info/rmake.rs +++ b/tests/run-make/rustdoc-dep-info/rmake.rs @@ -9,8 +9,6 @@ fn main() { rfs::create_dir("doc"); - // We're only emitting dep info, so we shouldn't be running static analysis to - // figure out that this program is erroneous. // Ensure that all kinds of input reading flags end up in dep-info. rustdoc() .input("lib.rs") @@ -18,7 +16,7 @@ fn main() { .arg("--html-before-content=before.html") .arg("--markdown-after-content=after.md") .arg("--extend-css=extend.css") - .arg("--theme=theme.css") + .arg("--theme=custom_theme.css") .arg("--index-page=index-page.md") .emit("dep-info") .run(); @@ -31,8 +29,31 @@ fn main() { assert_contains(&content, "after.md:"); assert_contains(&content, "before.html:"); assert_contains(&content, "extend.css:"); - assert_contains(&content, "theme.css:"); + assert_contains(&content, "custom_theme.css:"); assert_contains(&content, "index-page.md:"); + // Only emit dep-info. Don't emit the actual page. + assert!(!path("doc/foo/index.html").exists()); + assert!(!path("doc/custom_theme.css").exists()); + // weird that --extend-css generates a file named theme.css + assert!(!path("doc/theme.css").exists()); + + // Now try emitting dep-info and html files at the same time. + rustdoc() + .input("lib.rs") + .arg("-Zunstable-options") + .arg("--html-before-content=before.html") + .arg("--markdown-after-content=after.md") + .arg("--extend-css=extend.css") + .arg("--theme=custom_theme.css") + .arg("--index-page=index-page.md") + .emit("dep-info,html-non-static-files,html-static-files") + .run(); + assert!(path("doc/foo/index.html").exists()); + // These files are copied into the doc output folder, + // which is why they show up in dep-info. + assert!(path("doc/custom_theme.css").exists()); + // weird that --extend-css generates a file named theme.css + assert!(path("doc/theme.css").exists()); // Now we check that we can provide a file name to the `dep-info` argument. rustdoc().input("lib.rs").arg("-Zunstable-options").emit("dep-info=bla.d").run(); @@ -72,7 +93,9 @@ fn main() { assert_not_contains(&content, "after.md:"); assert_not_contains(&content, "before.html:"); assert_not_contains(&content, "extend.css:"); - assert_not_contains(&content, "theme.css:"); + assert_not_contains(&content, "custom_theme.css:"); + // Only emit dep-info, not the actual html. + assert!(!path("doc/example.html").exists()); // combine --emit=dep-info=filename with plain markdown input rustdoc() @@ -81,10 +104,12 @@ fn main() { .arg("--html-before-content=before.html") .arg("--markdown-after-content=after.md") .arg("--extend-css=extend.css") - .arg("--theme=theme.css") + .arg("--theme=custom_theme.css") + .arg("--markdown-css=markdown.css") .arg("--index-page=index-page.md") - .emit("dep-info=example.d") + .emit("dep-info=example.d,html-non-static-files,html-static-files") .run(); + assert!(path("doc/example.html").exists()); let content = rfs::read_to_string("example.d"); assert_contains(&content, "example.md:"); assert_not_contains(&content, "lib.rs:"); @@ -93,7 +118,17 @@ fn main() { assert_not_contains(&content, "doc.md:"); assert_contains(&content, "after.md:"); assert_contains(&content, "before.html:"); - assert_contains(&content, "extend.css:"); - assert_contains(&content, "theme.css:"); assert_contains(&content, "index-page.md:"); + // This is a hotlink, not a file that gets copied, + // so it shouldn't add to the dep-info, it shouldn't be copied, + // and it shouldn't be resolved relative to the root path. + // + // It's weird that this is different from the other two css + // files, but it's stable, so I can't change it. + assert!(!path("doc/markdown.css").exists()); + assert_not_contains(&content, "markdown.css:"); + // These files aren't actually used, and the fact that they show up + // is arguably a bug, but test it anyway. + assert_contains(&content, "extend.css:"); + assert_contains(&content, "custom_theme.css:"); } From c5e1f600f5e243c79293172db5971c91315db615 Mon Sep 17 00:00:00 2001 From: Amanjeev Sethi Date: Sat, 18 Apr 2026 15:01:56 -0400 Subject: [PATCH 17/55] Clarifications and instructions - nightly - Only latest nightly crates' docs are published - How to get an old nightly docs --- .../src/rustc-driver/external-rustc-drivers.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 2d409ad201b1..4f53d5e8a47d 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -71,6 +71,21 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. +### Getting Specific Nightly Documentation for `rustc_private` + +The nightly-rustc internal crates' documentation is only available for the latest nightly. If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. The only way to do this is to generate the documentation locally. For example, to get documentation for `nightly-2025-11-08`: + +Get the Git commit hash for that nightly: + +```sh +rustup toolchain install nightly-2025-11-08 +rustup default nightly-2025-11-08 +rustc -Vv +``` + +The output will include a `commit-hash` line identifying the exact source revision. Check out `rust-lang/rust` at that commit, then follow the steps in [compiler documentation](../building/compiler-documenting.md). + + ### Additional Resources - [GitHub Issue #137421] explains that `rustc_private` linker failures often occur because `llvm-tools` is not installed From 9faee77d93349e82e34db8587aa9b88ae3ef3f60 Mon Sep 17 00:00:00 2001 From: Amanjeev Sethi Date: Sat, 18 Apr 2026 15:02:22 -0400 Subject: [PATCH 18/55] Typo in crate name --- .../rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 4f53d5e8a47d..9db24a251f95 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -6,7 +6,7 @@ The `rustc_private` feature allows external crates to use compiler internals. -### Using `rustc-private` with Official Toolchains +### Using `rustc_private` with Official Toolchains When using the `rustc_private` feature with official Rust toolchains distributed via rustup, you need to install two additional components: From 369969a6c72f12cd29fa34cccf8ea8769397f9df Mon Sep 17 00:00:00 2001 From: Amanjeev Sethi Date: Sat, 18 Apr 2026 18:25:35 -0400 Subject: [PATCH 19/55] PR review: use better command --- .../rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 9db24a251f95..7465dd2e9734 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -79,8 +79,7 @@ Get the Git commit hash for that nightly: ```sh rustup toolchain install nightly-2025-11-08 -rustup default nightly-2025-11-08 -rustc -Vv +rustc +nightly-2025-11-08 --version --verbose ``` The output will include a `commit-hash` line identifying the exact source revision. Check out `rust-lang/rust` at that commit, then follow the steps in [compiler documentation](../building/compiler-documenting.md). From f6f98eb65f05f946061c058b148de94ea5166afa Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 20 Apr 2026 05:14:21 +0000 Subject: [PATCH 20/55] Prepare for merging from rust-lang/rust This updates the rust-version file to e22c616e4e87914135c1db261a03e0437255335e. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 290bd67d2cc3..e9fc6c4cd023 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -17584a181979f04f2aaad867332c22db1caa511a +e22c616e4e87914135c1db261a03e0437255335e From ae92374ad5c68ef0a2e72b136faf6bef93a61975 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 20 Apr 2026 13:01:17 +0000 Subject: [PATCH 21/55] Prepare for merging from rust-lang/rust This updates the rust-version file to d12e1e12ae6fb828df1b663a07c9191bc48c6d6d. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index e9fc6c4cd023..a428bf49dfdb 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -e22c616e4e87914135c1db261a03e0437255335e +d12e1e12ae6fb828df1b663a07c9191bc48c6d6d From 0add85cbe63cada1cb9f65f1a88094cf5810a2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Tue, 24 Mar 2026 01:16:52 +0100 Subject: [PATCH 22/55] Remove `nodes_in_current_session` field and related assertions --- compiler/rustc_middle/src/dep_graph/graph.rs | 82 ++------------------ 1 file changed, 6 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index be29e053a17c..87d5ae5aaa78 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -5,12 +5,11 @@ use std::sync::atomic::{AtomicU32, Ordering}; use rustc_data_structures::fingerprint::{Fingerprint, PackedFingerprint}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::outline; +use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::profiling::QueryInvocationId; use rustc_data_structures::sharded::{self, ShardedHashMap}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_data_structures::sync::{AtomicU64, Lock, is_dyn_thread_safe}; +use rustc_data_structures::sync::{AtomicU64, Lock}; use rustc_data_structures::unord::UnordMap; use rustc_errors::DiagInner; use rustc_index::IndexVec; @@ -635,11 +634,6 @@ fn assert_dep_node_not_yet_allocated_in_current_session( if !ok { panic!("{}", msg()) } - } else if let Some(nodes_in_current_session) = &self.current.nodes_in_current_session { - outline(|| { - let seen = nodes_in_current_session.lock().contains_key(dep_node); - assert!(!seen, "{}", msg()); - }); } } @@ -775,7 +769,8 @@ fn alloc_and_color_node( is_green, ); - self.current.record_node(dep_node_index, key, value_fingerprint); + #[cfg(debug_assertions)] + self.current.record_edge(dep_node_index, key, value_fingerprint); dep_node_index } else { @@ -787,8 +782,6 @@ fn promote_node_and_deps_to_current( &self, prev_index: SerializedDepNodeIndex, ) -> Option { - self.current.debug_assert_not_in_new_nodes(&self.previous, prev_index); - let dep_node_index = self.current.encoder.send_promoted(prev_index, &self.colors); #[cfg(debug_assertions)] @@ -1113,13 +1106,6 @@ pub(super) struct CurrentDepGraph { #[cfg(debug_assertions)] forbidden_edge: Option, - /// Used to verify the absence of hash collisions among DepNodes. - /// This field is only `Some` if the `-Z incremental_verify_ich` option is present - /// or if `debug_assertions` are enabled. - /// - /// The map contains all DepNodes that have been allocated in the current session so far. - nodes_in_current_session: Option>>, - /// Anonymous `DepNode`s are nodes whose IDs we compute from the list of /// their edges. This has the beneficial side-effect that multiple anonymous /// nodes can be coalesced into one without changing the semantics of the @@ -1160,9 +1146,6 @@ fn new( let new_node_count_estimate = 102 * prev_graph_node_count / 100 + 200; - let new_node_dbg = - session.opts.unstable_opts.incremental_verify_ich || cfg!(debug_assertions); - CurrentDepGraph { encoder: GraphEncoder::new(session, encoder, prev_graph_node_count, previous), anon_node_to_index: ShardedHashMap::with_capacity( @@ -1174,12 +1157,6 @@ fn new( forbidden_edge, #[cfg(debug_assertions)] value_fingerprints: Lock::new(IndexVec::from_elem_n(None, new_node_count_estimate)), - nodes_in_current_session: new_node_dbg.then(|| { - Lock::new(FxHashMap::with_capacity_and_hasher( - new_node_count_estimate, - Default::default(), - )) - }), total_read_count: AtomicU64::new(0), total_duplicate_read_count: AtomicU64::new(0), } @@ -1202,25 +1179,6 @@ fn record_edge( assert_eq!(prior_value_fingerprint, value_fingerprint, "Unstable fingerprints for {key:?}"); } - #[inline(always)] - fn record_node( - &self, - dep_node_index: DepNodeIndex, - key: DepNode, - _value_fingerprint: Fingerprint, - ) { - #[cfg(debug_assertions)] - self.record_edge(dep_node_index, key, _value_fingerprint); - - if let Some(ref nodes_in_current_session) = self.nodes_in_current_session { - outline(|| { - if nodes_in_current_session.lock().insert(key, dep_node_index).is_some() { - panic!("Found duplicate dep-node {key:?}"); - } - }); - } - } - /// Writes the node to the current dep-graph and allocates a `DepNodeIndex` for it. /// Assumes that this is a node that has no equivalent in the previous dep-graph. #[inline(always)] @@ -1232,28 +1190,11 @@ fn alloc_new_node( ) -> DepNodeIndex { let dep_node_index = self.encoder.send_new(key, value_fingerprint, edges); - self.record_node(dep_node_index, key, value_fingerprint); + #[cfg(debug_assertions)] + self.record_edge(dep_node_index, key, value_fingerprint); dep_node_index } - - #[inline] - fn debug_assert_not_in_new_nodes( - &self, - prev_graph: &SerializedDepGraph, - prev_index: SerializedDepNodeIndex, - ) { - if !is_dyn_thread_safe() - && let Some(ref nodes_in_current_session) = self.nodes_in_current_session - { - debug_assert!( - !nodes_in_current_session - .lock() - .contains_key(&prev_graph.index_to_node(prev_index)), - "node from previous graph present in new node collection" - ); - } - } } #[derive(Debug, Clone, Copy)] @@ -1430,17 +1371,6 @@ fn panic_on_forbidden_read(data: &DepGraphData, dep_node_index: DepNodeIndex) -> } } - if dep_node.is_none() - && let Some(nodes) = &data.current.nodes_in_current_session - { - // Try to find it among the nodes allocated so far in this session - // This is OK, there's only ever one node result possible so this is deterministic. - #[allow(rustc::potential_query_instability)] - if let Some((node, _)) = nodes.lock().iter().find(|&(_, index)| *index == dep_node_index) { - dep_node = Some(*node); - } - } - let dep_node = dep_node.map_or_else( || format!("with index {:?}", dep_node_index), |dep_node| format!("`{:?}`", dep_node), From babfcd8cec284ea80ba98c0aa5644b7022e3f1d3 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Tue, 21 Apr 2026 06:53:59 +0000 Subject: [PATCH 23/55] Prepare for merging from rust-lang/rust This updates the rust-version file to 84c11900724736a2b9afac4822031d78a9ed8e86. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index a428bf49dfdb..97c3c27262f8 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -d12e1e12ae6fb828df1b663a07c9191bc48c6d6d +84c11900724736a2b9afac4822031d78a9ed8e86 From 9af3d2cc14a1e73f7b35e7dd0180fa4c54afe869 Mon Sep 17 00:00:00 2001 From: MarcoIeni <11428655+MarcoIeni@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:24:14 +0200 Subject: [PATCH 24/55] document public dashboard (again) --- src/doc/rustc-dev-guide/src/tests/ci.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index b656d9495e0b..25869c533212 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -454,6 +454,23 @@ More information is available in the [toolstate documentation]. [rust-toolstate]: https://rust-lang-nursery.github.io/rust-toolstate [toolstate documentation]: https://forge.rust-lang.org/infra/toolstate.html +## Public CI dashboard + +To monitor the Rust CI, you can have a look at the [public dashboard] maintained by the infra team. + +These are some useful panels from the dashboard: + +- Pipeline duration: check how long the auto builds take to run. +- Top slowest jobs: check which jobs are taking the longest to run. +- Change in median job duration: check what jobs are slowest than before. Useful + to detect regressions. +- Top failed jobs: check which jobs are failing the most. + +To learn more about the dashboard, see the [Datadog CI docs]. + +[Datadog CI docs]: https://docs.datadoghq.com/continuous_integration/ +[public dashboard]: https://p.datadoghq.com/sb/3a172e20-e9e1-11ed-80e3-da7ad0900002-b5f7bb7e08b664a06b08527da85f7e30 + ## Determining the CI configuration If you want to determine which `bootstrap.toml` settings are used in CI for a From 6f511f1218624f2f5fa3c292aaa035b7fdea5df8 Mon Sep 17 00:00:00 2001 From: Amanjeev Sethi Date: Tue, 21 Apr 2026 09:38:44 -0400 Subject: [PATCH 25/55] Mentions rustc-docs for rustc_private This was missing from my last PR. Post-review. --- .../src/rustc-driver/external-rustc-drivers.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 7465dd2e9734..a243204b6ba7 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -71,9 +71,22 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. -### Getting Specific Nightly Documentation for `rustc_private` +### Getting Nightly Documentation for `rustc_private` -The nightly-rustc internal crates' documentation is only available for the latest nightly. If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. The only way to do this is to generate the documentation locally. For example, to get documentation for `nightly-2025-11-08`: +#### Latest Nightly + +For the latest nightly, you can install the `rustc-docs` component and open it directly in your browser: + +```sh +rustup component add rustc-docs +rustup doc --rustc-docs +``` + +> Note: The `rustc-docs` component is only available for recent nightly toolchains and may not be present for every nightly date. It was first introduced in [PR #75560](https://github.com/rust-lang/rust/pull/75560) (August 2020). + +#### Older Nightlies + +If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. The only way to do this is to generate the documentation locally. For example, to get documentation for `nightly-2025-11-08`: Get the Git commit hash for that nightly: From af3e34bf65417efad9e14fd857926a14a5ac08e2 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 20 Apr 2026 14:27:50 +0200 Subject: [PATCH 26/55] Stop creating ctor def ids for unions and move the ctor def id creation into the brg --- .../rustc_resolve/src/build_reduced_graph.rs | 10 +++++++++- compiler/rustc_resolve/src/def_collector.rs | 19 +------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 4148ec1b7abd..4a7237ba192b 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -902,7 +902,7 @@ fn build_reduced_graph_for_item(&mut self, item: &'a Item) { // If this is a tuple or unit struct, define a name // in the value namespace as well. - if let Some(ctor_node_id) = vdata.ctor_node_id() { + if let Some((ctor_kind, ctor_node_id)) = CtorKind::from_ast(vdata) { // If the structure is marked as non_exhaustive then lower the visibility // to within the crate. let mut ctor_vis = if vis.is_public() @@ -927,6 +927,14 @@ fn build_reduced_graph_for_item(&mut self, item: &'a Item) { } field_visibilities.push(field_vis.to_def_id()); } + // If this is a unit or tuple-like struct, register the constructor. + self.create_def( + ctor_node_id, + None, + DefKind::Ctor(CtorOf::Struct, ctor_kind), + item.span, + ); + let feed = self.r.feed(ctor_node_id); let ctor_def_id = feed.key(); let ctor_res = self.res(ctor_def_id); diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index a9b2ad9223a3..982e92a0c149 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -191,24 +191,7 @@ fn visit_item(&mut self, i: &'a Item) { } self.with_parent(def_id, |this| { - this.with_impl_trait(ImplTraitContext::Existential, |this| { - match i.kind { - ItemKind::Struct(_, _, ref struct_def) - | ItemKind::Union(_, _, ref struct_def) => { - // If this is a unit or tuple-like struct, register the constructor. - if let Some((ctor_kind, ctor_node_id)) = CtorKind::from_ast(struct_def) { - this.create_def( - ctor_node_id, - None, - DefKind::Ctor(CtorOf::Struct, ctor_kind), - i.span, - ); - } - } - _ => {} - } - this.brg_visit_item(i); - }) + this.with_impl_trait(ImplTraitContext::Existential, |this| this.brg_visit_item(i)) }); } From 797e68f240731cd805c553d34f79d39d1a36454c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 20 Apr 2026 14:45:04 +0200 Subject: [PATCH 27/55] Move variant ctor creation into the brg --- compiler/rustc_resolve/src/build_reduced_graph.rs | 8 +++++++- compiler/rustc_resolve/src/def_collector.rs | 14 ++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 4a7237ba192b..64e959265a15 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -1504,7 +1504,13 @@ pub(crate) fn brg_visit_variant(&mut self, variant: &'a ast::Variant) { }; // Define a constructor name in the value namespace. - if let Some(ctor_node_id) = variant.data.ctor_node_id() { + if let Some((ctor_kind, ctor_node_id)) = CtorKind::from_ast(&variant.data) { + self.create_def( + ctor_node_id, + None, + DefKind::Ctor(CtorOf::Variant, ctor_kind), + variant.span, + ); let feed = self.r.feed(ctor_node_id); let ctor_def_id = feed.key(); let ctor_res = self.res(ctor_def_id); diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 982e92a0c149..f050bc059782 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -7,8 +7,8 @@ use rustc_expand::expand::AstFragment; use rustc_hir as hir; use rustc_hir::Target; +use rustc_hir::def::DefKind; use rustc_hir::def::Namespace::{TypeNS, ValueNS}; -use rustc_hir::def::{CtorKind, CtorOf, DefKind}; use rustc_hir::def_id::LocalDefId; use rustc_middle::span_bug; use rustc_span::{Span, Symbol, sym}; @@ -295,17 +295,7 @@ fn visit_variant(&mut self, v: &'a Variant) { return; } let def = self.create_def(v.id, Some(v.ident.name), DefKind::Variant, v.span); - self.with_parent(def, |this| { - if let Some((ctor_kind, ctor_node_id)) = CtorKind::from_ast(&v.data) { - this.create_def( - ctor_node_id, - None, - DefKind::Ctor(CtorOf::Variant, ctor_kind), - v.span, - ); - } - this.brg_visit_variant(v); - }); + self.with_parent(def, |this| this.brg_visit_variant(v)); } fn visit_where_predicate(&mut self, pred: &'a WherePredicate) { From 209a2a6e3315a9d753f01caa8b83295d522d0a2c Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 20 Apr 2026 15:16:37 +0200 Subject: [PATCH 28/55] delegation macros are never handled in the def collector --- compiler/rustc_resolve/src/def_collector.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index f050bc059782..dc36d7b709ad 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -177,11 +177,12 @@ fn visit_item(&mut self, i: &'a Item) { self.brg_visit_item(i); return; } - ItemKind::MacCall(..) | ItemKind::DelegationMac(..) => { + ItemKind::MacCall(..) => { self.visit_macro_invoc(i.id); self.brg_visit_item(i); return; } + ItemKind::DelegationMac(..) => unreachable!(), }; let def_id = self.create_def(i.id, i.kind.ident().map(|ident| ident.name), def_kind, i.span); From af03b49b25b8d078c6ea2eb8b315f70563349969 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 20 Apr 2026 15:20:16 +0200 Subject: [PATCH 29/55] Handle MacCall items directly in the def collector instead of entangling it with the brg logic --- compiler/rustc_resolve/src/build_reduced_graph.rs | 7 ++++--- compiler/rustc_resolve/src/def_collector.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 64e959265a15..ffd18302cd46 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -1377,7 +1377,6 @@ pub(crate) fn brg_visit_item(&mut self, item: &'a Item) { visit::walk_item(self, item); macro_rules_scope } - ItemKind::MacCall(..) => self.visit_invoc_in_module(item.id), _ => { let orig_macro_rules_scope = self.parent_scope.macro_rules; self.build_reduced_graph_for_item(item); @@ -1402,8 +1401,10 @@ pub(crate) fn brg_visit_item(&mut self, item: &'a Item) { self.parent_scope.module = orig_module_scope; } - pub(crate) fn brg_visit_stmt_mac_call(&mut self, stmt: &'a ast::Stmt) { - self.parent_scope.macro_rules = self.visit_invoc_in_module(stmt.id); + /// Handle a macro call that itself can produce new `macro_rules` items + /// in the current module. + pub(crate) fn brg_visit_mac_call_in_module(&mut self, id: NodeId) { + self.parent_scope.macro_rules = self.visit_invoc_in_module(id); } pub(crate) fn brg_visit_block(&mut self, block: &'a Block) { diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index dc36d7b709ad..6b21464daff1 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -179,7 +179,7 @@ fn visit_item(&mut self, i: &'a Item) { } ItemKind::MacCall(..) => { self.visit_macro_invoc(i.id); - self.brg_visit_item(i); + self.brg_visit_mac_call_in_module(i.id); return; } ItemKind::DelegationMac(..) => unreachable!(), @@ -497,7 +497,7 @@ fn visit_ty(&mut self, ty: &'a Ty) { fn visit_stmt(&mut self, stmt: &'a Stmt) { match stmt.kind { StmtKind::MacCall(..) => { - self.brg_visit_stmt_mac_call(stmt); + self.brg_visit_mac_call_in_module(stmt.id); self.visit_macro_invoc(stmt.id) } // FIXME(impl_trait_in_bindings): We don't really have a good way of From 3070ce9d1ba683226fee8b9bdd2529ac09a42f50 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 20 Apr 2026 14:06:31 +0200 Subject: [PATCH 30/55] Immediately feed visibility on DefId creation --- compiler/rustc_middle/src/ty/context.rs | 17 ++++ .../rustc_resolve/src/build_reduced_graph.rs | 56 ++++++----- compiler/rustc_resolve/src/def_collector.rs | 92 ++++++++++--------- compiler/rustc_resolve/src/late.rs | 5 +- compiler/rustc_resolve/src/lib.rs | 39 +++----- 5 files changed, 115 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 44cd6499fb08..48f58f337ca1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -702,6 +702,23 @@ pub fn feed_anon_const_type(self, key: LocalDefId, value: ty::EarlyBinder<'tcx, pub fn feed_delayed_owner(self, key: LocalDefId, owner: MaybeOwner<'tcx>) { TyCtxtFeed { tcx: self, key }.delayed_owner(owner); } + + // Trait impl item visibility is inherited from its trait when not specified + // explicitly. In that case we cannot determine it in early resolve, + // but instead are feeding it in late resolve, where we don't have access to the + // `TyCtxtFeed` anymore. + // To avoid having to hash the `LocalDefId` multiple times for inserting and removing the + // `TyCtxtFeed` from a hash table, we add this hack to feed the visibility. + // Do not use outside of the resolver query. + pub fn feed_visibility_for_trait_impl_item(self, key: LocalDefId, vis: ty::Visibility) { + if cfg!(debug_assertions) { + match self.def_kind(self.local_parent(key)) { + DefKind::Impl { of_trait: true } => {} + other => bug!("{key:?} is not an assoc item of a trait impl: {other:?}"), + } + } + TyCtxtFeed { tcx: self, key }.visibility(vis.to_def_id()) + } } impl<'tcx, KEY: Copy> TyCtxtFeed<'tcx, KEY> { diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index ffd18302cd46..19f0ab6e37a0 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -21,7 +21,7 @@ use rustc_index::bit_set::DenseBitSet; use rustc_metadata::creader::LoadedMacro; use rustc_middle::metadata::{ModChild, Reexport}; -use rustc_middle::ty::{Feed, Visibility}; +use rustc_middle::ty::{TyCtxtFeed, Visibility}; use rustc_middle::{bug, span_bug}; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind}; use rustc_span::{Ident, Span, Symbol, kw, sym}; @@ -563,6 +563,7 @@ fn build_reduced_graph_for_use_tree( item: &Item, vis: Visibility, root_span: Span, + feed: TyCtxtFeed<'tcx, LocalDefId>, ) { debug!( "build_reduced_graph_for_use_tree(parent_prefix={:?}, use_tree={:?}, nested={})", @@ -572,7 +573,7 @@ fn build_reduced_graph_for_use_tree( // Top level use tree reuses the item's id and list stems reuse their parent // use tree's ids, so in both cases their visibilities are already filled. if nested && !list_stem { - self.r.feed_visibility(self.r.feed(id), vis); + self.r.feed_visibility(feed, vis); } let mut prefix_iter = parent_prefix @@ -735,11 +736,11 @@ fn build_reduced_graph_for_use_tree( } ast::UseTreeKind::Nested { ref items, .. } => { for &(ref tree, id) in items { - self.create_def(id, None, DefKind::Use, use_tree.span()); + let feed = self.create_def(id, None, DefKind::Use, use_tree.span()); self.build_reduced_graph_for_use_tree( // This particular use tree tree, id, &prefix, true, false, // The whole `use` item - item, vis, root_span, + item, vis, root_span, feed, ); } @@ -768,6 +769,7 @@ fn build_reduced_graph_for_use_tree( self.parent_scope.module.nearest_parent_mod().expect_local(), ), root_span, + feed, ); } } @@ -778,7 +780,7 @@ fn build_reduced_graph_for_struct_variant( &mut self, fields: &[ast::FieldDef], ident: Ident, - feed: Feed<'tcx, LocalDefId>, + feed: TyCtxtFeed<'tcx, LocalDefId>, adt_res: Res, adt_vis: Visibility, adt_span: Span, @@ -798,13 +800,12 @@ fn build_reduced_graph_for_struct_variant( } /// Constructs the reduced graph for one item. - fn build_reduced_graph_for_item(&mut self, item: &'a Item) { + fn build_reduced_graph_for_item(&mut self, item: &'a Item, feed: TyCtxtFeed<'tcx, LocalDefId>) { let parent_scope = &self.parent_scope; let parent = parent_scope.module.expect_local(); let expansion = parent_scope.expansion; let sp = item.span; let vis = self.resolve_visibility(&item.vis); - let feed = self.r.feed(item.id); let local_def_id = feed.key(); let def_id = local_def_id.to_def_id(); let def_kind = self.r.tcx.def_kind(def_id); @@ -825,6 +826,7 @@ fn build_reduced_graph_for_item(&mut self, item: &'a Item) { item, vis, use_tree.span(), + feed, ); } @@ -867,7 +869,7 @@ fn build_reduced_graph_for_item(&mut self, item: &'a Item) { // Functions introducing procedural macros reserve a slot // in the macro namespace as well (see #52225). - self.define_macro(item); + self.define_macro(item, feed); } // These items live in the type namespace. @@ -928,14 +930,13 @@ fn build_reduced_graph_for_item(&mut self, item: &'a Item) { field_visibilities.push(field_vis.to_def_id()); } // If this is a unit or tuple-like struct, register the constructor. - self.create_def( + let feed = self.create_def( ctor_node_id, None, DefKind::Ctor(CtorOf::Struct, ctor_kind), item.span, ); - let feed = self.r.feed(ctor_node_id); let ctor_def_id = feed.key(); let ctor_res = self.res(ctor_def_id); self.r.define_local(parent, ident, ValueNS, ctor_res, ctor_vis, sp, expansion); @@ -1070,8 +1071,8 @@ pub(crate) fn build_reduced_graph_for_foreign_item( &mut self, item: &ForeignItem, ident: Ident, + feed: TyCtxtFeed<'tcx, LocalDefId>, ) { - let feed = self.r.feed(item.id); let local_def_id = feed.key(); let def_id = local_def_id.to_def_id(); let ns = match item.kind { @@ -1267,10 +1268,13 @@ fn insert_unused_macro(&mut self, ident: Ident, def_id: LocalDefId, node_id: Nod } } - fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'ra> { + fn define_macro( + &mut self, + item: &ast::Item, + feed: TyCtxtFeed<'tcx, LocalDefId>, + ) -> MacroRulesScopeRef<'ra> { let parent_scope = self.parent_scope; let expansion = parent_scope.expansion; - let feed = self.r.feed(item.id); let def_id = feed.key(); let (res, orig_ident, span, macro_rules) = match &item.kind { ItemKind::MacroDef(ident, def) => { @@ -1369,17 +1373,17 @@ fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'ra> { } impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { - pub(crate) fn brg_visit_item(&mut self, item: &'a Item) { + pub(crate) fn brg_visit_item(&mut self, item: &'a Item, feed: TyCtxtFeed<'tcx, LocalDefId>) { let orig_module_scope = self.parent_scope.module; self.parent_scope.macro_rules = match item.kind { ItemKind::MacroDef(..) => { - let macro_rules_scope = self.define_macro(item); + let macro_rules_scope = self.define_macro(item, feed); visit::walk_item(self, item); macro_rules_scope } _ => { let orig_macro_rules_scope = self.parent_scope.macro_rules; - self.build_reduced_graph_for_item(item); + self.build_reduced_graph_for_item(item, feed); match item.kind { ItemKind::Mod(..) => { // Visit attributes after items for backward compatibility. @@ -1422,9 +1426,9 @@ pub(crate) fn brg_visit_assoc_item( ctxt: AssocCtxt, ident: Ident, ns: Namespace, + feed: TyCtxtFeed<'tcx, LocalDefId>, ) { let vis = self.resolve_visibility(&item.vis); - let feed = self.r.feed(item.id); let local_def_id = feed.key(); let def_id = local_def_id.to_def_id(); @@ -1476,21 +1480,28 @@ pub(crate) fn visit_assoc_item_mac_call( } } - pub(crate) fn brg_visit_field_def(&mut self, sf: &'a ast::FieldDef) { + pub(crate) fn brg_visit_field_def( + &mut self, + sf: &'a ast::FieldDef, + feed: TyCtxtFeed<'tcx, LocalDefId>, + ) { let vis = self.resolve_visibility(&sf.vis); - self.r.feed_visibility(self.r.feed(sf.id), vis); + self.r.feed_visibility(feed, vis); visit::walk_field_def(self, sf); } // Constructs the reduced graph for one variant. Variants exist in the // type and value namespaces. - pub(crate) fn brg_visit_variant(&mut self, variant: &'a ast::Variant) { + pub(crate) fn brg_visit_variant( + &mut self, + variant: &'a ast::Variant, + feed: TyCtxtFeed<'tcx, LocalDefId>, + ) { let parent = self.parent_scope.module.expect_local(); let expn_id = self.parent_scope.expansion; let ident = variant.ident; // Define a name in the type namespace. - let feed = self.r.feed(variant.id); let def_id = feed.key(); let vis = self.resolve_visibility(&variant.vis); self.r.define_local(parent, ident, TypeNS, self.res(def_id), vis, variant.span, expn_id); @@ -1506,13 +1517,12 @@ pub(crate) fn brg_visit_variant(&mut self, variant: &'a ast::Variant) { // Define a constructor name in the value namespace. if let Some((ctor_kind, ctor_node_id)) = CtorKind::from_ast(&variant.data) { - self.create_def( + let feed = self.create_def( ctor_node_id, None, DefKind::Ctor(CtorOf::Variant, ctor_kind), variant.span, ); - let feed = self.r.feed(ctor_node_id); let ctor_def_id = feed.key(); let ctor_res = self.res(ctor_def_id); self.r.define_local(parent, ident, ValueNS, ctor_res, ctor_vis, variant.span, expn_id); diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 6b21464daff1..0730c4e4ac9c 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -11,6 +11,7 @@ use rustc_hir::def::Namespace::{TypeNS, ValueNS}; use rustc_hir::def_id::LocalDefId; use rustc_middle::span_bug; +use rustc_middle::ty::TyCtxtFeed; use rustc_span::{Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -43,22 +44,20 @@ pub(super) fn create_def( name: Option, def_kind: DefKind, span: Span, - ) -> LocalDefId { + ) -> TyCtxtFeed<'tcx, LocalDefId> { let parent_def = self.invocation_parent.parent_def; debug!( "create_def(node_id={:?}, def_kind={:?}, parent_def={:?})", node_id, def_kind, parent_def ); - self.r - .create_def( - parent_def, - node_id, - name, - def_kind, - self.parent_scope.expansion.to_expn_id(), - span.with_parent(None), - ) - .def_id() + self.r.create_def( + parent_def, + node_id, + name, + def_kind, + self.parent_scope.expansion.to_expn_id(), + span.with_parent(None), + ) } fn with_parent(&mut self, parent_def: LocalDefId, f: F) { @@ -100,7 +99,7 @@ fn collect_field(&mut self, field: &'a FieldDef, index: Option) { } else { let name = field.ident.map_or_else(|| sym::integer(index(self)), |ident| ident.name); let def = self.create_def(field.id, Some(name), DefKind::Field, field.span); - self.with_parent(def, |this| this.brg_visit_field_def(field)); + self.with_parent(def.def_id(), |this| this.brg_visit_field_def(field, def)); } } @@ -173,8 +172,8 @@ fn visit_item(&mut self, i: &'a Item) { } ItemKind::GlobalAsm(..) => DefKind::GlobalAsm, ItemKind::Use(_) => { - self.create_def(i.id, None, DefKind::Use, i.span); - self.brg_visit_item(i); + let feed = self.create_def(i.id, None, DefKind::Use, i.span); + self.brg_visit_item(i, feed); return; } ItemKind::MacCall(..) => { @@ -184,15 +183,14 @@ fn visit_item(&mut self, i: &'a Item) { } ItemKind::DelegationMac(..) => unreachable!(), }; - let def_id = - self.create_def(i.id, i.kind.ident().map(|ident| ident.name), def_kind, i.span); + let feed = self.create_def(i.id, i.kind.ident().map(|ident| ident.name), def_kind, i.span); if let Some(macro_data) = opt_macro_data { - self.r.new_local_macro(def_id, macro_data); + self.r.new_local_macro(feed.def_id(), macro_data); } - self.with_parent(def_id, |this| { - this.with_impl_trait(ImplTraitContext::Existential, |this| this.brg_visit_item(i)) + self.with_parent(feed.def_id(), |this| { + this.with_impl_trait(ImplTraitContext::Existential, |this| this.brg_visit_item(i, feed)) }); } @@ -228,15 +226,17 @@ fn visit_fn(&mut self, fn_kind: FnKind<'a>, _: &AttrVec, span: Span, _: NodeId) } let (return_id, return_span) = coroutine_kind.return_id(); - let return_def = self.create_def(return_id, None, DefKind::OpaqueTy, return_span); + let return_def = + self.create_def(return_id, None, DefKind::OpaqueTy, return_span).def_id(); self.with_parent(return_def, |this| this.visit_fn_ret_ty(output)); // If this async fn has no body (i.e. it's an async fn signature in a trait) // then the closure_def will never be used, and we should avoid generating a // def-id for it. if let Some(body) = body { - let closure_def = - self.create_def(coroutine_kind.closure_id(), None, DefKind::Closure, span); + let closure_def = self + .create_def(coroutine_kind.closure_id(), None, DefKind::Closure, span) + .def_id(); self.with_parent(closure_def, |this| this.visit_block(body)); } } @@ -246,8 +246,9 @@ fn visit_fn(&mut self, fn_kind: FnKind<'a>, _: &AttrVec, span: Span, _: NodeId) // Async closures desugar to closures inside of closures, so // we must create two defs. - let coroutine_def = - self.create_def(coroutine_kind.closure_id(), None, DefKind::Closure, span); + let coroutine_def = self + .create_def(coroutine_kind.closure_id(), None, DefKind::Closure, span) + .def_id(); self.with_parent(coroutine_def, |this| this.visit_expr(body)); } _ => visit::walk_fn(self, fn_kind), @@ -283,8 +284,8 @@ fn visit_foreign_item(&mut self, fi: &'a ForeignItem) { let def = self.create_def(fi.id, Some(ident.name), def_kind, fi.span); - self.with_parent(def, |this| { - this.build_reduced_graph_for_foreign_item(fi, ident); + self.with_parent(def.def_id(), |this| { + this.build_reduced_graph_for_foreign_item(fi, ident, def); visit::walk_item(this, fi) }); } @@ -295,8 +296,8 @@ fn visit_variant(&mut self, v: &'a Variant) { self.visit_invoc_in_module(v.id); return; } - let def = self.create_def(v.id, Some(v.ident.name), DefKind::Variant, v.span); - self.with_parent(def, |this| this.brg_visit_variant(v)); + let feed = self.create_def(v.id, Some(v.ident.name), DefKind::Variant, v.span); + self.with_parent(feed.def_id(), |this| this.brg_visit_variant(v, feed)); } fn visit_where_predicate(&mut self, pred: &'a WherePredicate) { @@ -365,8 +366,8 @@ fn visit_assoc_item(&mut self, i: &'a AssocItem, ctxt: visit::AssocCtxt) { } }; - let def = self.create_def(i.id, Some(ident.name), def_kind, i.span); - self.with_parent(def, |this| this.brg_visit_assoc_item(i, ctxt, ident, ns)); + let feed = self.create_def(i.id, Some(ident.name), def_kind, i.span); + self.with_parent(feed.def_id(), |this| this.brg_visit_assoc_item(i, ctxt, ident, ns, feed)); } fn visit_pat(&mut self, pat: &'a Pat) { @@ -384,8 +385,9 @@ fn visit_anon_const(&mut self, constant: &'a AnonConst) { // to avoid affecting stable we have to feature gate the not creating // anon consts if !self.r.tcx.features().min_generic_const_args() { - let parent = - self.create_def(constant.id, None, DefKind::AnonConst, constant.value.span); + let parent = self + .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) + .def_id(); return self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); } @@ -395,8 +397,9 @@ fn visit_anon_const(&mut self, constant: &'a AnonConst) { }), MgcaDisambiguation::AnonConst => { self.with_const_arg(ConstArgContext::NonDirect, |this| { - let parent = - this.create_def(constant.id, None, DefKind::AnonConst, constant.value.span); + let parent = this + .create_def(constant.id, None, DefKind::AnonConst, constant.value.span) + .def_id(); this.with_parent(parent, |this| visit::walk_anon_const(this, constant)); }) } @@ -414,7 +417,7 @@ fn visit_expr(&mut self, expr: &'a Expr) { return; } ExprKind::Closure(..) | ExprKind::Gen(..) => { - self.create_def(expr.id, None, DefKind::Closure, expr.span) + self.create_def(expr.id, None, DefKind::Closure, expr.span).def_id() } ExprKind::ConstBlock(constant) => { // Under `min_generic_const_args` a `const { }` block sometimes @@ -429,7 +432,8 @@ fn visit_expr(&mut self, expr: &'a Expr) { visit::walk_attribute(this, attr); } - let def = this.create_def(constant.id, None, def_kind, constant.value.span); + let def = + this.create_def(constant.id, None, def_kind, constant.value.span).def_id(); this.with_parent(def, |this| visit::walk_anon_const(this, constant)); }); } @@ -479,7 +483,7 @@ fn visit_ty(&mut self, ty: &'a Ty) { ImplTraitContext::Existential => DefKind::OpaqueTy, ImplTraitContext::InBinding => return visit::walk_ty(self, ty), }; - let id = self.create_def(opaque_id, Some(name), kind, ty.span); + let id = self.create_def(opaque_id, Some(name), kind, ty.span).def_id(); match self.invocation_parent.impl_trait_context { // Do not nest APIT, as we desugar them as `impl_trait: bounds`, // so the `impl_trait` node is not a parent to `bounds`. @@ -602,12 +606,14 @@ fn visit_inline_asm(&mut self, asm: &'a InlineAsm) { } } InlineAsmOperand::Const { anon_const } => { - let def = self.create_def( - anon_const.id, - None, - DefKind::InlineConst, - anon_const.value.span, - ); + let def = self + .create_def( + anon_const.id, + None, + DefKind::InlineConst, + anon_const.value.span, + ) + .def_id(); self.with_parent(def, |this| visit::walk_anon_const(this, anon_const)); } InlineAsmOperand::Sym { sym } => self.visit_inline_asm_sym(sym), diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 87a2ba972e49..334b2449841f 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3765,7 +3765,10 @@ fn check_trait_item( ); Visibility::Public }; - this.r.feed_visibility(this.r.feed(id), vis); + // HACK: because we don't want to track the `TyCtxtFeed` through the resolver to here + // in a hash-map, we instead conjure a `TyCtxtFeed` for any `DefId` here, but prevent + // it from being used generally. + this.r.tcx.feed_visibility_for_trait_impl_item(this.r.local_def_id(id), vis); }; let Some(decl) = decl else { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a44a7b30b4e6..2368e28b0442 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -66,8 +66,8 @@ use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::query::Providers; use rustc_middle::ty::{ - self, DelegationInfo, Feed, MainDefinition, RegisteredTools, ResolverAstLowering, - ResolverGlobalCtxt, TyCtxt, TyCtxtFeed, Visibility, + self, DelegationInfo, MainDefinition, RegisteredTools, ResolverAstLowering, ResolverGlobalCtxt, + TyCtxt, TyCtxtFeed, Visibility, }; use rustc_session::config::CrateType; use rustc_session::lint::builtin::PRIVATE_MACRO_USE; @@ -1415,7 +1415,7 @@ pub struct Resolver<'ra, 'tcx> { next_node_id: NodeId = CRATE_NODE_ID, - node_id_to_def_id: NodeMap>, + node_id_to_def_id: NodeMap, per_parent_disambiguators: LocalDefIdMap, @@ -1582,19 +1582,11 @@ fn as_ref(&self) -> &Resolver<'ra, 'tcx> { impl<'tcx> Resolver<'_, 'tcx> { fn opt_local_def_id(&self, node: NodeId) -> Option { - self.opt_feed(node).map(|f| f.key()) - } - - fn local_def_id(&self, node: NodeId) -> LocalDefId { - self.feed(node).key() - } - - fn opt_feed(&self, node: NodeId) -> Option> { self.node_id_to_def_id.get(&node).copied() } - fn feed(&self, node: NodeId) -> Feed<'tcx, LocalDefId> { - self.opt_feed(node).unwrap_or_else(|| panic!("no entry for node id: `{node:?}`")) + fn local_def_id(&self, node: NodeId) -> LocalDefId { + self.opt_local_def_id(node).unwrap_or_else(|| panic!("no entry for node id: `{node:?}`")) } fn local_def_kind(&self, node: NodeId) -> DefKind { @@ -1617,7 +1609,7 @@ fn create_def( node_id, name, def_kind, - self.tcx.definitions_untracked().def_key(self.node_id_to_def_id[&node_id].key()), + self.tcx.definitions_untracked().def_key(self.node_id_to_def_id[&node_id]), ); // FIXME: remove `def_span` body, pass in the right spans here and call `tcx.at().create_def()` @@ -1645,7 +1637,7 @@ fn create_def( // we don't need a mapping from `NodeId` to `LocalDefId`. if node_id != ast::DUMMY_NODE_ID { debug!("create_def: def_id_to_node_id[{:?}] <-> {:?}", def_id, node_id); - self.node_id_to_def_id.insert(node_id, feed.downgrade()); + self.node_id_to_def_id.insert(node_id, def_id); } feed @@ -1696,7 +1688,7 @@ pub fn tcx(&self) -> TyCtxt<'tcx> { fn def_id_to_node_id(&self, def_id: LocalDefId) -> NodeId { self.node_id_to_def_id .items() - .filter(|(_, v)| v.key() == def_id) + .filter(|(_, v)| **v == def_id) .map(|(k, _)| *k) .get_only() .unwrap() @@ -1737,8 +1729,7 @@ pub fn new( let crate_feed = tcx.create_local_crate_def_id(crate_span); crate_feed.def_kind(DefKind::Mod); - let crate_feed = crate_feed.downgrade(); - node_id_to_def_id.insert(CRATE_NODE_ID, crate_feed); + node_id_to_def_id.insert(CRATE_NODE_ID, CRATE_DEF_ID); let mut invocation_parents = FxHashMap::default(); invocation_parents.insert(LocalExpnId::ROOT, InvocationParent::ROOT); @@ -1891,8 +1882,7 @@ pub fn arenas() -> ResolverArenas<'ra> { Default::default() } - fn feed_visibility(&mut self, feed: Feed<'tcx, LocalDefId>, vis: Visibility) { - let feed = feed.upgrade(self.tcx); + fn feed_visibility(&mut self, feed: TyCtxtFeed<'tcx, LocalDefId>, vis: Visibility) { feed.visibility(vis.to_def_id()); self.visibilities_for_hashing.push((feed.def_id(), vis)); } @@ -1911,8 +1901,7 @@ pub fn into_outputs(self) -> ResolverOutputs<'tcx> { .stripped_cfg_items .into_iter() .filter_map(|item| { - let parent_scope = - self.node_id_to_def_id.get(&item.parent_scope)?.key().to_def_id(); + let parent_scope = self.node_id_to_def_id.get(&item.parent_scope)?.to_def_id(); Some(StrippedCfgItem { parent_scope, ident: item.ident, cfg: item.cfg }) }) .collect(); @@ -1942,11 +1931,7 @@ pub fn into_outputs(self) -> ResolverOutputs<'tcx> { lifetimes_res_map: self.lifetimes_res_map, extra_lifetime_params_map: self.extra_lifetime_params_map, next_node_id: self.next_node_id, - node_id_to_def_id: self - .node_id_to_def_id - .into_items() - .map(|(k, f)| (k, f.key())) - .collect(), + node_id_to_def_id: self.node_id_to_def_id, trait_map: self.trait_map, lifetime_elision_allowed: self.lifetime_elision_allowed, lint_buffer: Steal::new(self.lint_buffer), From 0169aaea3152cc4c906aa2a4f7cfdf346c68b33a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 13 Feb 2026 14:09:26 +0100 Subject: [PATCH 31/55] va_arg: fix potential misaligned load --- compiler/rustc_codegen_llvm/src/va_arg.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index 19735a29dfb2..6f05872ac93e 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -69,7 +69,8 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>( { let adjusted_size = bx.cx().const_i32((slot_size.bytes() - size.bytes()) as i32); let adjusted = bx.inbounds_ptradd(addr, adjusted_size); - (adjusted, addr_align) + // We're in the middle of a slot now, so use the type's alignment, not the slot's. + (adjusted, align) } else { (addr, addr_align) } From 45f98b9aa703f9783807e2ec958e8ec6f38a98e4 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 13 Feb 2026 13:10:46 +0100 Subject: [PATCH 32/55] va_arg: remove unused argument --- compiler/rustc_codegen_llvm/src/va_arg.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index 6f05872ac93e..931f8bae1acd 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -11,7 +11,7 @@ use rustc_target::spec::{Arch, Env, LlvmAbi, RustcAbi}; use crate::builder::Builder; -use crate::llvm::{Type, Value}; +use crate::llvm::Value; use crate::type_of::LayoutLlvmExt; fn round_up_to_alignment<'ll>( @@ -27,12 +27,11 @@ fn round_pointer_up_to_alignment<'ll>( bx: &mut Builder<'_, 'll, '_>, addr: &'ll Value, align: Align, - ptr_ty: &'ll Type, ) -> &'ll Value { let ptr = bx.inbounds_ptradd(addr, bx.const_i32(align.bytes() as i32 - 1)); bx.call_intrinsic( "llvm.ptrmask", - &[ptr_ty, bx.type_i32()], + &[bx.type_ptr(), bx.type_i32()], &[ptr, bx.const_int(bx.isize_ty, -(align.bytes() as isize) as i64)], ) } @@ -53,7 +52,7 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>( let ptr = bx.load(va_list_ty, va_list_addr, ptr_align_abi); let (addr, addr_align) = if allow_higher_align && align > slot_size { - (round_pointer_up_to_alignment(bx, ptr, align, bx.type_ptr()), align) + (round_pointer_up_to_alignment(bx, ptr, align), align) } else { (ptr, slot_size) }; @@ -358,12 +357,8 @@ fn emit_powerpc_va_arg<'ll, 'tcx>( // Round up address of argument to alignment if layout.layout.align.abi > overflow_area_align { - overflow_area = round_pointer_up_to_alignment( - bx, - overflow_area, - layout.layout.align.abi, - bx.type_ptr(), - ); + overflow_area = + round_pointer_up_to_alignment(bx, overflow_area, layout.layout.align.abi); } let mem_addr = overflow_area; @@ -828,7 +823,7 @@ fn emit_hexagon_va_arg_musl<'ll, 'tcx>( } else { Align::from_bytes(4).unwrap() }; - let aligned_current = round_pointer_up_to_alignment(bx, current_ptr, arg_align, bx.type_ptr()); + let aligned_current = round_pointer_up_to_alignment(bx, current_ptr, arg_align); // Calculate next pointer position (following LLVM's logic) // Arguments <= 32 bits take 4 bytes, > 32 bits take 8 bytes @@ -850,8 +845,7 @@ fn emit_hexagon_va_arg_musl<'ll, 'tcx>( bx.switch_to_block(from_overflow); // Align overflow pointer using the same alignment rules - let aligned_overflow = - round_pointer_up_to_alignment(bx, overflow_ptr, arg_align, bx.type_ptr()); + let aligned_overflow = round_pointer_up_to_alignment(bx, overflow_ptr, arg_align); let overflow_value_addr = aligned_overflow; // Update overflow pointer - use the same size calculation @@ -891,7 +885,7 @@ fn emit_hexagon_va_arg_bare_metal<'ll, 'tcx>( let aligned_ptr = if ty_align.bytes() > 4 { // Ensure alignment is a power of 2 debug_assert!(ty_align.bytes().is_power_of_two(), "Alignment is not power of 2!"); - round_pointer_up_to_alignment(bx, current_ptr, ty_align, bx.type_ptr()) + round_pointer_up_to_alignment(bx, current_ptr, ty_align) } else { current_ptr }; From b90687377c0519b477af52aa6b2d08944122612f Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Wed, 22 Apr 2026 10:22:14 +0800 Subject: [PATCH 33/55] Add constness & asyncness --- .../rustc_public/src/compiler_interface.rs | 26 +++++- compiler/rustc_public/src/ty.rs | 34 +++++++ .../src/unstable/convert/internal.rs | 34 ++++++- .../src/unstable/convert/stable/mod.rs | 20 ++++ .../rustc_public_bridge/src/context/impls.rs | 10 ++ .../rustc_public/check_fn_attrs.rs | 93 +++++++++++++++++++ 6 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 tests/ui-fulldeps/rustc_public/check_fn_attrs.rs diff --git a/compiler/rustc_public/src/compiler_interface.rs b/compiler/rustc_public/src/compiler_interface.rs index 30fbc5c5233d..c2f36bea81ca 100644 --- a/compiler/rustc_public/src/compiler_interface.rs +++ b/compiler/rustc_public/src/compiler_interface.rs @@ -17,11 +17,11 @@ use crate::mir::{BinOp, Body, Place, UnOp}; use crate::target::{MachineInfo, MachineSize}; use crate::ty::{ - AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, CoroutineDef, Discr, FieldDef, FnDef, - ForeignDef, ForeignItemKind, ForeignModule, ForeignModuleDef, GenericArgs, GenericPredicates, - Generics, ImplDef, ImplTrait, IntrinsicDef, LineInfo, MirConst, PolyFnSig, RigidTy, Span, - TraitDecl, TraitDef, TraitRef, Ty, TyConst, TyConstId, TyKind, UintTy, VariantDef, VariantIdx, - VtblEntry, + AdtDef, AdtKind, Allocation, Asyncness, ClosureDef, ClosureKind, Constness, CoroutineDef, + Discr, FieldDef, FnDef, ForeignDef, ForeignItemKind, ForeignModule, ForeignModuleDef, + GenericArgs, GenericPredicates, Generics, ImplDef, ImplTrait, IntrinsicDef, LineInfo, MirConst, + PolyFnSig, RigidTy, Span, TraitDecl, TraitDef, TraitRef, Ty, TyConst, TyConstId, TyKind, + UintTy, VariantDef, VariantIdx, VtblEntry, }; use crate::unstable::{RustcInternal, Stable, new_item_kind}; use crate::{ @@ -388,6 +388,22 @@ pub(crate) fn fn_sig(&self, def: FnDef, args: &GenericArgs) -> PolyFnSig { cx.fn_sig(def_id, args_ref).stable(&mut *tables, cx) } + /// Retrieve the constness for the given function definition. + pub(crate) fn constness(&self, def: FnDef) -> Constness { + let mut tables = self.tables.borrow_mut(); + let cx = &*self.cx.borrow(); + let def_id = def.0.internal(&mut *tables, cx.tcx); + cx.constness(def_id).stable(&mut *tables, cx) + } + + /// Retrieve the asyncness for the given function definition. + pub(crate) fn asyncness(&self, def: FnDef) -> Asyncness { + let mut tables = self.tables.borrow_mut(); + let cx = &*self.cx.borrow(); + let def_id = def.0.internal(&mut *tables, cx.tcx); + cx.asyncness(def_id).stable(&mut *tables, cx) + } + /// Retrieve the intrinsic definition if the item corresponds one. pub(crate) fn intrinsic(&self, item: DefId) -> Option { let mut tables = self.tables.borrow_mut(); diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 7ec8b688402a..9b79c92ac2b9 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -710,6 +710,16 @@ pub fn is_intrinsic(&self) -> bool { self.as_intrinsic().is_some() } + /// Get the constness of this function definition. + pub fn constness(&self) -> Constness { + with(|cx| cx.constness(*self)) + } + + /// Get the asyncness of this function definition. + pub fn asyncness(&self) -> Asyncness { + with(|cx| cx.asyncness(*self)) + } + /// Get the function signature for this function definition. pub fn fn_sig(&self) -> PolyFnSig { let kind = self.ty().kind(); @@ -1103,6 +1113,30 @@ pub fn inputs(&self) -> &[Ty] { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Constness { + Const, + NotConst, +} + +impl Constness { + pub fn is_const(self) -> bool { + matches!(self, Constness::Const) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Asyncness { + Async, + NotAsync, +} + +impl Asyncness { + pub fn is_async(self) -> bool { + matches!(self, Asyncness::Async) + } +} + #[derive(Clone, PartialEq, Eq, Debug, Serialize)] pub enum Abi { Rust, diff --git a/compiler/rustc_public/src/unstable/convert/internal.rs b/compiler/rustc_public/src/unstable/convert/internal.rs index 4f3e9f94c599..ddecfd3c73f4 100644 --- a/compiler/rustc_public/src/unstable/convert/internal.rs +++ b/compiler/rustc_public/src/unstable/convert/internal.rs @@ -14,8 +14,8 @@ use crate::mir::mono::{Instance, MonoItem, StaticDef}; use crate::mir::{BinOp, Mutability, Place, ProjectionElem, RawPtrKind, Safety, UnOp}; use crate::ty::{ - Abi, AdtDef, Binder, BoundRegionKind, BoundTyKind, BoundVariableKind, ClosureKind, - ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, FloatTy, FnSig, + Abi, AdtDef, Asyncness, Binder, BoundRegionKind, BoundTyKind, BoundVariableKind, ClosureKind, + Constness, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, FloatTy, FnSig, GenericArgKind, GenericArgs, IntTy, MirConst, Movability, Pattern, Region, RigidTy, Span, TermKind, TraitRef, Ty, TyConst, UintTy, VariantDef, VariantIdx, }; @@ -637,6 +637,36 @@ fn internal<'tcx>( } } } + +impl RustcInternal for Constness { + type T<'tcx> = rustc_hir::Constness; + + fn internal<'tcx>( + &self, + _tables: &mut Tables<'_, BridgeTys>, + _tcx: impl InternalCx<'tcx>, + ) -> Self::T<'tcx> { + match self { + Constness::Const => rustc_hir::Constness::Const, + Constness::NotConst => rustc_hir::Constness::NotConst, + } + } +} + +impl RustcInternal for Asyncness { + type T<'tcx> = rustc_ty::Asyncness; + + fn internal<'tcx>( + &self, + _tables: &mut Tables<'_, BridgeTys>, + _tcx: impl InternalCx<'tcx>, + ) -> Self::T<'tcx> { + match self { + Asyncness::Async => rustc_ty::Asyncness::Yes, + Asyncness::NotAsync => rustc_ty::Asyncness::No, + } + } +} impl RustcInternal for Span { type T<'tcx> = rustc_span::Span; diff --git a/compiler/rustc_public/src/unstable/convert/stable/mod.rs b/compiler/rustc_public/src/unstable/convert/stable/mod.rs index 4d550487525f..ce55e898b2ac 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mod.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mod.rs @@ -21,6 +21,26 @@ fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) } } +impl<'tcx> Stable<'tcx> for rustc_hir::Constness { + type T = crate::ty::Constness; + fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) -> Self::T { + match self { + rustc_hir::Constness::Const => crate::ty::Constness::Const, + rustc_hir::Constness::NotConst => crate::ty::Constness::NotConst, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_middle::ty::Asyncness { + type T = crate::ty::Asyncness; + fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) -> Self::T { + match self { + rustc_middle::ty::Asyncness::Yes => crate::ty::Asyncness::Async, + rustc_middle::ty::Asyncness::No => crate::ty::Asyncness::NotAsync, + } + } +} + impl<'tcx> Stable<'tcx> for FieldIdx { type T = usize; fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) -> Self::T { diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 1df2b1fa6428..bb504bd0017a 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -383,6 +383,16 @@ pub fn fn_sig( sig } + /// Retrieve the constness for the given function definition. + pub fn constness(&self, def_id: DefId) -> rustc_hir::Constness { + self.tcx.constness(def_id) + } + + /// Retrieve the asyncness for the given function definition. + pub fn asyncness(&self, def_id: DefId) -> ty::Asyncness { + self.tcx.asyncness(def_id) + } + /// Retrieve the intrinsic definition if the item corresponds one. pub fn intrinsic(&self, def_id: DefId) -> Option { let intrinsic = self.tcx.intrinsic_raw(def_id); diff --git a/tests/ui-fulldeps/rustc_public/check_fn_attrs.rs b/tests/ui-fulldeps/rustc_public/check_fn_attrs.rs new file mode 100644 index 000000000000..552e8cbec376 --- /dev/null +++ b/tests/ui-fulldeps/rustc_public/check_fn_attrs.rs @@ -0,0 +1,93 @@ +//@ run-pass +//! Test that users are able to query function-level constness and asyncness. + +//@ ignore-stage1 +//@ ignore-cross-compile +//@ ignore-remote +//@ edition: 2021 + +#![feature(rustc_private)] + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_public; + +use rustc_public::crate_def::CrateDef; +use rustc_public::ty::{Asyncness, Constness, FnDef}; +use std::io::Write; +use std::ops::ControlFlow; + +const CRATE_NAME: &str = "input"; + +fn test_stable_mir() -> ControlFlow<()> { + let fns = rustc_public::local_crate().fn_defs(); + + check_fn(&fns, "input::const_sync", Constness::Const, Asyncness::NotAsync); + check_fn(&fns, "input::async_fn", Constness::NotConst, Asyncness::Async); + check_fn(&fns, "input::plain", Constness::NotConst, Asyncness::NotAsync); + check_fn(&fns, "input::Widget::assoc_const", Constness::Const, Asyncness::NotAsync); + check_fn(&fns, "input::Widget::assoc_async", Constness::NotConst, Asyncness::Async); + check_fn(&fns, "input::Widget::assoc_plain", Constness::NotConst, Asyncness::NotAsync); + + ControlFlow::Continue(()) +} + +fn check_fn(fns: &[FnDef], name: &str, constness: Constness, asyncness: Asyncness) { + let fn_def = + fns.iter().find(|def| def.name() == name).unwrap_or_else(|| panic!("missing {name}")); + assert_eq!(fn_def.constness(), constness, "wrong constness for {}", fn_def.name()); + assert_eq!(fn_def.asyncness(), asyncness, "wrong asyncness for {}", fn_def.name()); +} + +fn main() { + let path = "fn_attrs_input.rs"; + generate_input(&path).unwrap(); + let args = &[ + "rustc".to_string(), + "--edition=2021".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, test_stable_mir).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + pub const fn const_sync() -> u32 {{ + 1 + }} + + pub async fn async_fn() -> u32 {{ + 2 + }} + + pub fn plain() -> u32 {{ + 3 + }} + + pub struct Widget; + + impl Widget {{ + pub const fn assoc_const() -> u32 {{ + 4 + }} + + pub async fn assoc_async() -> u32 {{ + 5 + }} + + pub fn assoc_plain() -> u32 {{ + 6 + }} + }} + "# + )?; + Ok(()) +} From 2f864a9d03d2f01fff284e228e7e9302f4b00e6e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Apr 2026 20:56:14 +1000 Subject: [PATCH 34/55] Split `Metadata` trait into `MetaBlob` and `MetaDecoder`. `Metadata` has two methods, `blob` and `decoder`, which are not used together. Splitting the trait in two will allow some cleanups in subsequent commits. --- compiler/rustc_metadata/src/rmeta/decoder.rs | 32 +++++++++++--------- compiler/rustc_metadata/src/rmeta/table.rs | 4 +-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 280b3b12e5c8..af2fb03f71d1 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -221,7 +221,7 @@ fn get_lazy_state(&self) -> LazyState { /// This is the decode context used when crate metadata was already read. /// Decoding of some types, like `Span` require some information to already been read. -/// Can be constructed from a [`TyCtxt`] and [`CrateMetadataRef`] (see the [`Metadata`] trait) +/// Can be constructed from a [`TyCtxt`] and [`CrateMetadataRef`] (see the [`MetaDecoder`] trait) pub(super) struct MetadataDecodeContext<'a, 'tcx> { blob_decoder: BlobDecodeContext<'a>, cdata: CrateMetadataRef<'a>, @@ -255,19 +255,24 @@ fn deref(&self) -> &Self::Target { } } -pub(super) trait Metadata<'a>: Copy { +pub(super) trait MetaBlob<'a>: Copy { + fn blob(self) -> &'a MetadataBlob; +} + +pub(super) trait MetaDecoder: Copy { type Context: BlobDecoder + LazyDecoder; - fn blob(self) -> &'a MetadataBlob; fn decoder(self, pos: usize) -> Self::Context; } -impl<'a> Metadata<'a> for &'a MetadataBlob { - type Context = BlobDecodeContext<'a>; - +impl<'a> MetaBlob<'a> for &'a MetadataBlob { fn blob(self) -> &'a MetadataBlob { self } +} + +impl<'a> MetaDecoder for &'a MetadataBlob { + type Context = BlobDecodeContext<'a>; fn decoder(self, pos: usize) -> Self::Context { BlobDecodeContext { @@ -285,12 +290,14 @@ fn decoder(self, pos: usize) -> Self::Context { } } -impl<'a, 'tcx> Metadata<'a> for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { - type Context = MetadataDecodeContext<'a, 'tcx>; - +impl<'a, 'tcx> MetaBlob<'a> for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { fn blob(self) -> &'a MetadataBlob { &self.0.cdata.blob } +} + +impl<'a, 'tcx> MetaDecoder for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { + type Context = MetadataDecodeContext<'a, 'tcx>; fn decoder(self, pos: usize) -> MetadataDecodeContext<'a, 'tcx> { MetadataDecodeContext { @@ -304,7 +311,7 @@ fn decoder(self, pos: usize) -> MetadataDecodeContext<'a, 'tcx> { impl LazyValue { #[inline] - fn decode<'a, 'tcx, M: Metadata<'a>>(self, metadata: M) -> T::Value<'tcx> + fn decode<'tcx, M: MetaDecoder>(self, metadata: M) -> T::Value<'tcx> where T::Value<'tcx>: Decodable, { @@ -344,10 +351,7 @@ unsafe impl> TrustedLen for DecodeIterator {} impl LazyArray { #[inline] - fn decode<'a, 'tcx, M: Metadata<'a>>( - self, - metadata: M, - ) -> DecodeIterator, M::Context> + fn decode<'tcx, M: MetaDecoder>(self, metadata: M) -> DecodeIterator, M::Context> where T::Value<'tcx>: Decodable, { diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index 3b0570dd373a..26c590856377 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -1,7 +1,7 @@ use rustc_hir::def::CtorOf; use rustc_index::Idx; -use crate::rmeta::decoder::Metadata; +use crate::rmeta::decoder::MetaBlob; use crate::rmeta::*; pub(super) trait IsDefault: Default { @@ -515,7 +515,7 @@ fn trailing_zeros(x: &[u8]) -> usize { for<'tcx> T::Value<'tcx>: FixedSizeEncoding, { /// Given the metadata, extract out the value at a particular index (if any). - pub(super) fn get<'a, 'tcx, M: Metadata<'a>>(&self, metadata: M, i: I) -> T::Value<'tcx> { + pub(super) fn get<'a, 'tcx, M: MetaBlob<'a>>(&self, metadata: M, i: I) -> T::Value<'tcx> { // Access past the end of the table returns a Default if i.index() >= self.len { return Default::default(); From be52e75d69c4f5d32f7605e8cf506ef8d5582342 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Apr 2026 21:04:10 +1000 Subject: [PATCH 35/55] Simplify one `MetaBlob` impl. It's currently an impl for `(CrateMetadataRef, TyCtxt)`, but (a) the `TyCtxt` is not used, and (b) the `CrateMetadataRef` can be simplified to a `CrateMetadata` because `CStore` access isn't required. This require changing `blob` to take `&self`, which is no big deal, and it simplifies many `get` calls. --- compiler/rustc_metadata/src/rmeta/decoder.rs | 97 +++++++++---------- .../src/rmeta/decoder/cstore_impl.rs | 14 +-- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index af2fb03f71d1..847a6428b30e 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -256,7 +256,7 @@ fn deref(&self) -> &Self::Target { } pub(super) trait MetaBlob<'a>: Copy { - fn blob(self) -> &'a MetadataBlob; + fn blob(&self) -> &'a MetadataBlob; } pub(super) trait MetaDecoder: Copy { @@ -266,7 +266,7 @@ pub(super) trait MetaDecoder: Copy { } impl<'a> MetaBlob<'a> for &'a MetadataBlob { - fn blob(self) -> &'a MetadataBlob { + fn blob(&self) -> &'a MetadataBlob { self } } @@ -290,9 +290,9 @@ fn decoder(self, pos: usize) -> Self::Context { } } -impl<'a, 'tcx> MetaBlob<'a> for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { - fn blob(self) -> &'a MetadataBlob { - &self.0.cdata.blob +impl<'a> MetaBlob<'a> for &'a CrateMetadata { + fn blob(&self) -> &'a MetadataBlob { + &self.blob } } @@ -301,7 +301,7 @@ impl<'a, 'tcx> MetaDecoder for (CrateMetadataRef<'a>, TyCtxt<'tcx>) { fn decoder(self, pos: usize) -> MetadataDecodeContext<'a, 'tcx> { MetadataDecodeContext { - blob_decoder: self.blob().decoder(pos), + blob_decoder: self.0.cdata.blob().decoder(pos), cdata: self.0, tcx: self.1, alloc_decoding_session: self.0.cdata.alloc_decoding_state.new_decoding_session(), @@ -472,7 +472,7 @@ fn decode_syntax_context(&mut self) -> SyntaxContext { cdata .root .syntax_contexts - .get((cdata, tcx), id) + .get(cdata.cdata, id) .unwrap_or_else(|| panic!("Missing SyntaxContext {id:?} for crate {cname:?}")) .decode((cdata, tcx)) }) @@ -498,13 +498,13 @@ fn decode_expn_id(&mut self) -> ExpnId { let expn_data = crate_data .root .expn_data - .get((crate_data, tcx), index) + .get(crate_data.cdata, index) .unwrap() .decode((crate_data, tcx)); let expn_hash = crate_data .root .expn_hashes - .get((crate_data, tcx), index) + .get(crate_data.cdata, index) .unwrap() .decode((crate_data, tcx)); (expn_data, expn_hash) @@ -1021,7 +1021,7 @@ fn opt_item_ident(self, tcx: TyCtxt<'_>, item_index: DefIndex) -> Option .root .tables .def_ident_span - .get((self, tcx), item_index) + .get(self.cdata, item_index) .unwrap_or_else(|| self.missing("def_ident_span", item_index)) .decode((self, tcx)); Some(Ident::new(name, span)) @@ -1036,11 +1036,11 @@ pub(super) fn map_encoded_cnum_to_current(self, cnum: CrateNum) -> CrateNum { if cnum == LOCAL_CRATE { self.cnum } else { self.cnum_map[cnum] } } - fn def_kind(self, tcx: TyCtxt<'_>, item_id: DefIndex) -> DefKind { + fn def_kind(self, _tcx: TyCtxt<'_>, item_id: DefIndex) -> DefKind { self.root .tables .def_kind - .get((self, tcx), item_id) + .get(self.cdata, item_id) .unwrap_or_else(|| self.missing("def_kind", item_id)) } @@ -1048,7 +1048,7 @@ fn get_span(self, tcx: TyCtxt<'_>, index: DefIndex) -> Span { self.root .tables .def_span - .get((self, tcx), index) + .get(self.cdata, index) .unwrap_or_else(|| self.missing("def_span", index)) .decode((self, tcx)) } @@ -1101,7 +1101,7 @@ fn get_variant( }; let data = - self.root.tables.variant_data.get((self, tcx), index).unwrap().decode((self, tcx)); + self.root.tables.variant_data.get(self.cdata, index).unwrap().decode((self, tcx)); let variant_did = if adt_kind == ty::AdtKind::Enum { Some(self.local_def_id(index)) } else { None }; @@ -1141,13 +1141,13 @@ fn get_adt_def<'tcx>(self, tcx: TyCtxt<'tcx>, item_id: DefIndex) -> ty::AdtDef<' _ => bug!("get_adt_def called on a non-ADT {:?}", did), }; let repr = - self.root.tables.repr_options.get((self, tcx), item_id).unwrap().decode((self, tcx)); + self.root.tables.repr_options.get(self.cdata, item_id).unwrap().decode((self, tcx)); let mut variants: Vec<_> = if let ty::AdtKind::Enum = adt_kind { self.root .tables .module_children_non_reexports - .get((self, tcx), item_id) + .get(self.cdata, item_id) .expect("variants are not encoded for an enum") .decode((self, tcx)) .filter_map(|index| { @@ -1176,25 +1176,25 @@ fn get_visibility(self, tcx: TyCtxt<'_>, id: DefIndex) -> Visibility { self.root .tables .visibility - .get((self, tcx), id) + .get(self.cdata, id) .unwrap_or_else(|| self.missing("visibility", id)) .decode((self, tcx)) .map_id(|index| self.local_def_id(index)) } - fn get_safety(self, tcx: TyCtxt<'_>, id: DefIndex) -> Safety { - self.root.tables.safety.get((self, tcx), id) + fn get_safety(self, _tcx: TyCtxt<'_>, id: DefIndex) -> Safety { + self.root.tables.safety.get(self.cdata, id) } fn get_default_field(self, tcx: TyCtxt<'_>, id: DefIndex) -> Option { - self.root.tables.default_fields.get((self, tcx), id).map(|d| d.decode((self, tcx))) + self.root.tables.default_fields.get(self.cdata, id).map(|d| d.decode((self, tcx))) } fn get_expn_that_defined(self, tcx: TyCtxt<'_>, id: DefIndex) -> ExpnId { self.root .tables .expn_that_defined - .get((self, tcx), id) + .get(self.cdata, id) .unwrap_or_else(|| self.missing("expn_that_defined", id)) .decode((self, tcx)) } @@ -1286,12 +1286,12 @@ fn get_module_children(self, tcx: TyCtxt<'_>, id: DefIndex) -> impl Iterator impl Iterator { gen move { - let children = self.root.tables.ambig_module_children.get((self, tcx), id); + let children = self.root.tables.ambig_module_children.get(self.cdata, id); if !children.is_default() { for child in children.decode((self, tcx)) { yield child; @@ -1316,15 +1316,15 @@ fn get_ambig_module_children( } } - fn is_item_mir_available(self, tcx: TyCtxt<'_>, id: DefIndex) -> bool { - self.root.tables.optimized_mir.get((self, tcx), id).is_some() + fn is_item_mir_available(self, _tcx: TyCtxt<'_>, id: DefIndex) -> bool { + self.root.tables.optimized_mir.get(self.cdata, id).is_some() } fn get_fn_has_self_parameter(self, tcx: TyCtxt<'_>, id: DefIndex) -> bool { self.root .tables .fn_arg_idents - .get((self, tcx), id) + .get(self.cdata, id) .expect("argument names not encoded for a function") .decode((self, tcx)) .nth(0) @@ -1339,7 +1339,7 @@ fn get_associated_item_or_field_def_ids( self.root .tables .associated_item_or_field_def_ids - .get((self, tcx), id) + .get(self.cdata, id) .unwrap_or_else(|| self.missing("associated_item_or_field_def_ids", id)) .decode((self, tcx)) .map(move |child_index| self.local_def_id(child_index)) @@ -1356,7 +1356,7 @@ fn get_associated_item(self, tcx: TyCtxt<'_>, id: DefIndex) -> ty::AssocItem { }, DefKind::AssocTy => { let data = if let Some(rpitit_info) = - self.root.tables.opt_rpitit_info.get((self, tcx), id) + self.root.tables.opt_rpitit_info.get(self.cdata, id) { ty::AssocTypeData::Rpitit(rpitit_info.decode((self, tcx))) } else { @@ -1367,7 +1367,7 @@ fn get_associated_item(self, tcx: TyCtxt<'_>, id: DefIndex) -> ty::AssocItem { _ => bug!("cannot get associated-item of `{:?}`", self.def_key(id)), }; let container = - self.root.tables.assoc_container.get((self, tcx), id).unwrap().decode((self, tcx)); + self.root.tables.assoc_container.get(self.cdata, id).unwrap().decode((self, tcx)); ty::AssocItem { kind, def_id: self.local_def_id(id), container } } @@ -1379,7 +1379,7 @@ fn get_ctor(self, tcx: TyCtxt<'_>, node_id: DefIndex) -> Option<(CtorKind, DefId .root .tables .variant_data - .get((self, tcx), node_id) + .get(self.cdata, node_id) .unwrap() .decode((self, tcx)); vdata.ctor.map(|(kind, index)| (kind, self.local_def_id(index))) @@ -1392,7 +1392,7 @@ fn get_item_attrs(self, tcx: TyCtxt<'_>, id: DefIndex) -> impl Iterator, id: DefIndex) -> impl Iterator( self.root .tables .inherent_impls - .get((self, tcx), id) + .get(self.cdata, id) .decode((self, tcx)) .map(|index| self.local_def_id(index)), ) @@ -1479,7 +1479,7 @@ fn get_proc_macro_quoted_span(self, tcx: TyCtxt<'_>, index: usize) -> Span { self.root .tables .proc_macro_quoted_spans - .get((self, tcx), index) + .get(self.cdata, index) .unwrap_or_else(|| panic!("Missing proc macro quoted span: {index:?}")) .decode((self, tcx)) } @@ -1544,12 +1544,12 @@ fn exported_generic_symbols<'tcx>( fn get_macro(self, tcx: TyCtxt<'_>, id: DefIndex) -> ast::MacroDef { match self.def_kind(tcx, id) { DefKind::Macro(_) => { - let macro_rules = self.root.tables.is_macro_rules.get((self, tcx), id); + let macro_rules = self.root.tables.is_macro_rules.get(self.cdata, id); let body = self .root .tables .macro_definition - .get((self, tcx), id) + .get(self.cdata, id) .unwrap() .decode((self, tcx)); ast::MacroDef { macro_rules, body: Box::new(body), eii_declaration: None } @@ -1590,11 +1590,8 @@ fn def_path_hash_to_def_index(self, hash: DefPathHash) -> Option { fn expn_hash_to_expn_id(self, tcx: TyCtxt<'_>, index_guess: u32, hash: ExpnHash) -> ExpnId { let index_guess = ExpnIndex::from_u32(index_guess); - let old_hash = self - .root - .expn_hashes - .get((self, tcx), index_guess) - .map(|lazy| lazy.decode((self, tcx))); + let old_hash = + self.root.expn_hashes.get(self.cdata, index_guess).map(|lazy| lazy.decode((self, tcx))); let index = if old_hash == Some(hash) { // Fast path: the expn and its index is unchanged from the @@ -1611,7 +1608,7 @@ fn expn_hash_to_expn_id(self, tcx: TyCtxt<'_>, index_guess: u32, hash: ExpnHash) UnhashMap::with_capacity_and_hasher(end_id as usize, Default::default()); for i in 0..end_id { let i = ExpnIndex::from_u32(i); - if let Some(hash) = self.root.expn_hashes.get((self, tcx), i) { + if let Some(hash) = self.root.expn_hashes.get(self.cdata, i) { map.insert(hash.decode((self, tcx)), i); } } @@ -1620,7 +1617,7 @@ fn expn_hash_to_expn_id(self, tcx: TyCtxt<'_>, index_guess: u32, hash: ExpnHash) map[&hash] }; - let data = self.root.expn_data.get((self, tcx), index).unwrap().decode((self, tcx)); + let data = self.root.expn_data.get(self.cdata, index).unwrap().decode((self, tcx)); rustc_span::hygiene::register_expn_id(self.cnum, index, data, hash) } @@ -1757,7 +1754,7 @@ fn filter<'a>( let source_file_to_import = self .root .source_map - .get((self, tcx), source_file_index) + .get(self.cdata, source_file_index) .expect("missing source file") .decode((self, tcx)); @@ -1858,19 +1855,19 @@ fn filter<'a>( .clone() } - fn get_attr_flags(self, tcx: TyCtxt<'_>, index: DefIndex) -> AttrFlags { - self.root.tables.attr_flags.get((self, tcx), index) + fn get_attr_flags(self, _tcx: TyCtxt<'_>, index: DefIndex) -> AttrFlags { + self.root.tables.attr_flags.get(self.cdata, index) } fn get_intrinsic(self, tcx: TyCtxt<'_>, index: DefIndex) -> Option { - self.root.tables.intrinsic.get((self, tcx), index).map(|d| d.decode((self, tcx))) + self.root.tables.intrinsic.get(self.cdata, index).map(|d| d.decode((self, tcx))) } fn get_doc_link_resolutions(self, tcx: TyCtxt<'_>, index: DefIndex) -> DocLinkResMap { self.root .tables .doc_link_resolutions - .get((self, tcx), index) + .get(self.cdata, index) .expect("no resolutions for a doc link") .decode((self, tcx)) } @@ -1883,7 +1880,7 @@ fn get_doc_link_traits_in_scope( self.root .tables .doc_link_traits_in_scope - .get((self, tcx), index) + .get(self.cdata, index) .expect("no traits in scope for a doc link") .decode((self, tcx)) } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index f9f746188f23..125c92efc8a8 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -100,7 +100,7 @@ macro_rules! provide_one { .root .tables .$name - .get(($cdata, $tcx), $def_id.index) + .get($cdata.cdata, $def_id.index) .map(|lazy| lazy.decode(($cdata, $tcx))) .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } @@ -109,7 +109,7 @@ macro_rules! provide_one { ($tcx:ident, $def_id:ident, $other:ident, $cdata:ident, $name:ident => { table_defaulted_array }) => { provide_one! { $tcx, $def_id, $other, $cdata, $name => { - let lazy = $cdata.root.tables.$name.get(($cdata, $tcx), $def_id.index); + let lazy = $cdata.root.tables.$name.get($cdata.cdata, $def_id.index); let value = if lazy.is_default() { &[] as &[_] } else { @@ -127,7 +127,7 @@ macro_rules! provide_one { .root .tables .$name - .get(($cdata, $tcx), $def_id.index) + .get($cdata.cdata, $def_id.index) .process_decoded($tcx, || panic!("{:?} does not have a {:?}", $def_id, stringify!($name))) } } @@ -264,7 +264,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { .root .tables .coerce_unsized_info - .get((cdata, tcx), def_id.index) + .get(cdata.cdata, def_id.index) .map(|lazy| lazy.decode((cdata, tcx))) .process_decoded(tcx, || panic!("{def_id:?} does not have coerce_unsized_info"))) } mir_const_qualif => { table } @@ -280,7 +280,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { .root .tables .eval_static_initializer - .get((cdata, tcx), def_id.index) + .get(cdata.cdata, def_id.index) .map(|lazy| lazy.decode((cdata, tcx))) .unwrap_or_else(|| panic!("{def_id:?} does not have eval_static_initializer"))) } @@ -293,7 +293,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { .root .tables .deduced_param_attrs - .get((cdata, tcx), def_id.index) + .get(cdata.cdata, def_id.index) .map(|lazy| { &*tcx.arena.alloc_from_iter(lazy.decode((cdata, tcx))) }) @@ -306,7 +306,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { .root .tables .trait_impl_trait_tys - .get((cdata, tcx), def_id.index) + .get(cdata.cdata, def_id.index) .map(|lazy| lazy.decode((cdata, tcx))) .process_decoded(tcx, || panic!("{def_id:?} does not have trait_impl_trait_tys"))) } From f0ebd66eaa9e9742e872eb8044d93aec8081d3cf Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 21 Apr 2026 21:08:19 +1000 Subject: [PATCH 36/55] Remove unused `tcx` params. They are unused because of the previous commit. --- compiler/rustc_metadata/src/rmeta/decoder.rs | 22 +++++++++---------- .../src/rmeta/decoder/cstore_impl.rs | 10 ++++----- .../rustc_resolve/src/build_reduced_graph.rs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 847a6428b30e..181dbfa126d7 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1036,7 +1036,7 @@ pub(super) fn map_encoded_cnum_to_current(self, cnum: CrateNum) -> CrateNum { if cnum == LOCAL_CRATE { self.cnum } else { self.cnum_map[cnum] } } - fn def_kind(self, _tcx: TyCtxt<'_>, item_id: DefIndex) -> DefKind { + fn def_kind(self, item_id: DefIndex) -> DefKind { self.root .tables .def_kind @@ -1119,7 +1119,7 @@ fn get_variant( did, name: self.item_name(did.index), vis: self.get_visibility(tcx, did.index), - safety: self.get_safety(tcx, did.index), + safety: self.get_safety(did.index), value: self.get_default_field(tcx, did.index), }) .collect(), @@ -1131,7 +1131,7 @@ fn get_variant( } fn get_adt_def<'tcx>(self, tcx: TyCtxt<'tcx>, item_id: DefIndex) -> ty::AdtDef<'tcx> { - let kind = self.def_kind(tcx, item_id); + let kind = self.def_kind(item_id); let did = self.local_def_id(item_id); let adt_kind = match kind { @@ -1151,7 +1151,7 @@ fn get_adt_def<'tcx>(self, tcx: TyCtxt<'tcx>, item_id: DefIndex) -> ty::AdtDef<' .expect("variants are not encoded for an enum") .decode((self, tcx)) .filter_map(|index| { - let kind = self.def_kind(tcx, index); + let kind = self.def_kind(index); match kind { DefKind::Ctor(..) => None, _ => Some(self.get_variant(tcx, kind, index, did)), @@ -1182,7 +1182,7 @@ fn get_visibility(self, tcx: TyCtxt<'_>, id: DefIndex) -> Visibility { .map_id(|index| self.local_def_id(index)) } - fn get_safety(self, _tcx: TyCtxt<'_>, id: DefIndex) -> Safety { + fn get_safety(self, id: DefIndex) -> Safety { self.root.tables.safety.get(self.cdata, id) } @@ -1263,7 +1263,7 @@ fn get_diagnostic_items(self, tcx: TyCtxt<'_>) -> DiagnosticItems { fn get_mod_child(self, tcx: TyCtxt<'_>, id: DefIndex) -> ModChild { let ident = self.item_ident(tcx, id); - let res = Res::Def(self.def_kind(tcx, id), self.local_def_id(id)); + let res = Res::Def(self.def_kind(id), self.local_def_id(id)); let vis = self.get_visibility(tcx, id); ModChild { ident, res, vis, reexport_chain: Default::default() } @@ -1316,7 +1316,7 @@ fn get_ambig_module_children( } } - fn is_item_mir_available(self, _tcx: TyCtxt<'_>, id: DefIndex) -> bool { + fn is_item_mir_available(self, id: DefIndex) -> bool { self.root.tables.optimized_mir.get(self.cdata, id).is_some() } @@ -1346,7 +1346,7 @@ fn get_associated_item_or_field_def_ids( } fn get_associated_item(self, tcx: TyCtxt<'_>, id: DefIndex) -> ty::AssocItem { - let kind = match self.def_kind(tcx, id) { + let kind = match self.def_kind(id) { DefKind::AssocConst { is_type_const } => { ty::AssocKind::Const { name: self.item_name(id), is_type_const } } @@ -1373,7 +1373,7 @@ fn get_associated_item(self, tcx: TyCtxt<'_>, id: DefIndex) -> ty::AssocItem { } fn get_ctor(self, tcx: TyCtxt<'_>, node_id: DefIndex) -> Option<(CtorKind, DefId)> { - match self.def_kind(tcx, node_id) { + match self.def_kind(node_id) { DefKind::Struct | DefKind::Variant => { let vdata = self .root @@ -1542,7 +1542,7 @@ fn exported_generic_symbols<'tcx>( } fn get_macro(self, tcx: TyCtxt<'_>, id: DefIndex) -> ast::MacroDef { - match self.def_kind(tcx, id) { + match self.def_kind(id) { DefKind::Macro(_) => { let macro_rules = self.root.tables.is_macro_rules.get(self.cdata, id); let body = self @@ -1855,7 +1855,7 @@ fn filter<'a>( .clone() } - fn get_attr_flags(self, _tcx: TyCtxt<'_>, index: DefIndex) -> AttrFlags { + fn get_attr_flags(self, index: DefIndex) -> AttrFlags { self.root.tables.attr_flags.get(self.cdata, index) } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 125c92efc8a8..c82bed7761aa 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -253,7 +253,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { lookup_default_body_stability => { table } lookup_deprecation_entry => { table } params_in_repr => { table } - def_kind => { cdata.def_kind(tcx, def_id.index) } + def_kind => { cdata.def_kind(def_id.index) } impl_parent => { table } defaultness => { table_direct } constness => { table_direct } @@ -323,7 +323,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { associated_item => { cdata.get_associated_item(tcx, def_id.index) } inherent_impls => { cdata.get_inherent_implementations_for_type(tcx, def_id.index) } attrs_for_def => { tcx.arena.alloc_from_iter(cdata.get_item_attrs(tcx, def_id.index)) } - is_mir_available => { cdata.is_item_mir_available(tcx, def_id.index) } + is_mir_available => { cdata.is_item_mir_available(def_id.index) } cross_crate_inlinable => { table_direct } dylib_dependency_formats => { cdata.get_dylib_dependency_formats(tcx) } @@ -411,7 +411,7 @@ fn into_args(self) -> (DefId, SimplifiedType) { crate_extern_paths => { cdata.source().paths().cloned().collect() } expn_that_defined => { cdata.get_expn_that_defined(tcx, def_id.index) } default_field => { cdata.get_default_field(tcx, def_id.index) } - is_doc_hidden => { cdata.get_attr_flags(tcx,def_id.index).contains(AttrFlags::IS_DOC_HIDDEN) } + is_doc_hidden => { cdata.get_attr_flags(def_id.index).contains(AttrFlags::IS_DOC_HIDDEN) } doc_link_resolutions => { tcx.arena.alloc(cdata.get_doc_link_resolutions(tcx, def_id.index)) } doc_link_traits_in_scope => { tcx.arena.alloc_from_iter(cdata.get_doc_link_traits_in_scope(tcx, def_id.index)) @@ -613,8 +613,8 @@ pub fn def_span_untracked(&self, tcx: TyCtxt<'_>, def_id: DefId) -> Span { self.get_crate_data(def_id.krate).get_span(tcx, def_id.index) } - pub fn def_kind_untracked(&self, tcx: TyCtxt<'_>, def: DefId) -> DefKind { - self.get_crate_data(def.krate).def_kind(tcx, def.index) + pub fn def_kind_untracked(&self, def: DefId) -> DefKind { + self.get_crate_data(def.krate).def_kind(def.index) } pub fn expn_that_defined_untracked(&self, tcx: TyCtxt<'_>, def_id: DefId) -> ExpnId { diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index a2231d8d3b0f..26e80b6fc5de 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -157,7 +157,7 @@ pub(crate) fn get_module(&self, def_id: DefId) -> Option> { } // Query `def_kind` is not used because query system overhead is too expensive here. - let def_kind = self.cstore().def_kind_untracked(self.tcx, def_id); + let def_kind = self.cstore().def_kind_untracked(def_id); if def_kind.is_module_like() { let parent = self.tcx.opt_parent(def_id).map(|parent_id| { self.get_nearest_non_block_module(parent_id).expect_extern() From 5149a31515f2bb1ae9c1923acdc80b82531ad52b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 22 Apr 2026 00:09:41 +0200 Subject: [PATCH 37/55] change `llvm.ptrmask` argument to `isize` we were saying that the type is i32, but would often provide an i64. That never failed so far, but starts failing (like, crashing LLVM) when working with 128-bit values that are 16-byte aligned. So, we may as well use the more robust approach now. --- compiler/rustc_codegen_llvm/src/va_arg.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index 931f8bae1acd..be1733086c83 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -29,10 +29,12 @@ fn round_pointer_up_to_alignment<'ll>( align: Align, ) -> &'ll Value { let ptr = bx.inbounds_ptradd(addr, bx.const_i32(align.bytes() as i32 - 1)); + let pointer_width = bx.tcx().sess.target.pointer_width; + let mask = align.bytes().wrapping_neg() & (u64::MAX >> (64 - pointer_width)); bx.call_intrinsic( "llvm.ptrmask", - &[bx.type_ptr(), bx.type_i32()], - &[ptr, bx.const_int(bx.isize_ty, -(align.bytes() as isize) as i64)], + &[bx.type_ptr(), bx.type_isize()], + &[ptr, bx.const_usize(mask)], ) } From 3bb7d7d8a4970064d8866bc04a86238b5d8e8753 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Wed, 22 Apr 2026 10:00:53 +0000 Subject: [PATCH 38/55] Prepare for merging from rust-lang/rust This updates the rust-version file to cf1817bc6ecd2d14ca492247c804bad31948dd56. --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 97c3c27262f8..1175e3372ab6 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -84c11900724736a2b9afac4822031d78a9ed8e86 +cf1817bc6ecd2d14ca492247c804bad31948dd56 From 24b1634898c25b8ff58def34c5304bda6eea715a Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 22 Apr 2026 12:08:58 +0200 Subject: [PATCH 39/55] BinOpAssign always returns unit --- compiler/rustc_hir_typeck/src/op.rs | 13 ++++-------- .../let-chains-assign-add-incorrect.fixed | 4 ++++ .../parser/let-chains-assign-add-incorrect.rs | 4 ++++ .../let-chains-assign-add-incorrect.stderr | 20 +++++++++++++++---- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 8b08c7b68b78..334dfa3a2ab3 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -34,19 +34,14 @@ pub(crate) fn check_expr_assign_op( rhs: &'tcx Expr<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx> { - let (lhs_ty, rhs_ty, return_ty) = + let (lhs_ty, rhs_ty, _return_ty) = self.check_overloaded_binop(expr, lhs, rhs, Op::AssignOp(op), expected); let category = BinOpCategory::from(op.node); - let ty = if !lhs_ty.is_ty_var() - && !rhs_ty.is_ty_var() - && is_builtin_binop(lhs_ty, rhs_ty, category) + if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, category) { self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, category); - self.tcx.types.unit - } else { - return_ty - }; + } self.check_lhs_assignable(lhs, E0067, op.span, |err| { if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { @@ -86,7 +81,7 @@ pub(crate) fn check_expr_assign_op( } }); - ty + self.tcx.types.unit } /// Checks a potentially overloaded binary operator. diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.fixed b/tests/ui/parser/let-chains-assign-add-incorrect.fixed index b1abce8bd2df..469c78bb6939 100644 --- a/tests/ui/parser/let-chains-assign-add-incorrect.fixed +++ b/tests/ui/parser/let-chains-assign-add-incorrect.fixed @@ -15,6 +15,8 @@ fn test_where_left_is_not_let() { //~| ERROR binary assignment operation `+=` cannot be used in a let chain //~| NOTE cannot use `+=` in a let chain //~| HELP you might have meant to compare with `==` instead of assigning with `+=` + //~| ERROR mismatched types + //~| NOTE expected `bool`, found `()` } fn test_where_left_is_let() { @@ -28,6 +30,8 @@ fn test_where_left_is_let() { //~| ERROR binary assignment operation `+=` cannot be used in a let chain //~| NOTE cannot use `+=` in a let chain //~| HELP you might have meant to compare with `==` instead of assigning with `+=` + //~| ERROR mismatched types + //~| NOTE expected `bool`, found `()` } fn main() { diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.rs b/tests/ui/parser/let-chains-assign-add-incorrect.rs index 3b2e5e93d91a..6bdb4b648eba 100644 --- a/tests/ui/parser/let-chains-assign-add-incorrect.rs +++ b/tests/ui/parser/let-chains-assign-add-incorrect.rs @@ -15,6 +15,8 @@ fn test_where_left_is_not_let() { //~| ERROR binary assignment operation `+=` cannot be used in a let chain //~| NOTE cannot use `+=` in a let chain //~| HELP you might have meant to compare with `==` instead of assigning with `+=` + //~| ERROR mismatched types + //~| NOTE expected `bool`, found `()` } fn test_where_left_is_let() { @@ -28,6 +30,8 @@ fn test_where_left_is_let() { //~| ERROR binary assignment operation `+=` cannot be used in a let chain //~| NOTE cannot use `+=` in a let chain //~| HELP you might have meant to compare with `==` instead of assigning with `+=` + //~| ERROR mismatched types + //~| NOTE expected `bool`, found `()` } fn main() { diff --git a/tests/ui/parser/let-chains-assign-add-incorrect.stderr b/tests/ui/parser/let-chains-assign-add-incorrect.stderr index dea8e6a1982b..02926965edd0 100644 --- a/tests/ui/parser/let-chains-assign-add-incorrect.stderr +++ b/tests/ui/parser/let-chains-assign-add-incorrect.stderr @@ -7,7 +7,7 @@ LL | if let _ = 1 && true && y += 2 {}; = note: only supported directly in conditions of `if` and `while` expressions error: expected expression, found `let` statement - --> $DIR/let-chains-assign-add-incorrect.rs:22:8 + --> $DIR/let-chains-assign-add-incorrect.rs:24:8 | LL | if let _ = 1 && y += 2 {}; | ^^^^^^^^^ @@ -37,13 +37,19 @@ LL + if let _ = 1 && true && y == 2 {}; | error[E0308]: mismatched types - --> $DIR/let-chains-assign-add-incorrect.rs:22:21 + --> $DIR/let-chains-assign-add-incorrect.rs:8:8 + | +LL | if let _ = 1 && true && y += 2 {}; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/let-chains-assign-add-incorrect.rs:24:21 | LL | if let _ = 1 && y += 2 {}; | ^ expected `bool`, found integer error: binary assignment operation `+=` cannot be used in a let chain - --> $DIR/let-chains-assign-add-incorrect.rs:22:23 + --> $DIR/let-chains-assign-add-incorrect.rs:24:23 | LL | if let _ = 1 && y += 2 {}; | -------------- ^^ cannot use `+=` in a let chain @@ -56,6 +62,12 @@ LL - if let _ = 1 && y += 2 {}; LL + if let _ = 1 && y == 2 {}; | -error: aborting due to 6 previous errors +error[E0308]: mismatched types + --> $DIR/let-chains-assign-add-incorrect.rs:24:8 + | +LL | if let _ = 1 && y += 2 {}; + | ^^^^^^^^^^^^^^^^^^^ expected `bool`, found `()` + +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0308`. From 248cfe7312e249ebae51395d040bba9a3fe231f9 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:27:16 +0200 Subject: [PATCH 40/55] sembr src/rustc-driver/external-rustc-drivers.md --- .../src/rustc-driver/external-rustc-drivers.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index a243204b6ba7..619026baaecc 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -55,7 +55,8 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co #### Configuration Steps -1. Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. +1. Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. + For VS Code, add to `rust_analyzer_settings.json`: ```json { @@ -69,7 +70,7 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co rustc_private = true ``` -This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. +This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. ### Getting Nightly Documentation for `rustc_private` @@ -86,7 +87,9 @@ rustup doc --rustc-docs #### Older Nightlies -If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. The only way to do this is to generate the documentation locally. For example, to get documentation for `nightly-2025-11-08`: +If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. +The only way to do this is to generate the documentation locally. +For example, to get documentation for `nightly-2025-11-08`: Get the Git commit hash for that nightly: @@ -95,7 +98,8 @@ rustup toolchain install nightly-2025-11-08 rustc +nightly-2025-11-08 --version --verbose ``` -The output will include a `commit-hash` line identifying the exact source revision. Check out `rust-lang/rust` at that commit, then follow the steps in [compiler documentation](../building/compiler-documenting.md). +The output will include a `commit-hash` line identifying the exact source revision. +Check out `rust-lang/rust` at that commit, then follow the steps in [compiler documentation](../building/compiler-documenting.md). ### Additional Resources From 5f0877e21b492fe00c60324c2bcdf27120fc5304 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:28:46 +0200 Subject: [PATCH 41/55] whitespace --- .../rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 619026baaecc..6e7eaf3397dd 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -56,7 +56,7 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co #### Configuration Steps 1. Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. - + For VS Code, add to `rust_analyzer_settings.json`: ```json { From 4d31601bef4dda8b11bd6e492c3ded17b49e4bed Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:31:32 +0200 Subject: [PATCH 42/55] title case --- .../src/rustc-driver/external-rustc-drivers.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md index 6e7eaf3397dd..23e0f4e08c48 100644 --- a/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md +++ b/src/doc/rustc-dev-guide/src/rustc-driver/external-rustc-drivers.md @@ -6,14 +6,14 @@ The `rustc_private` feature allows external crates to use compiler internals. -### Using `rustc_private` with Official Toolchains +### Using `rustc_private` with official toolchains When using the `rustc_private` feature with official Rust toolchains distributed via rustup, you need to install two additional components: 1. **`rustc-dev`**: Provides compiler libraries 2. **`llvm-tools`**: Provides LLVM libraries required for linking -#### Installation Steps +#### Installation steps Install both components using rustup: @@ -21,7 +21,7 @@ Install both components using rustup: rustup component add rustc-dev llvm-tools ``` -#### Common Error +#### Common error Without the `llvm-tools` component, you'll encounter linking errors like: @@ -40,7 +40,7 @@ For custom-built toolchains or environments not using rustup, additional configu - LLVM libraries must be available in your system's library search paths - The LLVM version must match the one used to build your Rust toolchain -#### Troubleshooting Steps +#### Troubleshooting steps 1. Verify LLVM is installed and accessible 2. Ensure that library paths are set: @@ -53,7 +53,7 @@ For custom-built toolchains or environments not using rustup, additional configu When developing out-of-tree projects that use `rustc_private` crates, you can configure `rust-analyzer` to recognize these crates. -#### Configuration Steps +#### Configuration steps 1. Configure `rust-analyzer.rustc.source` to `"discover"` in your editor settings. @@ -72,9 +72,9 @@ When developing out-of-tree projects that use `rustc_private` crates, you can co This configuration allows `rust-analyzer` to properly recognize and provide IDE support for `rustc_private` crates in out-of-tree projects. -### Getting Nightly Documentation for `rustc_private` +### Getting nightly documentation for `rustc_private` -#### Latest Nightly +#### Latest nightly For the latest nightly, you can install the `rustc-docs` component and open it directly in your browser: @@ -85,7 +85,7 @@ rustup doc --rustc-docs > Note: The `rustc-docs` component is only available for recent nightly toolchains and may not be present for every nightly date. It was first introduced in [PR #75560](https://github.com/rust-lang/rust/pull/75560) (August 2020). -#### Older Nightlies +#### Older nightlies If you depend on compiler internals from an older nightly, you may want to refer to the internal documentation from that particular nightly. The only way to do this is to generate the documentation locally. @@ -102,7 +102,7 @@ The output will include a `commit-hash` line identifying the exact source revisi Check out `rust-lang/rust` at that commit, then follow the steps in [compiler documentation](../building/compiler-documenting.md). -### Additional Resources +### Additional resources - [GitHub Issue #137421] explains that `rustc_private` linker failures often occur because `llvm-tools` is not installed From 0be2aa6e27f0866e12c6053764fb6400b4e6b400 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:33:55 +0200 Subject: [PATCH 43/55] sembr src/tests/ci.md --- src/doc/rustc-dev-guide/src/tests/ci.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index 25869c533212..8b92f3c2aae5 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -462,8 +462,8 @@ These are some useful panels from the dashboard: - Pipeline duration: check how long the auto builds take to run. - Top slowest jobs: check which jobs are taking the longest to run. -- Change in median job duration: check what jobs are slowest than before. Useful - to detect regressions. +- Change in median job duration: check what jobs are slowest than before. + Useful to detect regressions. - Top failed jobs: check which jobs are failing the most. To learn more about the dashboard, see the [Datadog CI docs]. From 6ff11cfdde17ac73a8cca0c3bc69f3ac760bc1a8 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:35:20 +0200 Subject: [PATCH 44/55] make sentence feel more complete --- src/doc/rustc-dev-guide/src/tests/ci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index 8b92f3c2aae5..8f06d69d8cff 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -463,7 +463,7 @@ These are some useful panels from the dashboard: - Pipeline duration: check how long the auto builds take to run. - Top slowest jobs: check which jobs are taking the longest to run. - Change in median job duration: check what jobs are slowest than before. - Useful to detect regressions. + This is useful for detecting regressions. - Top failed jobs: check which jobs are failing the most. To learn more about the dashboard, see the [Datadog CI docs]. From a3a299256a7e181171543fd456965a1b08e59e4b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:36:35 +0200 Subject: [PATCH 45/55] sembr src/tests/compiletest.md --- src/doc/rustc-dev-guide/src/tests/compiletest.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index beef777ccc33..80078e27e249 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -457,7 +457,8 @@ as they must be compilable by a stage 0 rustc that may be a beta or even stable By default, run-make tests print each subprocess command and its stdout/stderr. When running with `--no-capture` on `panic=abort` test suites (such as `cg_clif`), -this can flood the terminal. Omit `--verbose-run-make-subprocess-output` to +this can flood the terminal. +Omit `--verbose-run-make-subprocess-output` to suppress this output for passing tests — failing tests always print regardless: ```bash From 3be0e76c36a7f91d21481891678e255cc37c76b3 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 22 Apr 2026 12:27:12 +0200 Subject: [PATCH 46/55] Update a very outdated comment --- compiler/rustc_hir_typeck/src/op.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 334dfa3a2ab3..1e9986fa761c 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -1306,21 +1306,16 @@ fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> { } /// Returns `true` if this is a built-in arithmetic operation (e.g., -/// u32 + u32, i16x4 == i16x4) and false if these types would have to be -/// overloaded to be legal. There are two reasons that we distinguish +/// u32 + u32) and false if these types would have to be +/// overloaded to be legal. The reason that we distinguish /// builtin operations from overloaded ones (vs trying to drive /// everything uniformly through the trait system and intrinsics or -/// something like that): +/// something like that) is that builtin operations can trivially +/// be evaluated in constants on stable, but the traits and their +/// impls for these primitive types. /// -/// 1. Builtin operations can trivially be evaluated in constants. -/// 2. For comparison operators applied to SIMD types the result is -/// not of type `bool`. For example, `i16x4 == i16x4` yields a -/// type like `i16x4`. This means that the overloaded trait -/// `PartialEq` is not applicable. -/// -/// Reason #2 is the killer. I tried for a while to always use -/// overloaded logic and just check the types in constants/codegen after -/// the fact, and it worked fine, except for SIMD types. -nmatsakis +/// FIXME(const_trait_impls): once the traits and their impls are const stable +/// remove this function and the builtin-specific checks. fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool { // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. // (See https://github.com/rust-lang/rust/issues/57447.) From c392defd21dfadc5dfd70900354293eae175246b Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 12:48:54 +0200 Subject: [PATCH 47/55] sembr src/stability.md --- src/doc/rustc-dev-guide/src/stability.md | 92 +++++++++++++++--------- 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/stability.md b/src/doc/rustc-dev-guide/src/stability.md index 93c59675d893..43788b6146bc 100644 --- a/src/doc/rustc-dev-guide/src/stability.md +++ b/src/doc/rustc-dev-guide/src/stability.md @@ -3,31 +3,36 @@ This section is about the stability attributes and schemes that allow stable APIs to use unstable APIs internally in the rustc standard library. -**NOTE**: this section is for *library* features, not *language* features. For instructions on +**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")]` -attribute explicitly marks an item as unstable. Items that are marked as +attribute explicitly marks an item as unstable. +Items that are marked as "unstable" cannot be used without a corresponding `#![feature]` attribute on -the crate, even on a nightly compiler. This restriction only applies across -crate boundaries, unstable items may be used within the crate that defines -them. +the crate, even on a nightly compiler. +This restriction only applies across +crate boundaries, unstable items may be used within the crate that defines them. -The `issue` field specifies the associated GitHub [issue number]. This field is -required and all unstable features should have an associated tracking issue. In -rare cases where there is no sensible value `issue = "none"` is used. +The `issue` field specifies the associated GitHub [issue number]. +This field is +required and all unstable features should have an associated tracking issue. +In rare cases where there is no sensible value `issue = "none"` is used. The `unstable` attribute infects all sub-items, where the attribute doesn't -have to be reapplied. So if you apply this to a module, all items in the module -will be unstable. +have to be reapplied. +So if you apply this to a module, all items in the module will be unstable. If you rename a feature, you can add `old_name = "old_name"` to produce a useful error message. You can make specific sub-items stable by using the `#[stable]` attribute on -them. The stability scheme works similarly to how `pub` works. You can have +them. +The stability scheme works similarly to how `pub` works. +You can have public functions of nonpublic modules and you can have stable functions in unstable modules or vice versa. @@ -35,13 +40,15 @@ Previously, due to a [rustc bug], stable items inside unstable modules were available to stable code in that location. As of September 2024, items with [accidentally stabilized paths] are marked with the `#[rustc_allowed_through_unstable_modules]` attribute -to prevent code dependent on those paths from breaking. Do *not* add this attribute +to prevent code dependent on those paths from breaking. +Do *not* add this attribute to any more items unless that is needed to avoid breaking changes. The `unstable` attribute may also have the `soft` value, which makes it a -future-incompatible deny-by-default lint instead of a hard error. This is used -by the `bench` attribute which was accidentally accepted in the past. This -prevents breaking dependencies by leveraging Cargo's lint capping. +future-incompatible deny-by-default lint instead of a hard error. +This is used +by the `bench` attribute which was accidentally accepted in the past. +This prevents breaking dependencies by leveraging Cargo's lint capping. [issue number]: https://github.com/rust-lang/rust/issues [rustc bug]: https://github.com/rust-lang/rust/issues/15702 @@ -49,22 +56,26 @@ prevents breaking dependencies by leveraging Cargo's lint capping. ## stable The `#[stable(feature = "foo", since = "1.420.69")]` attribute explicitly -marks an item as stabilized. Note that stable functions may use unstable things in their body. +marks an item as stabilized. +Note that stable functions may use unstable things in their body. ## rustc_const_unstable The `#[rustc_const_unstable(feature = "foo", issue = "1234", reason = "lorem -ipsum")]` has the same interface as the `unstable` attribute. It is used to mark -`const fn` as having their constness be unstable. This is only needed in rare cases: +ipsum")]` has the same interface as the `unstable` attribute. +It is used to mark `const fn` as having their constness be unstable. +This is only needed in rare cases: - If a `const fn` makes use of unstable language features or intrinsics. (The compiler will tell you to add the attribute if you run into this.) - If a `const fn` is `#[stable]` but not yet intended to be const-stable. - To change the feature gate that is required to call a const-unstable intrinsic. Const-stability differs from regular stability in that it is *recursive*: a -`#[rustc_const_unstable(...)]` function cannot even be indirectly called from stable code. This is +`#[rustc_const_unstable(...)]` function cannot even be indirectly called from stable code. +This is to avoid accidentally leaking unstable compiler implementation artifacts to stable code or locking -us into the accidental quirks of an incomplete implementation. See the rustc_const_stable_indirect +us into the accidental quirks of an incomplete implementation. +See the rustc_const_stable_indirect and rustc_allow_const_fn_unstable attributes below for how to fine-tune this check. ## rustc_const_stable @@ -75,7 +86,8 @@ a `const fn` as having its constness be `stable`. ## rustc_const_stable_indirect The `#[rustc_const_stable_indirect]` attribute can be added to a `#[rustc_const_unstable(...)]` -function to make it callable from `#[rustc_const_stable(...)]` functions. This indicates that the +function to make it callable from `#[rustc_const_stable(...)]` functions. +This indicates that the function is ready for stable in terms of its implementation (i.e., it doesn't use any unstable compiler features); the only reason it is not const-stable yet are API concerns. @@ -105,7 +117,8 @@ To stabilize a feature, follow these steps: 1. Ask a **@T-libs-api** member to start an FCP on the tracking issue and wait for the FCP to complete (with `disposition-merge`). 2. Change `#[unstable(...)]` to `#[stable(since = "CURRENT_RUSTC_VERSION")]`. -3. Remove `#![feature(...)]` from any test or doc-test for this API. If the feature is used in the +3. Remove `#![feature(...)]` from any test or doc-test for this API. + If the feature is used in the compiler or tools, remove it from there as well. 4. If this is a `const fn`, add `#[rustc_const_stable(since = "CURRENT_RUSTC_VERSION")]`. Alternatively, if this is not supposed to be const-stabilized yet, @@ -121,14 +134,15 @@ and the associated ## allow_internal_unstable -Macros and compiler desugarings expose their bodies to the call -site. To work around not being able to use unstable things in the standard +Macros and compiler desugarings expose their bodies to the call site. +To work around not being able to use unstable things in the standard library's macros, there's the `#[allow_internal_unstable(feature1, feature2)]` attribute that allows the given features to be used in stable macros. Note that if a macro is used in const context and generates a call to a `#[rustc_const_unstable(...)]` function, that will *still* be rejected even with -`allow_internal_unstable`. Add `#[rustc_const_stable_indirect]` to the function to ensure the macro +`allow_internal_unstable`. +Add `#[rustc_const_stable_indirect]` to the function to ensure the macro cannot accidentally bypass the recursive const stability checks. ## rustc_allow_const_fn_unstable @@ -138,12 +152,14 @@ indirectly. However, sometimes we do know that a feature will get stabilized, just not when, or there is a stable (but e.g. runtime-slow) workaround, so we could always fall back to some stable version if we -scrapped the unstable feature. In those cases, the `[rustc_allow_const_fn_unstable(feature1, +scrapped the unstable feature. +In those cases, the `[rustc_allow_const_fn_unstable(feature1, feature2)]` attribute can be used to allow some unstable features in the body of a stable (or indirectly stable) `const fn`. You also need to take care to uphold the `const fn` invariant that calling it at runtime and -compile-time needs to behave the same (see also [this blog post][blog]). This means that you +compile-time needs to behave the same (see also [this blog post][blog]). +This means that you may not create a `const fn` that e.g. transmutes a memory address to an integer, because the addresses of things are nondeterministic and often unknown at compile-time. @@ -159,7 +175,8 @@ Any crate that uses the `stable` or `unstable` attributes must include the ## deprecated Deprecations in the standard library are nearly identical to deprecations in -user code. When `#[deprecated]` is used on an item, it must also have a `stable` +user code. +When `#[deprecated]` is used on an item, it must also have a `stable` or `unstable `attribute. `deprecated` has the following form: @@ -172,20 +189,26 @@ or `unstable `attribute. )] ``` -The `suggestion` field is optional. If given, it should be a string that can be -used as a machine-applicable suggestion to correct the warning. This is +The `suggestion` field is optional. +If given, it should be a string that can be +used as a machine-applicable suggestion to correct the warning. +This is typically used when the identifier is renamed, but no other significant changes -are necessary. When the `suggestion` field is used, you need to have +are necessary. +When the `suggestion` field is used, you need to have `#![feature(deprecated_suggestion)]` at the crate root. Another difference from user code is that the `since` field is actually checked -against the current version of `rustc`. If `since` is in a future version, then +against the current version of `rustc`. +If `since` is in a future version, then 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. +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` @@ -193,7 +216,8 @@ Currently, the items that can be annotated with `#[unstable_feature_bound]` are: - trait ## renamed and removed features -Unstable features can get renamed and removed. If you rename a feature, you can add `old_name = "old_name"` to the `#[unstable]` attribute. +Unstable features can get renamed and removed. +If you rename a feature, you can add `old_name = "old_name"` to the `#[unstable]` attribute. If you remove a feature, the `#!unstable_removed(feature = "foo", reason = "brief description", link = "link", since = "1.90.0")` attribute should be used to produce a good error message for users of the removed feature. From 8d75f0cbfce45c7d258297f223e18c8ea9679131 Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 19 Apr 2026 20:57:37 +0800 Subject: [PATCH 48/55] add on_unmatch_args --- .../src/attributes/diagnostic/mod.rs | 10 +++- .../attributes/diagnostic/on_unmatch_args.rs | 58 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/errors.rs | 4 ++ compiler/rustc_expand/src/mbe/diagnostics.rs | 30 +++++++++- compiler/rustc_expand/src/mbe/macro_rules.rs | 37 ++++++++++-- compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 7 +++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_lint/src/early/diagnostics.rs | 1 - compiler/rustc_passes/src/check_attr.rs | 19 +++++- compiler/rustc_passes/src/errors.rs | 7 +++ compiler/rustc_resolve/src/macros.rs | 1 + compiler/rustc_span/src/symbol.rs | 2 + library/core/src/field.rs | 3 + library/core/src/lib.rs | 1 + library/core/src/mem/mod.rs | 3 + .../diagnostic-on-unmatch-args.md | 53 +++++++++++++++++ .../on_unmatch_args/auxiliary/other.rs | 12 ++++ .../error_is_shown_in_downstream_crates.rs | 12 ++++ ...error_is_shown_in_downstream_crates.stderr | 16 +++++ .../on_unmatch_args/message_and_label.rs | 21 +++++++ .../on_unmatch_args/message_and_label.stderr | 19 ++++++ .../on_unmatch_args/notes_on_extra_args.rs | 21 +++++++ .../notes_on_extra_args.stderr | 19 ++++++ .../on_unmatch_args/on_unmatch_args.rs | 19 ++++++ .../on_unmatch_args/on_unmatch_args.stderr | 19 ++++++ .../other_match_macro_error.rs | 14 +++++ .../other_match_macro_error.stderr | 18 ++++++ .../report_warning_on_invalid_formats.rs | 14 +++++ .../report_warning_on_invalid_formats.stderr | 11 ++++ ...ort_warning_on_invalid_meta_item_syntax.rs | 12 ++++ ...warning_on_invalid_meta_item_syntax.stderr | 11 ++++ .../report_warning_on_missing_options.rs | 12 ++++ .../report_warning_on_missing_options.stderr | 11 ++++ .../report_warning_on_non_macro.rs | 10 ++++ .../report_warning_on_non_macro.stderr | 10 ++++ .../report_warning_on_unknown_options.rs | 12 ++++ .../report_warning_on_unknown_options.stderr | 11 ++++ ...feature-gate-diagnostic-on-unmatch-args.rs | 15 +++++ ...ure-gate-diagnostic-on-unmatch-args.stderr | 17 ++++++ .../invalid.next.stderr | 3 + .../invalid.old.stderr | 3 + tests/ui/offset-of/offset-of-arg-count.stderr | 3 + tests/ui/offset-of/offset-of-tuple.stderr | 1 + 45 files changed, 576 insertions(+), 11 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs create mode 100644 src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs create mode 100644 tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index ea5e81c3db81..32d73ff32361 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod on_move; pub(crate) mod on_unimplemented; pub(crate) mod on_unknown; +pub(crate) mod on_unmatch_args; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -38,6 +39,8 @@ pub(crate) enum Mode { DiagnosticOnMove, /// `#[diagnostic::on_unknown]` DiagnosticOnUnknown, + /// `#[diagnostic::on_unmatch_args]` + DiagnosticOnUnmatchArgs, } impl Mode { @@ -48,6 +51,7 @@ fn as_str(&self) -> &'static str { Self::DiagnosticOnConst => "diagnostic::on_const", Self::DiagnosticOnMove => "diagnostic::on_move", Self::DiagnosticOnUnknown => "diagnostic::on_unknown", + Self::DiagnosticOnUnmatchArgs => "diagnostic::on_unmatch_args", } } @@ -62,6 +66,7 @@ fn expected_options(&self) -> &'static str { Self::DiagnosticOnConst => DEFAULT, Self::DiagnosticOnMove => DEFAULT, Self::DiagnosticOnUnknown => DEFAULT, + Self::DiagnosticOnUnmatchArgs => DEFAULT, } } @@ -75,6 +80,7 @@ fn allowed_options(&self) -> &'static str { Self::DiagnosticOnConst => DEFAULT, Self::DiagnosticOnMove => DEFAULT, Self::DiagnosticOnUnknown => DEFAULT, + Self::DiagnosticOnUnmatchArgs => DEFAULT, } } } @@ -398,7 +404,9 @@ fn parse_arg( Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) { // Only `#[rustc_on_unimplemented]` can use these (Mode::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, - (Mode::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, + (Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnmatchArgs, sym::This) => { + FormatArg::This + } (Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, // Any attribute can use these (_, kw::SelfUpper) => FormatArg::SelfUpper, diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs new file mode 100644 index 000000000000..f541f631bf12 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatch_args.rs @@ -0,0 +1,58 @@ +use rustc_errors::Diagnostic; +use rustc_hir::attrs::diagnostic::Directive; +use rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; +use crate::errors::DiagnosticOnUnmatchArgsOnlyForMacros; + +#[derive(Default)] +pub(crate) struct OnUnmatchArgsParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl AttributeParser for OnUnmatchArgsParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_unmatch_args], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + if !cx.features().diagnostic_on_unmatch_args() { + return; + } + + let span = cx.attr_span; + this.span = Some(span); + + if !matches!(cx.target, Target::MacroDef) { + cx.emit_dyn_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + move |dcx, level| DiagnosticOnUnmatchArgsOnlyForMacros.into_diag(dcx, level), + span, + ); + return; + } + + let mode = Mode::DiagnosticOnUnmatchArgs; + let Some(items) = parse_list(cx, args, mode) else { return }; + + let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) else { + return; + }; + merge_directives(cx, &mut this.directive, (span, directive)); + }, + )]; + + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + if let Some(span) = self.span { + Some(AttributeKind::OnUnmatchArgs { + span, + directive: self.directive.map(|d| Box::new(d.1)), + }) + } else { + None + } + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index becdaee0f3d3..5ced5d5f975a 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -33,6 +33,7 @@ use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; use crate::attributes::diagnostic::on_unknown::*; +use crate::attributes::diagnostic::on_unmatch_args::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -159,6 +160,7 @@ mod late { OnMoveParser, OnUnimplementedParser, OnUnknownParser, + OnUnmatchArgsParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs index 96a4c473c3ae..d3e6845799e7 100644 --- a/compiler/rustc_attr_parsing/src/errors.rs +++ b/compiler/rustc_attr_parsing/src/errors.rs @@ -315,6 +315,10 @@ pub(crate) struct DiagnosticOnUnknownOnlyForImports { pub target_span: Span, } +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_unmatch_args]` can only be applied to macro definitions")] +pub(crate) struct DiagnosticOnUnmatchArgsOnlyForMacros; + #[derive(Diagnostic)] #[diag("`#[diagnostic::do_not_recommend]` can only be placed on trait implementations")] pub(crate) struct IncorrectDoNotRecommendLocation { diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index b8040c3d9c1a..4e7e51c0a43c 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -3,6 +3,7 @@ use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream::TokenStream; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, DiagMessage}; +use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_macros::Subdiagnostic; use rustc_parse::parser::{Parser, Recovery, token_descr}; use rustc_session::parse::ParseSess; @@ -32,6 +33,7 @@ pub(super) fn failed_to_match_macro( args: FailedMacro<'_>, body: &TokenStream, rules: &[MacroRule], + on_unmatch_args: Option<&Directive>, ) -> (Span, ErrorGuaranteed) { debug!("failed to match macro"); let def_head_span = if !def_span.is_dummy() && !psess.source_map().is_imported(def_span) { @@ -72,9 +74,30 @@ pub(super) fn failed_to_match_macro( }; let span = token.span.substitute_dummy(sp); + let CustomDiagnostic { + message: custom_message, label: custom_label, notes: custom_notes, .. + } = { + let macro_name = name.to_string(); + on_unmatch_args + .map(|directive| { + directive.eval( + None, + &FormatArgs { + this: macro_name.clone(), + this_sugared: macro_name, + item_context: "macro invocation", + generic_args: Vec::new(), + }, + ) + }) + .unwrap_or_default() + }; - let mut err = psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)); - err.span_label(span, label); + let mut err = match custom_message { + Some(message) => psess.dcx().struct_span_err(span, message), + None => psess.dcx().struct_span_err(span, parse_failure_msg(&token, None)), + }; + err.span_label(span, custom_label.unwrap_or_else(|| label.to_string())); if !def_head_span.is_dummy() { err.span_label(def_head_span, "when calling this macro"); } @@ -86,6 +109,9 @@ pub(super) fn failed_to_match_macro( } else { err.note(format!("while trying to match {remaining_matcher}")); } + for note in custom_notes { + err.note(note); + } if let MatcherLoc::Token { token: expected_token } = &remaining_matcher && (matches!(expected_token.kind, token::OpenInvisible(_)) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index fd5dac3cd926..eed13a13fa91 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -14,6 +14,7 @@ use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan}; use rustc_feature::Features; use rustc_hir as hir; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::def::MacroKinds; use rustc_hir::find_attr; use rustc_lint_defs::builtin::{ @@ -164,6 +165,7 @@ pub struct MacroRulesMacroExpander { node_id: NodeId, name: Ident, span: Span, + on_unmatch_args: Option, transparency: Transparency, kinds: MacroKinds, rules: Vec, @@ -194,7 +196,8 @@ pub fn expand_derive( ) -> 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 name = self.name; + let rules = &self.rules; let psess = &cx.sess.psess; if cx.trace_macros() { @@ -220,8 +223,8 @@ pub fn expand_derive( 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); + if is_defined_in_current_crate(self.node_id) { + cx.resolver.record_macro_rule_usage(self.node_id, rule_index); } Ok(tts) @@ -236,6 +239,7 @@ pub fn expand_derive( FailedMacro::Derive, body, rules, + self.on_unmatch_args.as_ref(), ); cx.macro_error_and_trace_macros_diag(); Err(guar) @@ -260,6 +264,7 @@ fn expand<'cx, 'a: 'cx>( self.transparency, input, &self.rules, + self.on_unmatch_args.as_ref(), )) } } @@ -294,6 +299,7 @@ fn expand_with_safety( args, body, &self.rules, + self.on_unmatch_args.as_ref(), ) } } @@ -355,7 +361,7 @@ fn description() -> &'static str { } /// Expands the rules based macro defined by `rules` for a given input `arg`. -#[instrument(skip(cx, transparency, arg, rules))] +#[instrument(skip(cx, transparency, arg, rules, on_unmatch_args))] fn expand_macro<'cx, 'a: 'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, @@ -365,6 +371,7 @@ fn expand_macro<'cx, 'a: 'cx>( transparency: Transparency, arg: TokenStream, rules: &'a [MacroRule], + on_unmatch_args: Option<&Directive>, ) -> Box { let psess = &cx.sess.psess; @@ -423,6 +430,7 @@ fn expand_macro<'cx, 'a: 'cx>( FailedMacro::Func, &arg, rules, + on_unmatch_args, ); cx.macro_error_and_trace_macros_diag(); DummyResult::any(span, guar) @@ -431,7 +439,7 @@ fn expand_macro<'cx, 'a: 'cx>( } /// Expands the rules based macro defined by `rules` for a given attribute `args` and `body`. -#[instrument(skip(cx, transparency, args, body, rules))] +#[instrument(skip(cx, transparency, args, body, rules, on_unmatch_args))] fn expand_macro_attr( cx: &mut ExtCtxt<'_>, sp: Span, @@ -443,6 +451,7 @@ fn expand_macro_attr( args: TokenStream, body: TokenStream, rules: &[MacroRule], + on_unmatch_args: Option<&Directive>, ) -> Result { let psess = &cx.sess.psess; // Macros defined in the current crate have a real node id, @@ -507,6 +516,7 @@ fn expand_macro_attr( FailedMacro::Attr(&args), &body, rules, + on_unmatch_args, ); cx.trace_macros_diag(); Err(guar) @@ -849,7 +859,22 @@ 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 exp = MacroRulesMacroExpander { name: ident, kinds, span, node_id, transparency, rules }; + let on_unmatch_args = find_attr!( + attrs, + OnUnmatchArgs { directive, .. } => directive.clone() + ) + .flatten() + .map(|directive| *directive); + + let exp = MacroRulesMacroExpander { + name: ident, + kinds, + span, + node_id, + on_unmatch_args, + transparency, + rules, + }; (mk_syn_ext(SyntaxExtensionKind::MacroRules(Arc::new(exp))), nrules) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 82402b3c7e31..c175c3adc2f0 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -480,6 +480,8 @@ pub fn internal(&self, feature: Symbol) -> bool { (unstable, diagnostic_on_move, "1.96.0", Some(154181)), /// Allows giving unresolved imports a custom diagnostic message (unstable, diagnostic_on_unknown, "1.96.0", Some(152900)), + /// Allows macros to customize macro argument matcher diagnostics. + (unstable, diagnostic_on_unmatch_args, "CURRENT_RUSTC_VERSION", Some(155642)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 05f398058eca..78ce2f019a39 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1208,6 +1208,13 @@ pub enum AttributeKind { directive: Option>, }, + /// Represents `#[diagnostic::on_unmatch_args]`. + OnUnmatchArgs { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index ad4d0728888b..41e466a9d16b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -80,6 +80,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate { OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, OnUnknown { .. } => Yes, + OnUnmatchArgs { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 60bfdd689609..92c474848327 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -52,7 +52,6 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { lints::MalFormedDiagnosticAttributeLint { attribute, options, span } .into_diag(dcx, level) } - AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning { FormatWarning::PositionalArgument { .. } => { lints::DisallowedPositionalArgument.into_diag(dcx, level) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 06dd8cbc089b..d11825016baf 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -200,7 +200,10 @@ fn check_attributes( self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) }, Attribute::Parsed(AttributeKind::OnUnimplemented{directive,..}) => {self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} + Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)}, + Attribute::Parsed(AttributeKind::OnUnmatchArgs { directive, .. }) => { + self.check_diagnostic_on_unmatch_args(hir_id, directive.as_deref()) + }, Attribute::Parsed(AttributeKind::OnMove { directive , .. }) => { self.check_diagnostic_on_move(hir_id, directive.as_deref()) }, @@ -559,6 +562,20 @@ fn check_diagnostic_on_const( // ...whose generics would that be, anyway? The traits' or the impls'? } + /// Checks use of generic formatting parameters in `#[diagnostic::on_unmatch_args]`. + fn check_diagnostic_on_unmatch_args(&self, hir_id: HirId, directive: Option<&Directive>) { + if let Some(directive) = directive { + directive.visit_params(&mut |argument_name, span| { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + errors::OnUnmatchArgsMalformedFormatLiterals { name: argument_name }, + ) + }); + } + } + /// Checks use of generic formatting parameters in `#[diagnostic::on_move]` fn check_diagnostic_on_move(&self, hir_id: HirId, directive: Option<&Directive>) { if let Some(directive) = directive { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index a916b4670fde..5300267573e6 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1303,3 +1303,10 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr { pub(crate) struct OnMoveMalformedFormatLiterals { pub name: Symbol, } + +#[derive(Diagnostic)] +#[diag("unknown parameter `{$name}`")] +#[help(r#"use {"`{This}`"} to refer to the macro name"#)] +pub(crate) struct OnUnmatchArgsMalformedFormatLiterals { + pub name: Symbol, +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index c53763f92046..dc20d4fcf36b 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -710,6 +710,7 @@ fn smart_resolve_macro_path( (sym::on_move, Some(sym::diagnostic_on_move)), (sym::on_const, Some(sym::diagnostic_on_const)), (sym::on_unknown, Some(sym::diagnostic_on_unknown)), + (sym::on_unmatch_args, Some(sym::diagnostic_on_unmatch_args)), ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 981bfed363dc..e7574eaed5b2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -803,6 +803,7 @@ diagnostic_on_const, diagnostic_on_move, diagnostic_on_unknown, + diagnostic_on_unmatch_args, dialect, direct, discriminant_kind, @@ -1427,6 +1428,7 @@ on_move, on_unimplemented, on_unknown, + on_unmatch_args, opaque, opaque_module_name_placeholder: "", ops, diff --git a/library/core/src/field.rs b/library/core/src/field.rs index 0e537e2f92fc..120d7a9f4760 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -45,6 +45,9 @@ fn clone(&self) -> Self { /// variant must also be specified. Only a single field is supported. #[unstable(feature = "field_projections", issue = "145383")] #[allow_internal_unstable(field_representing_type_raw, builtin_syntax)] +#[diagnostic::on_unmatch_args( + note = "this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)`" +)] // NOTE: when stabilizing this macro, we can never add new trait impls for `FieldRepresentingType`, // since it is `#[fundamental]` and thus could break users of this macro, since the compiler expands // it to `FieldRepresentingType<...>`. Thus stabilizing this requires careful thought about the diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index bdc1c48f70df..2b4ac66212e5 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -131,6 +131,7 @@ #![feature(deprecated_suggestion)] #![feature(derive_const)] #![feature(diagnostic_on_const)] +#![feature(diagnostic_on_unmatch_args)] #![feature(doc_cfg)] #![feature(doc_notable_trait)] #![feature(extern_types)] diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 91934cfceb4e..2fbb5842ef77 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1567,6 +1567,9 @@ impl SizedTypeProperties for T {} /// [`offset_of_enum`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/offset-of-enum.html /// [`offset_of_slice`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/offset-of-slice.html #[stable(feature = "offset_of", since = "1.77.0")] +#[diagnostic::on_unmatch_args( + note = "this macro expects a container type and a (nested) field path, like `offset_of!(Type, field)`" +)] #[allow_internal_unstable(builtin_syntax, core_intrinsics)] pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { // The `{}` is for better error messages diff --git a/src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md b/src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md new file mode 100644 index 000000000000..514ad1969bb0 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/diagnostic-on-unmatch-args.md @@ -0,0 +1,53 @@ +# `diagnostic_on_unmatch_args` + +The tracking issue for this feature is: [#155642] + +[#155642]: https://github.com/rust-lang/rust/issues/155642 + +------------------------ + +The `diagnostic_on_unmatch_args` feature adds the +`#[diagnostic::on_unmatch_args(...)]` attribute for declarative macros. +It lets a macro definition customize diagnostics for matcher failures after all arms have been +tried, such as incomplete invocations or trailing extra arguments. + +This attribute currently applies to declarative macros such as `macro_rules!` and `pub macro`. +It is currently used for errors emitted by declarative macro matching itself; fragment parser +errors still use their existing diagnostics. + +```rust,compile_fail +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see ", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +pair!(u8); +``` + +This emits output like: + +```text +error: invalid arguments to pair macro invocation + --> example.rs:13:9 + | +9 | macro_rules! pair { + | ----------------- when calling this macro +... +13 | pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> example.rs:10:12 + | +10 | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see +``` diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs new file mode 100644 index 000000000000..2dd032ecf41c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/auxiliary/other.rs @@ -0,0 +1,12 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[macro_export] +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see the macro documentation for accepted forms", +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs new file mode 100644 index 000000000000..0d5f17f5c5c2 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.rs @@ -0,0 +1,12 @@ +//@ aux-build:other.rs + +extern crate other; + +fn main() { + other::pair!(u8); + //~^ ERROR invalid arguments to pair macro invocation + //~| NOTE expected a type and value here + //~| NOTE while trying to match `,` + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE see the macro documentation for accepted forms +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr new file mode 100644 index 000000000000..c09595835046 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/error_is_shown_in_downstream_crates.stderr @@ -0,0 +1,16 @@ +error: invalid arguments to pair macro invocation + --> $DIR/error_is_shown_in_downstream_crates.rs:6:20 + | +LL | other::pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> $DIR/auxiliary/other.rs:11:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see the macro documentation for accepted forms + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs new file mode 100644 index 000000000000..26a8b07bd6e4 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.rs @@ -0,0 +1,21 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "invalid arguments to {This} macro invocation", + label = "expected a type and value here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "see the macro documentation for accepted forms", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR invalid arguments to pair macro invocation + //~| NOTE expected a type and value here + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE see the macro documentation for accepted forms +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr new file mode 100644 index 000000000000..704479a1b1fa --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/message_and_label.stderr @@ -0,0 +1,19 @@ +error: invalid arguments to pair macro invocation + --> $DIR/message_and_label.rs:16:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ expected a type and value here + | +note: while trying to match `,` + --> $DIR/message_and_label.rs:11:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: see the macro documentation for accepted forms + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs new file mode 100644 index 000000000000..083445c7f969 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.rs @@ -0,0 +1,21 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "{This}! expects exactly two arguments", + label = "unexpected extra input starts here", + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "make sure to pass both arguments", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match meta-variable `$value:expr` +} + +fn main() { + pair!(u8, 0, 42); + //~^ ERROR pair! expects exactly two arguments + //~| NOTE unexpected extra input starts here + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE make sure to pass both arguments +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr new file mode 100644 index 000000000000..e10b8ef46bac --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/notes_on_extra_args.stderr @@ -0,0 +1,19 @@ +error: pair! expects exactly two arguments + --> $DIR/notes_on_extra_args.rs:16:16 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8, 0, 42); + | ^ unexpected extra input starts here + | +note: while trying to match meta-variable `$value:expr` + --> $DIR/notes_on_extra_args.rs:11:14 + | +LL | ($ty:ty, $value:expr) => {}; + | ^^^^^^^^^^^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: make sure to pass both arguments + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs new file mode 100644 index 000000000000..a4fc1460b60e --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + note = "this macro expects a type and a value, like `pair!(u8, 0)`", + note = "make sure to pass both arguments", +)] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR unexpected end of macro invocation + //~| NOTE missing tokens in macro arguments + //~| NOTE this macro expects a type and a value, like `pair!(u8, 0)` + //~| NOTE make sure to pass both arguments +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr new file mode 100644 index 000000000000..9d3c4a5392cd --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/on_unmatch_args.stderr @@ -0,0 +1,19 @@ +error: unexpected end of macro invocation + --> $DIR/on_unmatch_args.rs:14:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $DIR/on_unmatch_args.rs:9:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + = note: this macro expects a type and a value, like `pair!(u8, 0)` + = note: make sure to pass both arguments + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs new file mode 100644 index 000000000000..36197997b3e6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.rs @@ -0,0 +1,14 @@ +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "invalid route method", + note = "this macro expects a action, like `{This}!(get \"/hello\")`" +)] +macro_rules! route { + (get $path:literal) => {}; +} + +fn main() { + route!(post "/"); + //~^ ERROR invalid route method +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr new file mode 100644 index 000000000000..5f6c59e79287 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/other_match_macro_error.stderr @@ -0,0 +1,18 @@ +error: invalid route method + --> $DIR/other_match_macro_error.rs:12:12 + | +LL | macro_rules! route { + | ------------------ when calling this macro +... +LL | route!(post "/"); + | ^^^^ no rules expected this token in macro call + | +note: while trying to match `get` + --> $DIR/other_match_macro_error.rs:8:6 + | +LL | (get $path:literal) => {}; + | ^^^ + = note: this macro expects a action, like `route!(get "/hello")` + +error: aborting due to 1 previous error + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs new file mode 100644 index 000000000000..65cf18168638 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.rs @@ -0,0 +1,14 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args( + message = "{T}! is missing arguments", + //~^ WARN unknown parameter `T` +)] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr new file mode 100644 index 000000000000..2f631337c441 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_formats.stderr @@ -0,0 +1,11 @@ +warning: unknown parameter `T` + --> $DIR/report_warning_on_invalid_formats.rs:5:17 + | +LL | message = "{T}! is missing arguments", + | ^ + | + = help: use `{This}` to refer to the macro name + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs new file mode 100644 index 000000000000..aa5371de07c0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args = "foo"] +//~^ WARN malformed `diagnostic::on_unmatch_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr new file mode 100644 index 000000000000..51f25f1165d6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_invalid_meta_item_syntax.stderr @@ -0,0 +1,11 @@ +warning: malformed `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_invalid_meta_item_syntax.rs:4:1 + | +LL | #[diagnostic::on_unmatch_args = "foo"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs new file mode 100644 index 000000000000..13eec1834d7c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args] +//~^ WARN missing options for `diagnostic::on_unmatch_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr new file mode 100644 index 000000000000..bbd8dba4e620 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_missing_options.stderr @@ -0,0 +1,11 @@ +warning: missing options for `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_missing_options.rs:4:1 + | +LL | #[diagnostic::on_unmatch_args] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs new file mode 100644 index 000000000000..6f8629d679a0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.rs @@ -0,0 +1,10 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args(message = "not allowed here")] +//~^ WARN `#[diagnostic::on_unmatch_args]` can only be applied to macro definitions +struct Foo; + +fn main() { + let _ = Foo; +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr new file mode 100644 index 000000000000..c6d1b620c44b --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_non_macro.stderr @@ -0,0 +1,10 @@ +warning: `#[diagnostic::on_unmatch_args]` can only be applied to macro definitions + --> $DIR/report_warning_on_non_macro.rs:4:1 + | +LL | #[diagnostic::on_unmatch_args(message = "not allowed here")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs new file mode 100644 index 000000000000..b1e50256dc88 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(diagnostic_on_unmatch_args)] + +#[diagnostic::on_unmatch_args(unsupported = "foo")] +//~^ WARN malformed `diagnostic::on_unmatch_args` attribute [malformed_diagnostic_attributes] +macro_rules! pair { + ($ty:ty, $value:expr) => {}; +} + +fn main() { + pair!(u8, 0); +} diff --git a/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr new file mode 100644 index 000000000000..2e897a6b180d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unmatch_args/report_warning_on_unknown_options.stderr @@ -0,0 +1,11 @@ +warning: malformed `diagnostic::on_unmatch_args` attribute + --> $DIR/report_warning_on_unknown_options.rs:4:31 + | +LL | #[diagnostic::on_unmatch_args(unsupported = "foo")] + | ^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs new file mode 100644 index 000000000000..72686d100327 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.rs @@ -0,0 +1,15 @@ +//! This is an unusual feature gate test, as it doesn't test the feature +//! gate, but the fact that not adding the feature gate will cause the +//! diagnostic to not emit the custom diagnostic message. +#[diagnostic::on_unmatch_args(note = "custom note")] +macro_rules! pair { + //~^ NOTE when calling this macro + ($ty:ty, $value:expr) => {}; + //~^ NOTE while trying to match `,` +} + +fn main() { + pair!(u8); + //~^ ERROR unexpected end of macro invocation + //~| NOTE missing tokens in macro arguments +} diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr new file mode 100644 index 000000000000..39eecc8322b2 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unmatch-args.stderr @@ -0,0 +1,17 @@ +error: unexpected end of macro invocation + --> $DIR/feature-gate-diagnostic-on-unmatch-args.rs:12:13 + | +LL | macro_rules! pair { + | ----------------- when calling this macro +... +LL | pair!(u8); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $DIR/feature-gate-diagnostic-on-unmatch-args.rs:7:12 + | +LL | ($ty:ty, $value:expr) => {}; + | ^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/field_representing_types/invalid.next.stderr b/tests/ui/field_representing_types/invalid.next.stderr index 2a5e884a5e3f..611077a57022 100644 --- a/tests/ui/field_representing_types/invalid.next.stderr +++ b/tests/ui/field_representing_types/invalid.next.stderr @@ -6,6 +6,7 @@ LL | let _: field_of!(Struct); | note: while trying to match `,` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: unexpected end of macro invocation --> $DIR/invalid.rs:24:29 @@ -15,6 +16,7 @@ LL | let _: field_of!(Struct,); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: no rules expected `extra` --> $DIR/invalid.rs:25:37 @@ -23,6 +25,7 @@ LL | let _: field_of!(Struct, field, extra); | ^^^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: offset_of expects dot-separated field and variant names --> $DIR/invalid.rs:27:28 diff --git a/tests/ui/field_representing_types/invalid.old.stderr b/tests/ui/field_representing_types/invalid.old.stderr index 2a5e884a5e3f..611077a57022 100644 --- a/tests/ui/field_representing_types/invalid.old.stderr +++ b/tests/ui/field_representing_types/invalid.old.stderr @@ -6,6 +6,7 @@ LL | let _: field_of!(Struct); | note: while trying to match `,` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: unexpected end of macro invocation --> $DIR/invalid.rs:24:29 @@ -15,6 +16,7 @@ LL | let _: field_of!(Struct,); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/field.rs:LL:COL + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: no rules expected `extra` --> $DIR/invalid.rs:25:37 @@ -23,6 +25,7 @@ LL | let _: field_of!(Struct, field, extra); | ^^^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a field path, like `field_of!(Type, field)` or `field_of!(Enum, Variant.field)` error: offset_of expects dot-separated field and variant names --> $DIR/invalid.rs:27:28 diff --git a/tests/ui/offset-of/offset-of-arg-count.stderr b/tests/ui/offset-of/offset-of-arg-count.stderr index 0772bb18e0c6..ca6db9ecf097 100644 --- a/tests/ui/offset-of/offset-of-arg-count.stderr +++ b/tests/ui/offset-of/offset-of-arg-count.stderr @@ -6,6 +6,7 @@ LL | offset_of!(NotEnoughArguments); | note: while trying to match `,` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a (nested) field path, like `offset_of!(Type, field)` error: unexpected end of macro invocation --> $DIR/offset-of-arg-count.rs:5:45 @@ -15,6 +16,7 @@ LL | offset_of!(NotEnoughArgumentsWithAComma, ); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a (nested) field path, like `offset_of!(Type, field)` error: no rules expected `too` --> $DIR/offset-of-arg-count.rs:6:34 @@ -23,6 +25,7 @@ LL | offset_of!(Container, field, too many arguments); | ^^^ no rules expected this token in macro call | = note: while trying to match sequence end + = note: this macro expects a container type and a (nested) field path, like `offset_of!(Type, field)` error: unexpected token: `)` --> $DIR/offset-of-arg-count.rs:9:21 diff --git a/tests/ui/offset-of/offset-of-tuple.stderr b/tests/ui/offset-of/offset-of-tuple.stderr index 33dea9918cac..f90f2db1c6c3 100644 --- a/tests/ui/offset-of/offset-of-tuple.stderr +++ b/tests/ui/offset-of/offset-of-tuple.stderr @@ -72,6 +72,7 @@ LL | offset_of!((u8, u8), +1); | note: while trying to match meta-variable `$fields:expr` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL + = note: this macro expects a container type and a (nested) field path, like `offset_of!(Type, field)` error: offset_of expects dot-separated field and variant names --> $DIR/offset-of-tuple.rs:7:26 From e704d0f6248cc71720e61ca4a24530eb77c8a969 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:08:22 +0200 Subject: [PATCH 49/55] improve stability.md --- src/doc/rustc-dev-guide/src/stability.md | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/stability.md b/src/doc/rustc-dev-guide/src/stability.md index 43788b6146bc..f80dc48cd4c2 100644 --- a/src/doc/rustc-dev-guide/src/stability.md +++ b/src/doc/rustc-dev-guide/src/stability.md @@ -4,8 +4,8 @@ This section is about the stability attributes and schemes that allow stable 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). +For instructions on stabilizing a language feature, +see [Stabilizing Features](./stabilization-guide.md). ## unstable @@ -18,31 +18,30 @@ This restriction only applies across crate boundaries, unstable items may be used within the crate that defines them. The `issue` field specifies the associated GitHub [issue number]. -This field is -required and all unstable features should have an associated tracking issue. -In rare cases where there is no sensible value `issue = "none"` is used. +This field is required, +and all unstable features should have an associated tracking issue. +In rare cases where there is no sensible value, `issue = "none"` is used. The `unstable` attribute infects all sub-items, where the attribute doesn't have to be reapplied. -So if you apply this to a module, all items in the module will be unstable. +So, if you apply this to a module, all items in the module will be unstable. -If you rename a feature, you can add `old_name = "old_name"` to produce a +If you rename a feature, you can add `old_name = "old_name"` to produce a useful error message. You can make specific sub-items stable by using the `#[stable]` attribute on them. The stability scheme works similarly to how `pub` works. -You can have -public functions of nonpublic modules and you can have stable functions in -unstable modules or vice versa. +You can have public functions of non-public modules, +and you can have stable functions in unstable modules or vice versa. Previously, due to a [rustc bug], stable items inside unstable modules were available to stable code in that location. As of September 2024, items with [accidentally stabilized paths] are marked with the `#[rustc_allowed_through_unstable_modules]` attribute to prevent code dependent on those paths from breaking. -Do *not* add this attribute -to any more items unless that is needed to avoid breaking changes. +Do *not* add this attribute to any more items, +unless that is needed to avoid breaking changes. The `unstable` attribute may also have the `soft` value, which makes it a future-incompatible deny-by-default lint instead of a hard error. @@ -161,7 +160,7 @@ You also need to take care to uphold the `const fn` invariant that calling it at compile-time needs to behave the same (see also [this blog post][blog]). This means that you may not create a `const fn` that e.g. transmutes a memory address to an integer, -because the addresses of things are nondeterministic and often unknown at +because the addresses of things are non-deterministic and often unknown at compile-time. **Always ping @rust-lang/wg-const-eval if you are adding more From 14dd4b20861b0c39ae0159110ac4df1394137d89 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:18:09 +0200 Subject: [PATCH 50/55] sembr src/hir/lowering.md --- src/doc/rustc-dev-guide/src/hir/lowering.md | 39 +++++++++++++-------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/hir/lowering.md b/src/doc/rustc-dev-guide/src/hir/lowering.md index 7f9ac325a06e..32b0e9015192 100644 --- a/src/doc/rustc-dev-guide/src/hir/lowering.md +++ b/src/doc/rustc-dev-guide/src/hir/lowering.md @@ -2,8 +2,8 @@ The AST lowering step converts AST to [HIR](../hir.md). This means many structures are removed if they are irrelevant -for type analysis or similar syntax agnostic analyses. Examples -of such structures include but are not limited to +for type analysis or similar syntax agnostic analyses. +Examples of such structures include but are not limited to * Parenthesis * Removed without replacement, the tree structure makes order explicit @@ -19,16 +19,19 @@ The implementation of AST lowering is in the [`rustc_ast_lowering`] crate. The entry point is [`lower_to_hir`], which retrieves the post-expansion AST and resolver data from [`TyCtxt`] and builds the [`hir::Crate`] for the whole crate. -Lowering is organized around HIR owners. [`lower_to_hir`] first indexes the +Lowering is organized around HIR owners. +[`lower_to_hir`] first indexes the crate and then [`ItemLowerer::lower_node`] lowers each crate, item, associated item, and foreign item. -Most of the lowering logic lives on [`LoweringContext`]. The implementation is +Most of the lowering logic lives on [`LoweringContext`]. +The implementation is split across multiple files in the [`rustc_ast_lowering`] crate such as `item.rs`, `expr.rs`, `pat.rs`, `path.rs`, and others, but they all share the same [`LoweringContext`] state and ID‑lowering machinery. -Each owner is lowered in its own [`with_hir_id_owner`] scope. This is why the +Each owner is lowered in its own [`with_hir_id_owner`] scope. +This is why the `HirId` invariants below matter: `lower_node_id` maps AST `NodeId`s into the current owner, while `next_id` creates fresh HIR-only nodes introduced during desugaring. @@ -36,18 +39,20 @@ desugaring. Lowering needs to uphold several invariants in order to not trigger the sanity checks in [`compiler/rustc_passes/src/hir_id_validator.rs`][hir_id_validator]: -1. A `HirId` must be used if created. So if you use the `lower_node_id`, +1. A `HirId` must be used if created. + So if you use the `lower_node_id`, you *must* use the resulting `NodeId` or `HirId` (either is fine, since any `NodeId`s in the `HIR` are checked for existing `HirId`s) 2. Lowering a `HirId` must be done in the scope of the *owning* item. This means you need to use `with_hir_id_owner` if you are creating parts - of an item other than the one being currently lowered. This happens for - example during the lowering of existential `impl Trait` + of an item other than the one being currently lowered. + This happens for example during the lowering of existential `impl Trait` 3. A `NodeId` that will be placed into a HIR structure must be lowered, - even if its `HirId` is unused. Calling - `let _ = self.lower_node_id(node_id);` is perfectly legitimate. + even if its `HirId` is unused. + Calling `let _ = self.lower_node_id(node_id);` is perfectly legitimate. 4. If you are creating new nodes that didn't exist in the `AST`, you *must* - create new ids for them. This is done by calling the `next_id` method, + create new ids for them. + This is done by calling the `next_id` method, which produces both a new `NodeId` as well as automatically lowering it for you so you also get the `HirId`. @@ -62,12 +67,16 @@ sanity checks in [`compiler/rustc_passes/src/hir_id_validator.rs`][hir_id_valida If you are creating new `DefId`s, since each `DefId` needs to have a corresponding `NodeId`, it is advisable to add these `NodeId`s to the -`AST` so you don't have to generate new ones during lowering. This has +`AST` so you don't have to generate new ones during lowering. +This has the advantage of creating a way to find the `DefId` of something via its -`NodeId`. If lowering needs this `DefId` in multiple places, you can't +`NodeId`. +If lowering needs this `DefId` in multiple places, you can't generate a new `NodeId` in all those places because you'd also get a new -`DefId` then. With a `NodeId` from the `AST` this is not an issue. +`DefId` then. +With a `NodeId` from the `AST` this is not an issue. Having the `NodeId` also allows the `DefCollector` to generate the `DefId`s -instead of lowering having to do it on the fly. Centralizing the `DefId` +instead of lowering having to do it on the fly. +Centralizing the `DefId` generation in one place makes it easier to refactor and reason about. From dc675c72d34677d04099ef12078c58d96c52db21 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:23:38 +0200 Subject: [PATCH 51/55] improve hir/lowering.md --- src/doc/rustc-dev-guide/src/hir/lowering.md | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/hir/lowering.md b/src/doc/rustc-dev-guide/src/hir/lowering.md index 32b0e9015192..d555e0e7b3a8 100644 --- a/src/doc/rustc-dev-guide/src/hir/lowering.md +++ b/src/doc/rustc-dev-guide/src/hir/lowering.md @@ -2,7 +2,7 @@ The AST lowering step converts AST to [HIR](../hir.md). This means many structures are removed if they are irrelevant -for type analysis or similar syntax agnostic analyses. +for type analysis or similar syntax-agnostic analyses. Examples of such structures include but are not limited to * Parenthesis @@ -40,21 +40,21 @@ Lowering needs to uphold several invariants in order to not trigger the sanity checks in [`compiler/rustc_passes/src/hir_id_validator.rs`][hir_id_validator]: 1. A `HirId` must be used if created. - So if you use the `lower_node_id`, - you *must* use the resulting `NodeId` or `HirId` (either is fine, since - any `NodeId`s in the `HIR` are checked for existing `HirId`s) + So, if you use the `lower_node_id`, + you *must* use the resulting `NodeId` or `HirId` (either is fine, since + any `NodeId`s in the `HIR` are checked for existing `HirId`s). 2. Lowering a `HirId` must be done in the scope of the *owning* item. - This means you need to use `with_hir_id_owner` if you are creating parts - of an item other than the one being currently lowered. - This happens for example during the lowering of existential `impl Trait` + This means you need to use `with_hir_id_owner` if you are creating parts + of an item other than the one being currently lowered. + This happens, for example, during the lowering of existential `impl Trait`. 3. A `NodeId` that will be placed into a HIR structure must be lowered, - even if its `HirId` is unused. - Calling `let _ = self.lower_node_id(node_id);` is perfectly legitimate. + even if its `HirId` is unused. + Calling `let _ = self.lower_node_id(node_id);` is perfectly legitimate. 4. If you are creating new nodes that didn't exist in the `AST`, you *must* - create new ids for them. - This is done by calling the `next_id` method, - which produces both a new `NodeId` as well as automatically lowering it - for you so you also get the `HirId`. + create new ids for them. + This is done by calling the `next_id` method, + which produces both a new `NodeId` as well as automatically lowering it + for you so you also get the `HirId`. [`rustc_ast_lowering`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/index.html [`lower_to_hir`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast_lowering/fn.lower_to_hir.html @@ -74,7 +74,7 @@ the advantage of creating a way to find the `DefId` of something via its If lowering needs this `DefId` in multiple places, you can't generate a new `NodeId` in all those places because you'd also get a new `DefId` then. -With a `NodeId` from the `AST` this is not an issue. +With a `NodeId` from the `AST`, this is not an issue. Having the `NodeId` also allows the `DefCollector` to generate the `DefId`s instead of lowering having to do it on the fly. From 78b3073a747b39dea3ddda0ee254f2c2e62e0381 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:47:52 +0200 Subject: [PATCH 52/55] sembr src/coherence.md --- src/doc/rustc-dev-guide/src/coherence.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/coherence.md b/src/doc/rustc-dev-guide/src/coherence.md index 73f9213bf405..052b0df65503 100644 --- a/src/doc/rustc-dev-guide/src/coherence.md +++ b/src/doc/rustc-dev-guide/src/coherence.md @@ -8,13 +8,14 @@ Coherence checking is what detects both of trait impls and inherent impls overla Overlapping trait impls always produce an error, while overlapping inherent impls result in an error only if they have methods with the same name. -Checking for overlaps is split in two parts. First there's the [overlap check(s)](#overlap-checks), +Checking for overlaps is split in two parts. +First there's the [overlap check(s)](#overlap-checks), which finds overlaps between traits and inherent implementations that the compiler currently knows about. However, Coherence also results in an error if any other impls **could** exist, -even if they are currently unknown. +even if they are currently unknown. This affects impls which may get added to upstream crates in a backwards compatible way, -and impls from downstream crates. +and impls from downstream crates. This is called the Orphan check. ## Overlap checks @@ -25,7 +26,7 @@ Overlap checks always consider pairs of implementations, comparing them to each Overlap checking for inherent impl blocks is done through `fn check_item` (in coherence/inherent_impls_overlap.rs), where you can very clearly see that (at least for small `n`), the check really performs `n^2` -comparisons between impls. +comparisons between impls. In the case of traits, this check is currently done as part of building the [specialization graph](traits/specialization.md), to handle specializing impls overlapping with their parent, but this may change in the future. @@ -37,7 +38,7 @@ Overlapping is sometimes partially allowed: 1. for marker traits 2. under [specialization](traits/specialization.md) -but normally isn't. +but normally isn't. The overlap check has various modes (see [`OverlapMode`]). Importantly, there's the explicit negative impl check, and the implicit negative impl check. @@ -47,9 +48,9 @@ Both try to prove that an overlap is definitely impossible. ### The explicit negative impl check -This check is done in [`impl_intersection_has_negative_obligation`]. +This check is done in [`impl_intersection_has_negative_obligation`]. -This check tries to find a negative trait implementation. +This check tries to find a negative trait implementation. For example: ```rust From ca75ba23459e969212868479a22b6fb34deb4ad8 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:49:12 +0200 Subject: [PATCH 53/55] improve coherence.md --- src/doc/rustc-dev-guide/src/coherence.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/coherence.md b/src/doc/rustc-dev-guide/src/coherence.md index 052b0df65503..6e4e38b9f794 100644 --- a/src/doc/rustc-dev-guide/src/coherence.md +++ b/src/doc/rustc-dev-guide/src/coherence.md @@ -38,7 +38,7 @@ Overlapping is sometimes partially allowed: 1. for marker traits 2. under [specialization](traits/specialization.md) -but normally isn't. +It normally isn't. The overlap check has various modes (see [`OverlapMode`]). Importantly, there's the explicit negative impl check, and the implicit negative impl check. @@ -65,7 +65,7 @@ In this example, we'd get: `MyCustomErrorType: From<&str>` and `MyCustomErrorType: From`, giving `?E = &str`. And thus, these two implementations would overlap. -However, libstd provides `&str: !Error`, and therefore guarantees that there +However, libstd provides `&str: !Error`, and therefore guarantees that there will never be a positive implementation of `&str: Error`, and thus there is no overlap. Note that for this kind of negative impl check, we must have explicit negative implementations provided. @@ -78,13 +78,13 @@ This is not currently stable. This check is done in [`impl_intersection_has_impossible_obligation`], and does not rely on negative trait implementations and is stable. -Let's say there's a +Let's say there's a ```rust impl From for Box {} // in your own crate impl From for Box where E: Error {} // in std ``` -This would give: `Box: From`, and `Box: From`, +This would give: `Box: From`, and `Box: From`, giving `?E = MyLocalType`. In your crate there's no `MyLocalType: Error`, downstream crates cannot implement `Error` (a remote trait) for `MyLocalType` (a remote type). @@ -92,4 +92,3 @@ Therefore, these two impls do not overlap. Importantly, this works even if there isn't a `impl !Error for MyLocalType`. [`impl_intersection_has_impossible_obligation`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_trait_selection/traits/coherence/fn.impl_intersection_has_impossible_obligation.html - From f7f7989c06222c1738d7190c766efa379171ab59 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 14:50:02 +0200 Subject: [PATCH 54/55] sembr src/name-resolution.md --- .../rustc-dev-guide/src/name-resolution.md | 107 +++++++++++------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/name-resolution.md b/src/doc/rustc-dev-guide/src/name-resolution.md index 2e96382f7797..e91c93215a1f 100644 --- a/src/doc/rustc-dev-guide/src/name-resolution.md +++ b/src/doc/rustc-dev-guide/src/name-resolution.md @@ -1,36 +1,42 @@ # 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 -how this is actually done and more. +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 how this is actually done and more. [ast]: ./ast-validation.md In fact, we don't do full name resolution during macro expansion -- we only -resolve imports and macros at that time. This is required to know what to even -expand. Later, after we have the whole AST, we do full name resolution to -resolve all names in the crate. This happens in [`rustc_resolve::late`][late]. +resolve imports and macros at that time. +This is required to know what to even expand. +Later, after we have the whole AST, we do full name resolution to +resolve all names in the crate. +This happens in [`rustc_resolve::late`][late]. Unlike during macro expansion, in this late expansion, we only need to try to -resolve a name once, since no new names can be added. If we fail to resolve a -name, then it is a compiler error. +resolve a name once, since no new names can be added. +If we fail to resolve a name, then it is a compiler error. Name resolution is complex. There are different namespaces (e.g. macros, values, types, lifetimes), and names may be valid at different (nested) -scopes. Also, different types of names can fail resolution differently, and -failures can happen differently at different scopes. For example, in a module +scopes. +Also, different types of names can fail resolution differently, and +failures can happen differently at different scopes. +For example, in a module scope, failure means no unexpanded macros and no unresolved glob imports in -that module. On the other hand, in a function body scope, failure requires that a -name be absent from the block we are in, all outer scopes, and the global -scope. +that module. +On the other hand, in a function body scope, failure requires that a +name be absent from the block we are in, all outer scopes, and the global scope. [late]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/index.html ## Basics In our programs we refer to variables, types, functions, etc, by giving them -a name. These names are not always unique. For example, take this valid Rust -program: +a name. +These names are not always unique. +For example, take this valid Rust program: ```rust type x = u32; @@ -38,19 +44,23 @@ let x: x = 1; let y: x = 2; ``` -How do we know on line 3 whether `x` is a type (`u32`) or a value (1)? These -conflicts are resolved during name resolution. In this specific case, name +How do we know on line 3 whether `x` is a type (`u32`) or a value (1)? +These conflicts are resolved during name resolution. +In this specific case, name resolution defines that type names and variable names live in separate namespaces and therefore can co-exist. -The name resolution in Rust is a two-phase process. In the first phase, which runs -during `macro` expansion, we build a tree of modules and resolve imports. Macro -expansion and name resolution communicate with each other via the +The name resolution in Rust is a two-phase process. +In the first phase, which runs +during `macro` expansion, we build a tree of modules and resolve imports. +Macro expansion and name resolution communicate with each other via the [`ResolverAstLoweringExt`] trait. The input to the second phase is the syntax tree, produced by parsing input -files and expanding `macros`. This phase produces links from all the names in the -source to relevant places where the name was introduced. It also generates +files and expanding `macros`. +This phase produces links from all the names in the +source to relevant places where the name was introduced. +It also generates helpful error messages, like typo suggestions, traits to import or lints about unused items. @@ -68,9 +78,11 @@ The name resolution lives in the [`rustc_resolve`] crate, with the bulk in ## Namespaces Different kind of symbols live in different namespaces ‒ e.g. types don't -clash with variables. This usually doesn't happen, because variables start with +clash with variables. +This usually doesn't happen, because variables start with lower-case letter while types with upper-case one, but this is only a -convention. This is legal Rust code that will compile (with warnings): +convention. +This is legal Rust code that will compile (with warnings): ```rust type x = u32; @@ -83,19 +95,23 @@ namespaces, the resolver keeps them separated and builds separate structures for them. In other words, when the code talks about namespaces, it doesn't mean the module -hierarchy, it's types vs. values vs. macros. +hierarchy, it's types vs. +values vs. +macros. ## Scopes and ribs -A name is visible only in certain area in the source code. This forms a +A name is visible only in certain area in the source code. +This forms a hierarchical structure, but not necessarily a simple one ‒ if one scope is part of another, it doesn't mean a name visible in the outer scope is also visible in the inner scope, or that it refers to the same thing. -To cope with that, the compiler introduces the concept of [`Rib`]s. This is -an abstraction of a scope. Every time the set of visible names potentially changes, -a new [`Rib`] is pushed onto a stack. The places where this can happen include for -example: +To cope with that, the compiler introduces the concept of [`Rib`]s. +This is an abstraction of a scope. +Every time the set of visible names potentially changes, +a new [`Rib`] is pushed onto a stack. +The places where this can happen include for example: [`Rib`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/struct.Rib.html @@ -106,11 +122,14 @@ example: * Macro expansion border ‒ to cope with macro hygiene. When searching for a name, the stack of [`ribs`] is traversed from the innermost -outwards. This helps to find the closest meaning of the name (the one not -shadowed by anything else). The transition to outer [`Rib`] may also affect +outwards. +This helps to find the closest meaning of the name (the one not +shadowed by anything else). +The transition to outer [`Rib`] may also affect what names are usable ‒ if there are nested functions (not closures), the inner one can't access parameters and local bindings of the outer one, -even though they should be visible by ordinary scoping rules. An example: +even though they should be visible by ordinary scoping rules. +An example: [`ribs`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/struct.LateResolutionVisitor.html#structfield.ribs @@ -139,11 +158,13 @@ blocks), which isn't a full namespace in its own right. ## Overall strategy To perform the name resolution of the whole crate, the syntax tree is traversed -top-down and every encountered name is resolved. This works for most kinds of +top-down and every encountered name is resolved. +This works for most kinds of names, because at the point of use of a name it is already introduced in the [`Rib`] hierarchy. -There are some exceptions to this. Items are bit tricky, because they can be +There are some exceptions to this. +Items are bit tricky, because they can be used even before encountered ‒ therefore every block needs to be first scanned for items to fill in its [`Rib`]. @@ -156,14 +177,15 @@ Therefore, the resolution is performed in multiple stages. ## Speculative crate loading To give useful errors, rustc suggests importing paths into scope if they're -not found. How does it do this? It looks through every module of every crate -and looks for possible matches. This even includes crates that haven't yet -been loaded! +not found. +How does it do this? +It looks through every module of every crate and looks for possible matches. +This even includes crates that haven't yet been loaded! Eagerly loading crates to include import suggestions that haven't yet been loaded is called _speculative crate loading_, because any errors it encounters -shouldn't be reported: [`rustc_resolve`] decided to load them, not the user. The function -that does this is [`lookup_import_candidates`] and lives in +shouldn't be reported: [`rustc_resolve`] decided to load them, not the user. +The function that does this is [`lookup_import_candidates`] and lives in [`rustc_resolve::diagnostics`]. [`rustc_resolve`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/index.html @@ -176,8 +198,9 @@ the load is speculative. ## TODO: [#16](https://github.com/rust-lang/rustc-dev-guide/issues/16) -This is a result of the first pass of learning the code. It is definitely -incomplete and not detailed enough. It also might be inaccurate in places. +This is a result of the first pass of learning the code. +It is definitely incomplete and not detailed enough. +It also might be inaccurate in places. Still, it probably provides useful first guidepost to what happens in there. * What exactly does it link to and how is that published and consumed by From a39bff711214aeee9de9acd0c9e463db412161d8 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Wed, 22 Apr 2026 15:07:45 +0200 Subject: [PATCH 55/55] reflow --- .../rustc-dev-guide/src/name-resolution.md | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/name-resolution.md b/src/doc/rustc-dev-guide/src/name-resolution.md index e91c93215a1f..79d0897b4279 100644 --- a/src/doc/rustc-dev-guide/src/name-resolution.md +++ b/src/doc/rustc-dev-guide/src/name-resolution.md @@ -23,8 +23,8 @@ macros, values, types, lifetimes), and names may be valid at different (nested) scopes. Also, different types of names can fail resolution differently, and failures can happen differently at different scopes. -For example, in a module -scope, failure means no unexpanded macros and no unresolved glob imports in +For example, in a module scope, +failure means no unexpanded macros and no unresolved glob imports in that module. On the other hand, in a function body scope, failure requires that a name be absent from the block we are in, all outer scopes, and the global scope. @@ -46,13 +46,14 @@ let y: x = 2; How do we know on line 3 whether `x` is a type (`u32`) or a value (1)? These conflicts are resolved during name resolution. -In this specific case, name -resolution defines that type names and variable names live in separate +In this specific case, +name resolution defines that type names and variable names live in separate namespaces and therefore can co-exist. The name resolution in Rust is a two-phase process. -In the first phase, which runs -during `macro` expansion, we build a tree of modules and resolve imports. +In the first phase, +which runs during `macro` expansion, +we build a tree of modules and resolve imports. Macro expansion and name resolution communicate with each other via the [`ResolverAstLoweringExt`] trait. @@ -60,8 +61,8 @@ The input to the second phase is the syntax tree, produced by parsing input files and expanding `macros`. This phase produces links from all the names in the source to relevant places where the name was introduced. -It also generates -helpful error messages, like typo suggestions, traits to import or lints about +It also generates helpful error messages, +like typo suggestions, traits to import or lints about unused items. A successful run of the second phase ([`Resolver::resolve_crate`]) creates kind @@ -95,15 +96,13 @@ namespaces, the resolver keeps them separated and builds separate structures for them. In other words, when the code talks about namespaces, it doesn't mean the module -hierarchy, it's types vs. -values vs. -macros. +hierarchy, it's types versus values versus macros. ## Scopes and ribs A name is visible only in certain area in the source code. -This forms a -hierarchical structure, but not necessarily a simple one ‒ if one scope is +This forms a hierarchical structure, +but not necessarily a simple one ‒ if one scope is part of another, it doesn't mean a name visible in the outer scope is also visible in the inner scope, or that it refers to the same thing. @@ -159,8 +158,8 @@ blocks), which isn't a full namespace in its own right. To perform the name resolution of the whole crate, the syntax tree is traversed top-down and every encountered name is resolved. -This works for most kinds of -names, because at the point of use of a name it is already introduced in the [`Rib`] +This works for most kinds of names, +because at the point of use of a name it is already introduced in the [`Rib`] hierarchy. There are some exceptions to this.