From eda3ca48ae3853e93fe5e236b0dc39909c1cfa8f Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Thu, 27 Aug 2026 09:08:48 +0200 Subject: [PATCH 01/44] document EMIT_MIR_FOR_EACH_PANIC_STRATEGY --- src/doc/rustc-dev-guide/src/tests/compiletest.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index 4573a04d0281c..e3d044d50b040 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -460,6 +460,10 @@ problematic in the presence of pointers in constants or other bit width dependen In that case you can add `// EMIT_MIR_FOR_EACH_BIT_WIDTH` to your test, causing separate files to be generated for 32bit and 64bit systems. +Similarly, +to generate separate files for each panic strategy ("panic-abort" and "panic-unwind"), +add `// EMIT_MIR_FOR_EACH_PANIC_STRATEGY` to the test. + [`tests/mir-opt`]: https://github.com/rust-lang/rust/tree/HEAD/tests/mir-opt From a041b11a170d2d8f1724f47b8e5109b01f20e80d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 1 Oct 2026 22:51:00 +0200 Subject: [PATCH 02/44] Make docblock `` elements inline so they behave better --- src/librustdoc/html/static/css/rustdoc.css | 9 +- .../code-in-docblock-and-comma.goml | 93 +++++++++++++++++++ tests/rustdoc-gui/src/test_docs/lib.rs | 3 + 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-gui/code-in-docblock-and-comma.goml diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 6e7a6daa307cd..3d9433b67bb49 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -499,6 +499,9 @@ code, pre, .code-header, .type-signature { border-radius: 3px; padding: 0 0.125em; } +.docblock code { + padding: 2px 0.125em; +} .docblock pre code, .item-table dd pre code { padding: 0; } @@ -1149,10 +1152,14 @@ pre, .rustdoc.src .example-wrap, .example-wrap .src-line-numbers { background: var(--table-alt-row-background-color); } -.docblock .stab, .item-table dd .stab, .docblock p code { +.docblock .stab, .item-table dd .stab { display: inline-block; } +.docblock p code { + display: inline; +} + .docblock li { margin-bottom: .4em; } diff --git a/tests/rustdoc-gui/code-in-docblock-and-comma.goml b/tests/rustdoc-gui/code-in-docblock-and-comma.goml new file mode 100644 index 0000000000000..6bbbebb9bfe90 --- /dev/null +++ b/tests/rustdoc-gui/code-in-docblock-and-comma.goml @@ -0,0 +1,93 @@ +// This test ensures that `` in docblocks are `display: inline` so comma are +// "stuck" to them instead of going to next line on their own. +// Regression test for . + +go-to: "file://" + |DOC_PATH| + "/test_docs/inline_code/index.html" + +store-value: (height, 300) +// Very precise width! +store-value: (big_width, 588) +store-value: (small_width, 584) +set-window-size: (|big_width|, |height|) + +// Very annoying test as we can't query information about the comma itself, so instead +// we're gonna look for the position of the `` just before and after it. +store-position: ( + '//*[@class="docblock"]/p', + {"x": x_parent}, +) + +define-function: ( + "test_initial_positions", + [], + block { + store-position: ( + '//*[@class="docblock"]/p/code[text()="Color::srgba_u8"]', + {"x": x_before, "y": y_before}, + ) + store-position: ( + '//*[@class="docblock"]/p/code[text()="blob"]', + {"x": x_after, "y": y_after}, + ) + + // First we check that the "after" element is at the start of the line (so the same `x` position + // as its parent). + assert: |x_after| == |x_parent| + // Then we ensure that both `` aren't on the same line. + assert: |y_before| < |y_after| + }, +) + +call-function: ("test_initial_positions", {}) + +// We reduce the width of the screen a bit to force the first `` (and the comma) to go on the +// next line. +set-window-size: (|small_width|, |height|) + +store-position: ( + '//*[@class="docblock"]/p/code[text()="Color::srgba_u8"]', + {"x": x2_before, "y": y2_before}, +) +store-position: ( + '//*[@class="docblock"]/p/code[text()="blob"]', + {"x": x2_after, "y": y2_after}, +) + +// The "y" position of the second `` shouldn't have changed. +assert: |y_after| == |y2_after| +// However its "x" position should have changed. +assert: |x_after| < |x2_after| +// And the first `` should not be on the same line anymore. +assert: |y_before| < |y2_before| +// So the "x" position should now be the same as the parent. +assert: |x2_before| == |x_parent| + +// And now we switch to `display: inline-block` and confirm that the comma was not "stuck" to the +// `` (although it should). +set-window-size: (|big_width|, |height|) +set-css: ( + '//*[@class="docblock"]/p/code[text()="Color::srgba_u8"]', + {"display": "inline-block"}, +) + +call-function: ("test_initial_positions", {}) + +// Now we reduce the width, and that's where the interesting part starts. +set-window-size: (|small_width|, |height|) + +store-position: ( + '//*[@class="docblock"]/p/code[text()="Color::srgba_u8"]', + {"x": x2_before, "y": y2_before}, +) +store-position: ( + '//*[@class="docblock"]/p/code[text()="blob"]', + {"x": x2_after, "y": y2_after}, +) + +// The "y" position of the second `` shouldn't have changed. +assert: |y_after| == |y2_after| +// And its "x" position should also have changed (confirming that the comma is not on the same +// line anymore). +assert: |x_after| < |x2_after| +// However unlike previously, the first `` should still be on the same line. +assert: |y_before| == |y2_before| diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs index 54db340e7ba64..1ffcd80581c24 100644 --- a/tests/rustdoc-gui/src/test_docs/lib.rs +++ b/tests/rustdoc-gui/src/test_docs/lib.rs @@ -827,3 +827,6 @@ pub mod notable { pub macro decl_macro { () => { "bar" } } + +/// Some text before the code so the width isn't too small `Color::srgba_u8`, `blob`. +pub mod inline_code {} From 4621dfed54171d506464aec61201cc6dc006003b Mon Sep 17 00:00:00 2001 From: Jules Bertholet Date: Wed, 29 Jul 2026 19:12:43 -0400 Subject: [PATCH 03/44] Implement `Fn` traits for functions of any ABI This brings the behavior of the language in line with the documentation in `library/core/src/primitive_docs.rs`. --- compiler/rustc_abi/src/extern_abi.rs | 37 ++++++++++++++ .../src/error_reporting/traits/suggestions.rs | 5 +- compiler/rustc_type_ir/src/ty_kind.rs | 2 +- src/tools/miri/tests/pass/fn-traits-abi.rs | 39 +++++++++++++++ .../async-closures/c-fn-ptr-asyncfn.rs | 21 ++++++++ .../async-closures/fn-exception.rs | 2 +- .../async-closures/fn-exception.stderr | 16 +------ tests/ui/extern/extern-wrong-value-type.rs | 8 ++-- .../ui/extern/extern-wrong-value-type.stderr | 19 -------- .../suggest-option-asderef-unfixable.rs | 7 ++- .../suggest-option-asderef-unfixable.stderr | 33 +++++-------- tests/ui/traits/next-solver/fn-trait.rs | 2 - tests/ui/traits/next-solver/fn-trait.stderr | 36 +------------- .../unboxed-closures/unboxed-closures-abi.rs | 40 ++++++++++++++++ .../unboxed-closures-wrong-abi.rs | 34 ------------- .../unboxed-closures-wrong-abi.stderr | 48 ------------------- 16 files changed, 164 insertions(+), 185 deletions(-) create mode 100644 src/tools/miri/tests/pass/fn-traits-abi.rs create mode 100644 tests/ui/async-await/async-closures/c-fn-ptr-asyncfn.rs delete mode 100644 tests/ui/extern/extern-wrong-value-type.stderr create mode 100644 tests/ui/unboxed-closures/unboxed-closures-abi.rs delete mode 100644 tests/ui/unboxed-closures/unboxed-closures-wrong-abi.rs delete mode 100644 tests/ui/unboxed-closures/unboxed-closures-wrong-abi.stderr diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs index 79cee81d0fa2d..91c0bc0b59bca 100644 --- a/compiler/rustc_abi/src/extern_abi.rs +++ b/compiler/rustc_abi/src/extern_abi.rs @@ -369,6 +369,43 @@ impl ExternAbi { | Self::Swift => true, } } + + /// Returns whether safe functions with this ABI implement the `Fn` traits. + /// `false` only for uncallable or internal ABIs. + pub fn is_fn_trait_compatible(self) -> bool { + match self { + ExternAbi::C { .. } + | ExternAbi::System { .. } + | ExternAbi::Rust + | ExternAbi::RustCold + | ExternAbi::RustPreserveNone + | ExternAbi::RustTail + | ExternAbi::EfiApi + | ExternAbi::Swift + | ExternAbi::Aapcs { .. } + | ExternAbi::CmseNonSecureCall + | ExternAbi::CmseNonSecureEntry + | ExternAbi::Cdecl { .. } + | ExternAbi::Stdcall { .. } + | ExternAbi::Fastcall { .. } + | ExternAbi::Thiscall { .. } + | ExternAbi::Vectorcall { .. } + | ExternAbi::SysV64 { .. } + | ExternAbi::Win64 { .. } => true, + ExternAbi::RustCall + | ExternAbi::RustInvalid + | ExternAbi::LlvmIntrinsic + | ExternAbi::Custom + | ExternAbi::GpuKernel + | ExternAbi::PtxKernel + | ExternAbi::AvrInterrupt + | ExternAbi::AvrNonBlockingInterrupt + | ExternAbi::Msp430Interrupt + | ExternAbi::RiscvInterruptM + | ExternAbi::RiscvInterruptS + | ExternAbi::X86Interrupt => false, + } + } } pub fn all_names() -> Vec<&'static str> { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index b3ac3d179c8e8..b76095e9443e7 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -5,7 +5,6 @@ use std::path::PathBuf; use std::{debug_assert_matches, iter}; use itertools::{EitherOrBoth, Itertools}; -use rustc_abi::ExternAbi; use rustc_attr_ir::lang_items::{self, LangItem}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::codes::*; @@ -5520,9 +5519,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .. } = fn_ty.fn_sig(tcx).skip_binder() // FIXME(splat): this might need to change if the Fn* traits start using/supporting splat - && fn_sig.abi() == ExternAbi::Rust - && !fn_sig.c_variadic() - && fn_sig.safety() == hir::Safety::Safe + && fn_sig.is_fn_trait_compatible() // Extract first param of fn sig with peeled refs, e.g. `fn(&T)` -> `T` && let Some(&ty::Ref(_, target_ty, needs_mut)) = fn_sig.inputs().first().map(|t| t.kind()) diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 35f8897afd4c4..4d2c51e577a1f 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -1147,7 +1147,7 @@ impl FnSig { } pub fn is_fn_trait_compatible(self) -> bool { - !self.c_variadic() && self.safety().is_safe() && self.abi() == ExternAbi::Rust + !self.c_variadic() && self.safety().is_safe() && self.abi().is_fn_trait_compatible() } /// Set the safety flag. diff --git a/src/tools/miri/tests/pass/fn-traits-abi.rs b/src/tools/miri/tests/pass/fn-traits-abi.rs new file mode 100644 index 0000000000000..ed2579f7ab529 --- /dev/null +++ b/src/tools/miri/tests/pass/fn-traits-abi.rs @@ -0,0 +1,39 @@ +//! Test implementation of `Fn` traits for functions with non-`Rust` ABI + +use std::ops::{Fn, FnMut, FnOnce}; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Foo(i32, i32); + +extern "C" fn square(foo: Foo) -> Foo { + Foo(foo.0 * foo.1, foo.0 - foo.1) +} + +fn call_it Foo>(f: &F, i: Foo) -> Foo { + f(i) +} + +fn call_it_mut Foo>(f: &mut F, i: Foo) -> Foo { + f(i) +} + +fn call_it_once Foo>(f: F, i: Foo) -> Foo { + f(i) +} + +fn main() { + assert_eq!(call_it(&square, Foo(20, 10)), Foo(200, 10)); + assert_eq!(call_it_mut(&mut square, Foo(21, 9)), Foo(189, 12)); + assert_eq!(call_it_once(square, Foo(18, -3)), Foo(-54, 21)); + + let mut square_ptr: extern "C" fn(Foo) -> Foo = square; + assert_eq!(call_it(&square_ptr, Foo(30, 11)), Foo(330, 19)); + assert_eq!(call_it_mut(&mut square_ptr, Foo(273, -1)), Foo(-273, 274)); + assert_eq!(call_it_once(square_ptr, Foo(27, 27)), Foo(729, 0)); + + let mut square_dyn: Box Foo> = Box::new(square); + assert_eq!(call_it(&*square_dyn, Foo(1, 3)), Foo(3, -2)); + assert_eq!(call_it_mut(&mut *square_dyn, Foo(5, 25)), Foo(125, -20)); + assert_eq!(call_it_once(square_dyn, Foo(9, 2)), Foo(18, 7)); +} diff --git a/tests/ui/async-await/async-closures/c-fn-ptr-asyncfn.rs b/tests/ui/async-await/async-closures/c-fn-ptr-asyncfn.rs new file mode 100644 index 0000000000000..45d5302b1584d --- /dev/null +++ b/tests/ui/async-await/async-closures/c-fn-ptr-asyncfn.rs @@ -0,0 +1,21 @@ +//@ run-pass +//@ edition: 2021 + +use std::future::Future; +use std::pin::Pin; + +#[expect(improper_ctypes_definitions)] +pub extern "C" fn abi() -> Pin + 'static>> { + Box::pin(async {}) +} + +fn test(f: impl AsyncFn()) { + let _ = async { + f().await; + f().await; + }; +} + +fn main() { + test(abi); +} diff --git a/tests/ui/async-await/async-closures/fn-exception.rs b/tests/ui/async-await/async-closures/fn-exception.rs index 8ff63983e7932..71df2178ff0c4 100644 --- a/tests/ui/async-await/async-closures/fn-exception.rs +++ b/tests/ui/async-await/async-closures/fn-exception.rs @@ -15,5 +15,5 @@ fn test(f: impl AsyncFn()) {} fn main() { test(unsafety); //~ ERROR the trait bound - test(abi); //~ ERROR the trait bound + test(abi); } diff --git a/tests/ui/async-await/async-closures/fn-exception.stderr b/tests/ui/async-await/async-closures/fn-exception.stderr index aa74ed234df00..a3366dda5ba73 100644 --- a/tests/ui/async-await/async-closures/fn-exception.stderr +++ b/tests/ui/async-await/async-closures/fn-exception.stderr @@ -12,20 +12,6 @@ note: required by a bound in `test` LL | fn test(f: impl AsyncFn()) {} | ^^^^^^^^^ required by this bound in `test` -error[E0277]: the trait bound `extern "C" fn() -> Pin + 'static)>> {abi}: AsyncFn()` is not satisfied - --> $DIR/fn-exception.rs:18:10 - | -LL | test(abi); - | ---- ^^^ the trait `AsyncFn()` is not implemented for fn item `extern "C" fn() -> Pin + 'static)>> {abi}` - | | - | required by a bound introduced by this call - | -note: required by a bound in `test` - --> $DIR/fn-exception.rs:14:17 - | -LL | fn test(f: impl AsyncFn()) {} - | ^^^^^^^^^ required by this bound in `test` - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/extern/extern-wrong-value-type.rs b/tests/ui/extern/extern-wrong-value-type.rs index 38a77a697fe51..3aa3539777cad 100644 --- a/tests/ui/extern/extern-wrong-value-type.rs +++ b/tests/ui/extern/extern-wrong-value-type.rs @@ -1,11 +1,13 @@ +//@ run-pass + extern "C" fn f() { } -fn is_fn(_: F) where F: Fn() {} +fn is_fn(f: F) where F: Fn() { + f(); +} fn main() { - // extern functions are extern "C" fn let _x: extern "C" fn() = f; // OK is_fn(f); - //~^ ERROR expected `Fn()` signature, found `extern "C" fn() {f}` } diff --git a/tests/ui/extern/extern-wrong-value-type.stderr b/tests/ui/extern/extern-wrong-value-type.stderr deleted file mode 100644 index 0330992e62f06..0000000000000 --- a/tests/ui/extern/extern-wrong-value-type.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error[E0277]: expected `Fn()` signature, found `extern "C" fn() {f}` - --> $DIR/extern-wrong-value-type.rs:9:11 - | -LL | is_fn(f); - | ----- ^ expected `Fn()` signature, found `extern "C" fn() {f}` - | | - | required by a bound introduced by this call - | - = help: the trait `Fn()` is not implemented for fn item `extern "C" fn() {f}` - = note: wrap the `extern "C" fn() {f}` in a closure with no arguments: `|| { /* code */ }` -note: required by a bound in `is_fn` - --> $DIR/extern-wrong-value-type.rs:4:28 - | -LL | fn is_fn(_: F) where F: Fn() {} - | ^^^^ required by this bound in `is_fn` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs b/tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs index 6dae9384ecba3..d212486d995f9 100644 --- a/tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs +++ b/tests/ui/mismatched_types/suggest-option-asderef-unfixable.rs @@ -1,5 +1,5 @@ -fn produces_string() -> Option { - Some("my cool string".to_owned()) +fn produces_string() -> Option<&'static str> { + Some("my cool string") } fn takes_str_but_too_many_refs(_: &&str) -> Option<()> { @@ -24,9 +24,8 @@ fn main() { let _ = produces_string().and_then(takes_str_but_too_many_refs); //~^ ERROR type mismatch in function arguments let _ = produces_string().and_then(takes_str_but_wrong_abi); - //~^ ERROR expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}` let _ = produces_string().and_then(takes_str_but_unsafe); - //~^ ERROR expected `FnOnce(String)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` + //~^ ERROR expected `FnOnce(&str)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` let _ = produces_string().and_then(no_args); //~^ ERROR function is expected to take 1 argument, but it takes 0 arguments let _ = Some(TypeWithoutDeref).and_then(takes_str_but_too_many_refs); diff --git a/tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr b/tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr index 56f6437f77b54..1a3f1ed9a9ff4 100644 --- a/tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr +++ b/tests/ui/mismatched_types/suggest-option-asderef-unfixable.stderr @@ -9,42 +9,35 @@ LL | let _ = produces_string().and_then(takes_str_but_too_many_refs); | | | required by a bound introduced by this call | - = note: expected function signature `fn(String) -> _` - found function signature `fn(&&str) -> _` + = note: expected function signature `fn(&_) -> _` + found function signature `fn(&&_) -> _` note: required by a bound in `Option::::and_then` --> $SRC_DIR/core/src/option.rs:LL:COL help: consider wrapping the function in a closure | -LL | let _ = produces_string().and_then(|arg0: String| takes_str_but_too_many_refs(/* &&str */)); - | ++++++++++++++ +++++++++++++ - -error[E0277]: expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}` - --> $DIR/suggest-option-asderef-unfixable.rs:26:40 +LL | let _ = produces_string().and_then(|arg0: &str| takes_str_but_too_many_refs(&arg0)); + | ++++++++++++ +++++++ +help: consider adjusting the signature so it does not borrow its argument | -LL | let _ = produces_string().and_then(takes_str_but_wrong_abi); - | -------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `FnOnce(String)` signature, found `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}` - | | - | required by a bound introduced by this call +LL - fn takes_str_but_too_many_refs(_: &&str) -> Option<()> { +LL + fn takes_str_but_too_many_refs(_: &str) -> Option<()> { | - = help: the trait `FnOnce(String)` is not implemented for fn item `for<'a> extern "C" fn(&'a str) -> Option<()> {takes_str_but_wrong_abi}` -note: required by a bound in `Option::::and_then` - --> $SRC_DIR/core/src/option.rs:LL:COL -error[E0277]: expected `FnOnce(String)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` - --> $DIR/suggest-option-asderef-unfixable.rs:28:40 +error[E0277]: expected `FnOnce(&str)` signature, found `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` + --> $DIR/suggest-option-asderef-unfixable.rs:27:40 | LL | let _ = produces_string().and_then(takes_str_but_unsafe); | -------- ^^^^^^^^^^^^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` | | | required by a bound introduced by this call | - = help: the trait `FnOnce(String)` is not implemented for fn item `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` + = help: the trait `FnOnce(&str)` is not implemented for fn item `for<'a> unsafe fn(&'a str) -> Option<()> {takes_str_but_unsafe}` = note: unsafe function cannot be called generically without an unsafe block note: required by a bound in `Option::::and_then` --> $SRC_DIR/core/src/option.rs:LL:COL error[E0593]: function is expected to take 1 argument, but it takes 0 arguments - --> $DIR/suggest-option-asderef-unfixable.rs:30:40 + --> $DIR/suggest-option-asderef-unfixable.rs:29:40 | LL | fn no_args() -> Option<()> { | -------------------------- takes 0 arguments @@ -58,7 +51,7 @@ note: required by a bound in `Option::::and_then` --> $SRC_DIR/core/src/option.rs:LL:COL error[E0631]: type mismatch in function arguments - --> $DIR/suggest-option-asderef-unfixable.rs:32:45 + --> $DIR/suggest-option-asderef-unfixable.rs:31:45 | LL | fn takes_str_but_too_many_refs(_: &&str) -> Option<()> { | ------------------------------------------------------ found signature defined here @@ -77,7 +70,7 @@ help: consider wrapping the function in a closure LL | let _ = Some(TypeWithoutDeref).and_then(|arg0: TypeWithoutDeref| takes_str_but_too_many_refs(/* &&str */)); | ++++++++++++++++++++++++ +++++++++++++ -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors Some errors have detailed explanations: E0277, E0593, E0631. For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/next-solver/fn-trait.rs b/tests/ui/traits/next-solver/fn-trait.rs index 9d3d5ce904c87..113396845b81d 100644 --- a/tests/ui/traits/next-solver/fn-trait.rs +++ b/tests/ui/traits/next-solver/fn-trait.rs @@ -20,9 +20,7 @@ fn main() { require_fn(f as unsafe fn() -> i32); //~^ ERROR: expected `Fn()` signature, found `unsafe fn() -> i32` require_fn(g); - //~^ ERROR: expected `Fn()` signature, found `extern "C" fn() -> i32 {g}` require_fn(g as extern "C" fn() -> i32); - //~^ ERROR: expected `Fn()` signature, found `extern "C" fn() -> i32` require_fn(h); //~^ ERROR: expected `Fn()` signature, found `unsafe fn() -> i32 {h}` } diff --git a/tests/ui/traits/next-solver/fn-trait.stderr b/tests/ui/traits/next-solver/fn-trait.stderr index ec3a4dc54cca8..73bf3a77b6dfd 100644 --- a/tests/ui/traits/next-solver/fn-trait.stderr +++ b/tests/ui/traits/next-solver/fn-trait.stderr @@ -15,40 +15,8 @@ note: required by a bound in `require_fn` LL | fn require_fn(_: impl Fn() -> i32) {} | ^^^^^^^^^^^ required by this bound in `require_fn` -error[E0277]: expected `Fn()` signature, found `extern "C" fn() -> i32 {g}` - --> $DIR/fn-trait.rs:22:16 - | -LL | require_fn(g); - | ---------- ^ expected `Fn()` signature, found `extern "C" fn() -> i32 {g}` - | | - | required by a bound introduced by this call - | - = help: the trait `Fn()` is not implemented for fn item `extern "C" fn() -> i32 {g}` - = note: wrap the `extern "C" fn() -> i32 {g}` in a closure with no arguments: `|| { /* code */ }` -note: required by a bound in `require_fn` - --> $DIR/fn-trait.rs:3:23 - | -LL | fn require_fn(_: impl Fn() -> i32) {} - | ^^^^^^^^^^^ required by this bound in `require_fn` - -error[E0277]: expected `Fn()` signature, found `extern "C" fn() -> i32` - --> $DIR/fn-trait.rs:24:16 - | -LL | require_fn(g as extern "C" fn() -> i32); - | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Fn()` signature, found `extern "C" fn() -> i32` - | | - | required by a bound introduced by this call - | - = help: the trait `Fn()` is not implemented for `extern "C" fn() -> i32` - = note: wrap the `extern "C" fn() -> i32` in a closure with no arguments: `|| { /* code */ }` -note: required by a bound in `require_fn` - --> $DIR/fn-trait.rs:3:23 - | -LL | fn require_fn(_: impl Fn() -> i32) {} - | ^^^^^^^^^^^ required by this bound in `require_fn` - error[E0277]: expected `Fn()` signature, found `unsafe fn() -> i32 {h}` - --> $DIR/fn-trait.rs:26:16 + --> $DIR/fn-trait.rs:24:16 | LL | require_fn(h); | ---------- ^ call the function in a closure: `|| unsafe { /* code */ }` @@ -64,6 +32,6 @@ note: required by a bound in `require_fn` LL | fn require_fn(_: impl Fn() -> i32) {} | ^^^^^^^^^^^ required by this bound in `require_fn` -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/unboxed-closures/unboxed-closures-abi.rs b/tests/ui/unboxed-closures/unboxed-closures-abi.rs new file mode 100644 index 0000000000000..5aabc12971e63 --- /dev/null +++ b/tests/ui/unboxed-closures/unboxed-closures-abi.rs @@ -0,0 +1,40 @@ +//@ run-pass +//! Test implementation of `Fn` traits for functions with non-`Rust` ABI + +use std::ops::{Fn, FnMut, FnOnce}; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Foo(i32, i32); + +extern "C" fn square(foo: Foo) -> Foo { + Foo(foo.0 * foo.1, foo.0 - foo.1) +} + +fn call_it Foo>(f: &F, i: Foo) -> Foo { + f(i) +} + +fn call_it_mut Foo>(f: &mut F, i: Foo) -> Foo { + f(i) +} + +fn call_it_once Foo>(f: F, i: Foo) -> Foo { + f(i) +} + +fn main() { + assert_eq!(call_it(&square, Foo(20, 10)), Foo(200, 10)); + assert_eq!(call_it_mut(&mut square, Foo(21, 9)), Foo(189, 12)); + assert_eq!(call_it_once(square, Foo(18, -3)), Foo(-54, 21)); + + let mut square_ptr: extern "C" fn(Foo) -> Foo = square; + assert_eq!(call_it(&square_ptr, Foo(30, 11)), Foo(330, 19)); + assert_eq!(call_it_mut(&mut square_ptr, Foo(273, -1)), Foo(-273, 274)); + assert_eq!(call_it_once(square_ptr, Foo(27, 27)), Foo(729, 0)); + + let mut square_dyn: Box Foo> = Box::new(square); + assert_eq!(call_it(&*square_dyn, Foo(1, 3)), Foo(3, -2)); + assert_eq!(call_it_mut(&mut *square_dyn, Foo(5, 25)), Foo(125, -20)); + assert_eq!(call_it_once(square_dyn, Foo(9, 2)), Foo(18, 7)); +} diff --git a/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.rs b/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.rs deleted file mode 100644 index dd76c597d28ad..0000000000000 --- a/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Tests that unsafe extern fn pointers do not implement any Fn traits. - -use std::ops::{Fn, FnMut, FnOnce}; - -extern "C" fn square(x: &isize) -> isize { - (*x) * (*x) -} - -fn call_it isize>(_: &F, _: isize) -> isize { - 0 -} -fn call_it_mut isize>(_: &mut F, _: isize) -> isize { - 0 -} -fn call_it_once isize>(_: F, _: isize) -> isize { - 0 -} - -fn a() { - let x = call_it(&square, 22); - //~^ ERROR E0277 -} - -fn b() { - let y = call_it_mut(&mut square, 22); - //~^ ERROR E0277 -} - -fn c() { - let z = call_it_once(square, 22); - //~^ ERROR E0277 -} - -fn main() {} diff --git a/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.stderr b/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.stderr deleted file mode 100644 index 80c368fa46b5f..0000000000000 --- a/tests/ui/unboxed-closures/unboxed-closures-wrong-abi.stderr +++ /dev/null @@ -1,48 +0,0 @@ -error[E0277]: expected `Fn(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - --> $DIR/unboxed-closures-wrong-abi.rs:20:21 - | -LL | let x = call_it(&square, 22); - | ------- ^^^^^^^ expected `Fn(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - | | - | required by a bound introduced by this call - | - = help: the trait `for<'a> Fn(&'a isize)` is not implemented for fn item `for<'a> extern "C" fn(&'a isize) -> isize {square}` -note: required by a bound in `call_it` - --> $DIR/unboxed-closures-wrong-abi.rs:9:15 - | -LL | fn call_it isize>(_: &F, _: isize) -> isize { - | ^^^^^^^^^^^^^^^^^^^ required by this bound in `call_it` - -error[E0277]: expected `FnMut(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - --> $DIR/unboxed-closures-wrong-abi.rs:25:25 - | -LL | let y = call_it_mut(&mut square, 22); - | ----------- ^^^^^^^^^^^ expected `FnMut(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - | | - | required by a bound introduced by this call - | - = help: the trait `for<'a> FnMut(&'a isize)` is not implemented for fn item `for<'a> extern "C" fn(&'a isize) -> isize {square}` -note: required by a bound in `call_it_mut` - --> $DIR/unboxed-closures-wrong-abi.rs:12:19 - | -LL | fn call_it_mut isize>(_: &mut F, _: isize) -> isize { - | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `call_it_mut` - -error[E0277]: expected `FnOnce(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - --> $DIR/unboxed-closures-wrong-abi.rs:30:26 - | -LL | let z = call_it_once(square, 22); - | ------------ ^^^^^^ expected `FnOnce(&isize)` signature, found `for<'a> extern "C" fn(&'a isize) -> isize {square}` - | | - | required by a bound introduced by this call - | - = help: the trait `for<'a> FnOnce(&'a isize)` is not implemented for fn item `for<'a> extern "C" fn(&'a isize) -> isize {square}` -note: required by a bound in `call_it_once` - --> $DIR/unboxed-closures-wrong-abi.rs:15:20 - | -LL | fn call_it_once isize>(_: F, _: isize) -> isize { - | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `call_it_once` - -error: aborting due to 3 previous errors - -For more information about this error, try `rustc --explain E0277`. From ef45c8263f2d3f98c3cfe37fbfc36db7328e8025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 14:30:21 +0200 Subject: [PATCH 04/44] Document that `rustc_codegen_gcc` now uses Josh --- src/doc/rustc-dev-guide/src/external-repos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/external-repos.md b/src/doc/rustc-dev-guide/src/external-repos.md index 0e23be130c403..257757c17fca9 100644 --- a/src/doc/rustc-dev-guide/src/external-repos.md +++ b/src/doc/rustc-dev-guide/src/external-repos.md @@ -42,11 +42,11 @@ you should include fixes for those in the rustc PR as well. * Using `git subtree` * `clippy` ([sync guide](https://doc.rust-lang.org/nightly/clippy/development/infrastructure/sync.html#performing-the-sync-from-rust-langrust-to-clippy)) * `portable-simd` ([sync script](https://github.com/rust-lang/portable-simd/blob/master/subtree-sync.sh)) - * `rustc_codegen_gcc` ([sync guide](https://github.com/rust-lang/rustc_codegen_gcc/blob/master/doc/subtree.md)) * Using the [josh](#synchronizing-a-josh-subtree) tool * `miri` * `rust-analyzer` * `rustc_codegen_cranelift` + * `rustc_codegen_gcc` * `rustc-dev-guide` * `rustfmt` * `compiler-builtins` From 9a62a8d918ae8769c3ed0112927f268765e68f13 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:21:50 +0000 Subject: [PATCH 05/44] ci: Ignore `unpinned-uses` for `josh-sync/rustc-pull` See: https://rust-lang.zulipchat.com/#narrow/channel/242791-t-infra/topic/josh-sync.20pull.20workflow.20version.20pinning/near/629742031 Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml index 0a2cfd1c85985..675539db15762 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml @@ -9,6 +9,7 @@ on: jobs: pull: if: github.repository == 'rust-lang/rustc-dev-guide' + # zizmor: ignore[unpinned-uses] We explicitly want to track the main branch. uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main with: github-app-id: ${{ vars.APP_CLIENT_ID }} From 2aece4bbfe42be7bdb68f557c9c6d1ed39d48229 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:25:34 +0000 Subject: [PATCH 06/44] ci: Pin `actions/cache` to 4.3.0 hash See: https://github.com/actions/cache/releases/tag/v4.3.0 This is the same version we were already using at the time of writing. Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 23b590e4aebaf..369207e13a706 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: - name: Cache binaries id: mdbook-cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/bin @@ -41,7 +41,7 @@ jobs: - name: Restore cached Linkcheck if: github.event_name == 'schedule' id: cache-linkcheck-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: book/linkcheck/cache.json key: linkcheck--${{ env.MDBOOK_LINKCHECK2_VERSION }}--${{ github.run_id }} @@ -73,7 +73,7 @@ jobs: - name: Save cached Linkcheck id: cache-linkcheck-save if: ${{ !cancelled() && github.event_name == 'schedule' }} - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: book/linkcheck/cache.json key: linkcheck--${{ env.MDBOOK_LINKCHECK2_VERSION }}--${{ github.run_id }} From cc5cff8ae86942db56bc5a38de47815b621b9806 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:26:59 +0000 Subject: [PATCH 07/44] ci: Pin `actions/checkout` to 7.0.1 hash See: https://github.com/actions/checkout/releases/tag/v7.0.1 This is the same version we were already using at the time of writing. Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/date-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml index fb7ff3bc04b11..6010414eaaf28 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Ensure Rust is up-to-date run: | From 3d1d5a077477d7905057230381ccfbb86907ca88 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Wed, 7 Oct 2026 18:28:35 +0000 Subject: [PATCH 08/44] ci: Pin `actions/github-script` to 7.1.0 hash See: https://github.com/actions/github-script/releases/tag/v7.1.0 This is the same version we were already using at the time of writing. Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/date-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml index 6010414eaaf28..a3e807f71c696 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml @@ -25,7 +25,7 @@ jobs: run: cargo run --manifest-path ci/date-check/Cargo.toml . > date-check-output.txt - name: Open issue - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const fs = require('fs'); From 0f9d598ded94bb40d885342f0dcd319617504952 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:02:39 +0000 Subject: [PATCH 09/44] ci: Disable credential persistence Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 5 +++++ src/doc/rustc-dev-guide/.github/workflows/date-check.yml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 369207e13a706..3ab6b6dc469bf 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -26,6 +26,7 @@ jobs: with: # linkcheck needs the base commit. fetch-depth: 0 + persist-credentials: false - name: Test CI scripts run: ci/tests/report-scheduled-linkcheck-failure.sh @@ -109,6 +110,8 @@ jobs: issues: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Open an issue or comment on an existing one run: ci/report-scheduled-linkcheck-failure.sh env: @@ -124,6 +127,8 @@ jobs: issues: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Close issue run: ci/close-scheduled-linkcheck-issues.sh env: diff --git a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml index a3e807f71c696..3e31067a89f26 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml @@ -16,6 +16,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Ensure Rust is up-to-date run: | From afeff76f18aaa7db24dc5f2284ae5a2d17cfaee0 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:07:57 +0000 Subject: [PATCH 10/44] ci: Prefer shell expansion over template expansion Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 3ab6b6dc469bf..6b0917f6f4776 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -58,9 +58,9 @@ jobs: - name: Install Dependencies if: steps.mdbook-cache.outputs.cache-hit != 'true' run: | - cargo install --locked mdbook --version ${{ env.MDBOOK_VERSION }} - cargo install --locked mdbook-linkcheck2 --version ${{ env.MDBOOK_LINKCHECK2_VERSION }} - cargo install --locked mdbook-mermaid --version ${{ env.MDBOOK_MERMAID_VERSION }} + cargo install --locked mdbook --version ${MDBOOK_VERSION} + cargo install --locked mdbook-linkcheck2 --version ${MDBOOK_LINKCHECK2_VERSION} + cargo install --locked mdbook-mermaid --version ${MDBOOK_MERMAID_VERSION} - name: Check build run: ENABLE_LINKCHECK=1 mdbook build @@ -82,16 +82,16 @@ jobs: - name: Deploy to gh-pages if: github.event_name == 'push' run: | - touch "${{ env.DEPLOY_DIR }}/.nojekyll" - cp CNAME "${{ env.DEPLOY_DIR }}" - cd "${{ env.DEPLOY_DIR }}" + touch "${DEPLOY_DIR}/.nojekyll" + cp CNAME "${DEPLOY_DIR}" + cd "${DEPLOY_DIR}" rm -rf .git git init git config user.name "Deploy from CI" git config user.email "" git add . git commit -m "Deploy ${GITHUB_SHA} to gh-pages" - git push --quiet -f "https://x-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}" HEAD:gh-pages + git push --quiet -f "https://x-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}" HEAD:gh-pages - name: Check if files comply with semantic line breaks continue-on-error: true From e6c00a8e92b2b3f789e76df387aee4619143868e Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:13:56 +0000 Subject: [PATCH 11/44] ci: Disable all permissions by default Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 2 ++ src/doc/rustc-dev-guide/.github/workflows/date-check.yml | 2 ++ src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 6b0917f6f4776..8ba4b8c20c246 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -9,6 +9,8 @@ on: # Run multiple times a day as the successful cached links are not checked every time. - cron: "0 */8 * * *" +permissions: {} + jobs: ci: if: github.repository == 'rust-lang/rustc-dev-guide' diff --git a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml index 3e31067a89f26..55544e6d71b05 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml @@ -8,6 +8,8 @@ on: # Allow manually starting the workflow workflow_dispatch: +permissions: {} + jobs: date-check: if: github.repository == 'rust-lang/rustc-dev-guide' diff --git a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml index 675539db15762..5c4a33d59477c 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml @@ -6,6 +6,8 @@ on: # Run at 04:00 UTC every Monday - cron: '0 4 * * 1' +permissions: {} + jobs: pull: if: github.repository == 'rust-lang/rustc-dev-guide' From a50f3262305fd2f2a5b061d8f145932fc15deee3 Mon Sep 17 00:00:00 2001 From: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:31:50 +0000 Subject: [PATCH 12/44] ci: Grant minimum required permissions Signed-off-by: hashcatHitman <155700084+hashcatHitman@users.noreply.github.com> --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 9 ++++----- src/doc/rustc-dev-guide/.github/workflows/date-check.yml | 3 ++- src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 8ba4b8c20c246..afdf7ca185e80 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: ci: if: github.repository == 'rust-lang/rustc-dev-guide' runs-on: ubuntu-latest + permissions: + contents: write # Required by `git push` env: MDBOOK_VERSION: 0.5.2 MDBOOK_LINKCHECK2_VERSION: 0.13.0 @@ -107,9 +109,7 @@ jobs: if: failure() && github.event_name == 'schedule' runs-on: ubuntu-latest permissions: - actions: read - contents: read - issues: write + issues: write # Required to open issues with `gh issue create` steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -125,8 +125,7 @@ jobs: if: success() && github.event_name == 'schedule' runs-on: ubuntu-latest permissions: - contents: read - issues: write + issues: write # Required to close issues with `gh issue close` steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml index 55544e6d71b05..868891c786c24 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/date-check.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/date-check.yml @@ -14,7 +14,8 @@ jobs: date-check: if: github.repository == 'rust-lang/rustc-dev-guide' runs-on: ubuntu-latest - + permissions: + issues: write # Required by `actions/github-script` to open issues steps: - name: Checkout repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml index 5c4a33d59477c..fa967b3ee5bb1 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/rustc-pull.yml @@ -11,6 +11,9 @@ permissions: {} jobs: pull: if: github.repository == 'rust-lang/rustc-dev-guide' + permissions: + contents: write # Required by `josh-sync/rustc-pull` to push to a branch + pull-requests: write # Required by `josh-sync/rustc-pull` to open a PR # zizmor: ignore[unpinned-uses] We explicitly want to track the main branch. uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main with: From af54de29f29c0925dd53f0a264dc26911b676cbf Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:46:58 +0100 Subject: [PATCH 13/44] Fix alloctests for panic=abort This adds a cfg_attr(not(panic = "unwind"), ignore) to all sorting tests that use catch_unwind internally. --- ...oot_tests-Disable-long-running-tests.patch | 10 -- library/alloctests/tests/sort/tests.rs | 142 ++++++++++-------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/patches/0028-sysroot_tests-Disable-long-running-tests.patch b/compiler/rustc_codegen_cranelift/patches/0028-sysroot_tests-Disable-long-running-tests.patch index e503acf8fee8c..edbb26945ceb0 100644 --- a/compiler/rustc_codegen_cranelift/patches/0028-sysroot_tests-Disable-long-running-tests.patch +++ b/compiler/rustc_codegen_cranelift/patches/0028-sysroot_tests-Disable-long-running-tests.patch @@ -46,16 +46,6 @@ index 8402833..84592e0 100644 split_off_tests! { slice: &mut [(); usize::MAX], method: split_off_mut, (split_off_mut_in_bounds_max_range_to, (..usize::MAX), Some(empty_max_mut!()), &mut [(); 0]), -diff --git a/alloctests/tests/sort/tests.rs b/alloctests/tests/sort/tests.rs -index d321f8d..8b2040a 100644 ---- a/alloctests/tests/sort/tests.rs -+++ b/alloctests/tests/sort/tests.rs -@@ -1,3 +1,5 @@ -+#![cfg(any())] -+ - use std::cell::Cell; - use std::cmp::Ordering; - use std::fmt::Debug; diff --git a/alloctests/tests/str.rs b/alloctests/tests/str.rs index 906fa2d..b82fa99 100644 --- a/alloctests/tests/str.rs diff --git a/library/alloctests/tests/sort/tests.rs b/library/alloctests/tests/sort/tests.rs index ec4c4fa619ddf..2802a13e1faa3 100644 --- a/library/alloctests/tests/sort/tests.rs +++ b/library/alloctests/tests/sort/tests.rs @@ -927,24 +927,33 @@ gen_sort_test_fns_with_default_patterns_3_ty!( ); macro_rules! instantiate_sort_test_inner { - ($sort_impl:ty, miri_yes, $test_fn_name:ident) => { + ($test_fn_name:ident, $sort_impl:ty, (), [$($meta:meta),*]) => { #[test] + $(#[$meta])* fn $test_fn_name() { $crate::sort::tests::$test_fn_name::<$sort_impl>(); } }; - ($sort_impl:ty, miri_no, $test_fn_name:ident) => { - #[test] - #[cfg_attr(miri, ignore)] - fn $test_fn_name() { - $crate::sort::tests::$test_fn_name::<$sort_impl>(); - } + ($test_fn_name:ident, $sort_impl:ty, (miri_no $(, $attr:ident)*), [$($meta:meta),*]) => { + instantiate_sort_test_inner! ( + $test_fn_name, $sort_impl, ($($attr)*), [$($meta,)* cfg_attr(miri, ignore)] + ); + }; + ($test_fn_name:ident, $sort_impl:ty, (miri_yes $(, $attr:ident)*), [$($meta:meta),*]) => { + instantiate_sort_test_inner! ( + $test_fn_name, $sort_impl, ($($attr)*), [$($meta),*] + ); + }; + ($test_fn_name:ident, $sort_impl:ty, (panic $(, $attr:ident)*), [$($meta:meta),*]) => { + instantiate_sort_test_inner! ( + $test_fn_name, $sort_impl, ($($attr)*), [$($meta,)* cfg_attr(not(panic = "unwind"), ignore)] + ); }; } // Using this construct allows us to get warnings for unused test functions. macro_rules! define_instantiate_sort_tests { - ($([$miri_use:ident, $test_fn_name:ident]),*,) => { + ($([$miri_use:ident, $test_fn_name:ident $(, $attr:ident)*]),*,) => { $(pub fn $test_fn_name() { ${concat($test_fn_name, _impl)}::(); })* @@ -954,9 +963,10 @@ macro_rules! define_instantiate_sort_tests { ($sort_impl:ty) => { $( instantiate_sort_test_inner!( + $test_fn_name, $sort_impl, - $miri_use, - $test_fn_name + ($miri_use $(, $attr)*), + [] ); )* } @@ -1083,38 +1093,38 @@ define_instantiate_sort_tests!( [miri_no, observable_is_less_ascending], [miri_no, observable_is_less_descending], [miri_no, observable_is_less_saw_mixed], - [miri_no, panic_retain_orig_set_i32_random], - [miri_yes, panic_retain_orig_set_i32_random_z1], - [miri_no, panic_retain_orig_set_i32_random_d2], - [miri_no, panic_retain_orig_set_i32_random_d20], - [miri_no, panic_retain_orig_set_i32_random_s95], - [miri_no, panic_retain_orig_set_i32_ascending], - [miri_no, panic_retain_orig_set_i32_descending], - [miri_no, panic_retain_orig_set_i32_saw_mixed], - [miri_no, panic_retain_orig_set_cell_i32_random], - [miri_yes, panic_retain_orig_set_cell_i32_random_z1], - [miri_no, panic_retain_orig_set_cell_i32_random_d2], - [miri_no, panic_retain_orig_set_cell_i32_random_d20], - [miri_no, panic_retain_orig_set_cell_i32_random_s95], - [miri_no, panic_retain_orig_set_cell_i32_ascending], - [miri_no, panic_retain_orig_set_cell_i32_descending], - [miri_no, panic_retain_orig_set_cell_i32_saw_mixed], - [miri_no, panic_retain_orig_set_string_random], - [miri_yes, panic_retain_orig_set_string_random_z1], - [miri_no, panic_retain_orig_set_string_random_d2], - [miri_no, panic_retain_orig_set_string_random_d20], - [miri_no, panic_retain_orig_set_string_random_s95], - [miri_no, panic_retain_orig_set_string_ascending], - [miri_no, panic_retain_orig_set_string_descending], - [miri_no, panic_retain_orig_set_string_saw_mixed], - [miri_no, panic_observable_is_less_random], - [miri_yes, panic_observable_is_less_random_z1], - [miri_no, panic_observable_is_less_random_d2], - [miri_no, panic_observable_is_less_random_d20], - [miri_no, panic_observable_is_less_random_s95], - [miri_no, panic_observable_is_less_ascending], - [miri_no, panic_observable_is_less_descending], - [miri_no, panic_observable_is_less_saw_mixed], + [miri_no, panic_retain_orig_set_i32_random, panic], + [miri_yes, panic_retain_orig_set_i32_random_z1, panic], + [miri_no, panic_retain_orig_set_i32_random_d2, panic], + [miri_no, panic_retain_orig_set_i32_random_d20, panic], + [miri_no, panic_retain_orig_set_i32_random_s95, panic], + [miri_no, panic_retain_orig_set_i32_ascending, panic], + [miri_no, panic_retain_orig_set_i32_descending, panic], + [miri_no, panic_retain_orig_set_i32_saw_mixed, panic], + [miri_no, panic_retain_orig_set_cell_i32_random, panic], + [miri_yes, panic_retain_orig_set_cell_i32_random_z1, panic], + [miri_no, panic_retain_orig_set_cell_i32_random_d2, panic], + [miri_no, panic_retain_orig_set_cell_i32_random_d20, panic], + [miri_no, panic_retain_orig_set_cell_i32_random_s95, panic], + [miri_no, panic_retain_orig_set_cell_i32_ascending, panic], + [miri_no, panic_retain_orig_set_cell_i32_descending, panic], + [miri_no, panic_retain_orig_set_cell_i32_saw_mixed, panic], + [miri_no, panic_retain_orig_set_string_random, panic], + [miri_yes, panic_retain_orig_set_string_random_z1, panic], + [miri_no, panic_retain_orig_set_string_random_d2, panic], + [miri_no, panic_retain_orig_set_string_random_d20, panic], + [miri_no, panic_retain_orig_set_string_random_s95, panic], + [miri_no, panic_retain_orig_set_string_ascending, panic], + [miri_no, panic_retain_orig_set_string_descending, panic], + [miri_no, panic_retain_orig_set_string_saw_mixed, panic], + [miri_no, panic_observable_is_less_random, panic], + [miri_yes, panic_observable_is_less_random_z1, panic], + [miri_no, panic_observable_is_less_random_d2, panic], + [miri_no, panic_observable_is_less_random_d20, panic], + [miri_no, panic_observable_is_less_random_s95, panic], + [miri_no, panic_observable_is_less_ascending, panic], + [miri_no, panic_observable_is_less_descending, panic], + [miri_no, panic_observable_is_less_saw_mixed, panic], [miri_no, deterministic_i32_random], [miri_yes, deterministic_i32_random_z1], [miri_no, deterministic_i32_random_d2], @@ -1163,30 +1173,30 @@ define_instantiate_sort_tests!( [miri_no, self_cmp_string_ascending], [miri_no, self_cmp_string_descending], [miri_no, self_cmp_string_saw_mixed], - [miri_no, violate_ord_retain_orig_set_i32_random], - [miri_yes, violate_ord_retain_orig_set_i32_random_z1], - [miri_no, violate_ord_retain_orig_set_i32_random_d2], - [miri_no, violate_ord_retain_orig_set_i32_random_d20], - [miri_no, violate_ord_retain_orig_set_i32_random_s95], - [miri_no, violate_ord_retain_orig_set_i32_ascending], - [miri_no, violate_ord_retain_orig_set_i32_descending], - [miri_no, violate_ord_retain_orig_set_i32_saw_mixed], - [miri_no, violate_ord_retain_orig_set_cell_i32_random], - [miri_yes, violate_ord_retain_orig_set_cell_i32_random_z1], - [miri_no, violate_ord_retain_orig_set_cell_i32_random_d2], - [miri_no, violate_ord_retain_orig_set_cell_i32_random_d20], - [miri_no, violate_ord_retain_orig_set_cell_i32_random_s95], - [miri_no, violate_ord_retain_orig_set_cell_i32_ascending], - [miri_no, violate_ord_retain_orig_set_cell_i32_descending], - [miri_no, violate_ord_retain_orig_set_cell_i32_saw_mixed], - [miri_no, violate_ord_retain_orig_set_string_random], - [miri_yes, violate_ord_retain_orig_set_string_random_z1], - [miri_no, violate_ord_retain_orig_set_string_random_d2], - [miri_no, violate_ord_retain_orig_set_string_random_d20], - [miri_no, violate_ord_retain_orig_set_string_random_s95], - [miri_no, violate_ord_retain_orig_set_string_ascending], - [miri_no, violate_ord_retain_orig_set_string_descending], - [miri_no, violate_ord_retain_orig_set_string_saw_mixed], + [miri_no, violate_ord_retain_orig_set_i32_random, panic], + [miri_yes, violate_ord_retain_orig_set_i32_random_z1, panic], + [miri_no, violate_ord_retain_orig_set_i32_random_d2, panic], + [miri_no, violate_ord_retain_orig_set_i32_random_d20, panic], + [miri_no, violate_ord_retain_orig_set_i32_random_s95, panic], + [miri_no, violate_ord_retain_orig_set_i32_ascending, panic], + [miri_no, violate_ord_retain_orig_set_i32_descending, panic], + [miri_no, violate_ord_retain_orig_set_i32_saw_mixed, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_random, panic], + [miri_yes, violate_ord_retain_orig_set_cell_i32_random_z1, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_random_d2, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_random_d20, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_random_s95, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_ascending, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_descending, panic], + [miri_no, violate_ord_retain_orig_set_cell_i32_saw_mixed, panic], + [miri_no, violate_ord_retain_orig_set_string_random, panic], + [miri_yes, violate_ord_retain_orig_set_string_random_z1, panic], + [miri_no, violate_ord_retain_orig_set_string_random_d2, panic], + [miri_no, violate_ord_retain_orig_set_string_random_d20, panic], + [miri_no, violate_ord_retain_orig_set_string_random_s95, panic], + [miri_no, violate_ord_retain_orig_set_string_ascending, panic], + [miri_no, violate_ord_retain_orig_set_string_descending, panic], + [miri_no, violate_ord_retain_orig_set_string_saw_mixed, panic], ); macro_rules! instantiate_sort_tests { From 0b2bf3eda375789724e20bae9da7dbd695d72b90 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 1 Nov 2026 01:20:56 +1100 Subject: [PATCH 14/44] Move `llvm::Context` into a new submodule This module makes it easier to restrict the visibility of safety-critical fields. --- .../src/back/llvm_backend.rs | 24 +++++++---------- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 13 +++------ .../src/llvm/ffi/branded.rs | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 16465436e239e..2d64cfac5f5de 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -461,22 +461,15 @@ pub struct ModuleLlvm { // `ModuleLlvm` owns the context and only allows borrowing LLVM objects tied to the context. unsafe impl Send for ModuleLlvm {} -/// Converts an unbranded pointer-to-context to a lifetime-branded [`llvm::Context`]. -/// -/// # Safety -/// - The pointer must be a valid LLVM context. -/// - If the context is passed to safe code, its caller-requested lifetime must be distinct -/// from that of any other context, and must not outlive the underlying context. -unsafe fn wrap_llcx<'ll>(llcx_raw: ptr::NonNull) -> llvm::Context<'ll> { - llvm::Context { llcx_raw, _invariant_ll: PhantomData } -} - /// Shared helper to call [`llvm::LLVMContextCreate`] and then configure /// [`llvm::LLVMContextSetDiscardValueNames`]. fn create_context_and_set_fewer_names(fewer_names: bool) -> ptr::NonNull { unsafe { let llcx_raw = llvm::LLVMContextCreate(); - llvm::LLVMContextSetDiscardValueNames(wrap_llcx(llcx_raw), fewer_names.to_llvm_bool()); + llvm::LLVMContextSetDiscardValueNames( + llvm::wrap_llcx(llcx_raw), + fewer_names.to_llvm_bool(), + ); llcx_raw } } @@ -485,7 +478,7 @@ impl ModuleLlvm { pub(crate) fn new(tcx: TyCtxt<'_>, mod_name: &str) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(tcx.sess.fewer_names()); - let llmod_raw = context::create_module(tcx, wrap_llcx(llcx_raw), mod_name); + let llmod_raw = context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name); ModuleLlvm { llmod_raw, llcx_raw, @@ -498,7 +491,7 @@ impl ModuleLlvm { pub(crate) fn new_metadata(tcx: TyCtxt<'_>, mod_name: &str) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(tcx.sess.fewer_names()); - let llmod_raw = context::create_module(tcx, wrap_llcx(llcx_raw), mod_name); + let llmod_raw = context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name); ModuleLlvm { llmod_raw, llcx_raw, @@ -517,7 +510,7 @@ impl ModuleLlvm { ) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(cgcx.fewer_names); - let llmod_raw = back::lto::parse_module(wrap_llcx(llcx_raw), name, buffer, dcx); + let llmod_raw = back::lto::parse_module(llvm::wrap_llcx(llcx_raw), name, buffer, dcx); let tm = tm_factory(dcx, TargetMachineFactoryConfig::new(cgcx, name.to_str().unwrap())); ModuleLlvm { @@ -538,8 +531,9 @@ impl ModuleLlvm { &self, inner_fn: impl for<'ll> FnOnce(ModuleRefs<'ll>) -> R, ) -> R { + let llcx = unsafe { llvm::wrap_llcx(self.llcx_raw) }; inner_fn(ModuleRefs { - llcx: unsafe { wrap_llcx(self.llcx_raw) }, + llcx, llmod: unsafe { &*self.llmod_raw }, tm: self.tm.raw(), _ph: PhantomData, diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 7e6c4869f0b26..b9e30e5b7d3ae 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -20,6 +20,7 @@ use std::ptr; use bitflags::bitflags; use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_void, size_t}; +pub(crate) use self::branded::{Context, ContextRaw, wrap_llcx}; use super::RustString; use super::debuginfo::{ DIArray, DIBuilder, DIDerivedType, DIDescriptor, DIFile, DIFlags, DILocation, DISPFlags, @@ -29,6 +30,8 @@ use crate::llvm; use crate::llvm::MetadataKindId; use crate::macros::TryFromU32; +mod branded; + /// In the LLVM-C API, boolean values are passed as `typedef int LLVMBool`, /// which has a different ABI from Rust or C++ `bool`. /// @@ -701,17 +704,7 @@ struct InvariantOpaque<'a> { // Opaque pointer types -/// Corresponds to `LLVMContextRef`. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct Context<'ll> { - pub(crate) llcx_raw: ptr::NonNull, - pub(crate) _invariant_ll: PhantomData &'ll ()>, -} - unsafe extern "C" { - /// Pointee of the raw pointer that is wrapped by a lifetime-branded [`Context`]. - pub(crate) type ContextRaw; pub(crate) type Module; pub(crate) type Type; pub(crate) type Value; diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs new file mode 100644 index 0000000000000..be5233c9b2d9c --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs @@ -0,0 +1,27 @@ +//! Lifetime-branded references to things manipulated by the LLVM bindings. + +use std::marker::PhantomData; +use std::ptr; + +/// Corresponds to `LLVMContextRef`. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub(crate) struct Context<'ll> { + llcx_raw: ptr::NonNull, + _invariant_ll: PhantomData &'ll ()>, +} + +unsafe extern "C" { + /// Pointee of the raw pointer that is wrapped by a lifetime-branded [`Context`]. + pub(crate) type ContextRaw; +} + +/// Converts an unbranded pointer-to-context to a lifetime-branded [`Context`]. +/// +/// # Safety +/// - The pointer must be a valid LLVM context. +/// - If the context is passed to safe code, its caller-requested lifetime must be distinct +/// from that of any other context, and must not outlive the underlying context. +pub(crate) unsafe fn wrap_llcx<'ll>(llcx_raw: ptr::NonNull) -> Context<'ll> { + Context { llcx_raw, _invariant_ll: PhantomData } +} From d0eb8f903472ab5267a8718b7d89f4101e29d6f1 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 1 Nov 2026 01:20:56 +1100 Subject: [PATCH 15/44] Use an invariant lifetime brand for LLVM modules This commit changes the type of LLVM module references from `&'ll Module` to a newtype struct `Module<'ll>`, similar to the existing `Context<'ll>`. --- .../src/back/llvm_backend.rs | 21 +-- compiler/rustc_codegen_llvm/src/back/lto.rs | 4 +- compiler/rustc_codegen_llvm/src/back/write.rs | 12 +- compiler/rustc_codegen_llvm/src/base.rs | 4 +- compiler/rustc_codegen_llvm/src/context.rs | 17 +- .../src/coverageinfo/llvm_cov.rs | 4 +- .../src/debuginfo/di_builder.rs | 2 +- .../rustc_codegen_llvm/src/debuginfo/mod.rs | 2 +- .../rustc_codegen_llvm/src/llvm/enzyme_ffi.rs | 9 +- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 173 ++++++++++-------- .../src/llvm/ffi/branded.rs | 28 +++ compiler/rustc_codegen_llvm/src/llvm/mod.rs | 20 +- .../src/llvm/offload_ffi.rs | 6 +- compiler/rustc_codegen_llvm/src/type_.rs | 2 +- compiler/rustc_codegen_llvm/src/typetree.rs | 2 +- 15 files changed, 179 insertions(+), 127 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 2d64cfac5f5de..69d63c52e4b88 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -443,7 +443,7 @@ impl CodegenBackend for LlvmCodegenBackend { pub struct ModuleLlvm { llcx_raw: ptr::NonNull, - llmod_raw: *const llvm::Module, + llmod_raw: ptr::NonNull, // This field is `ManuallyDrop` because it is important that the `TargetMachine` // is disposed prior to the `Context` being disposed otherwise UAFs can occur. @@ -478,7 +478,8 @@ impl ModuleLlvm { pub(crate) fn new(tcx: TyCtxt<'_>, mod_name: &str) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(tcx.sess.fewer_names()); - let llmod_raw = context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name); + let llmod_raw = + context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name).llmod_raw(); ModuleLlvm { llmod_raw, llcx_raw, @@ -491,7 +492,8 @@ impl ModuleLlvm { pub(crate) fn new_metadata(tcx: TyCtxt<'_>, mod_name: &str) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(tcx.sess.fewer_names()); - let llmod_raw = context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name); + let llmod_raw = + context::create_module(tcx, llvm::wrap_llcx(llcx_raw), mod_name).llmod_raw(); ModuleLlvm { llmod_raw, llcx_raw, @@ -510,7 +512,8 @@ impl ModuleLlvm { ) -> Self { unsafe { let llcx_raw = create_context_and_set_fewer_names(cgcx.fewer_names); - let llmod_raw = back::lto::parse_module(llvm::wrap_llcx(llcx_raw), name, buffer, dcx); + let llmod_raw = + back::lto::parse_module(llvm::wrap_llcx(llcx_raw), name, buffer, dcx).llmod_raw(); let tm = tm_factory(dcx, TargetMachineFactoryConfig::new(cgcx, name.to_str().unwrap())); ModuleLlvm { @@ -532,12 +535,8 @@ impl ModuleLlvm { inner_fn: impl for<'ll> FnOnce(ModuleRefs<'ll>) -> R, ) -> R { let llcx = unsafe { llvm::wrap_llcx(self.llcx_raw) }; - inner_fn(ModuleRefs { - llcx, - llmod: unsafe { &*self.llmod_raw }, - tm: self.tm.raw(), - _ph: PhantomData, - }) + let llmod = unsafe { llvm::wrap_llmod(llcx, self.llmod_raw) }; + inner_fn(ModuleRefs { llcx, llmod, tm: self.tm.raw(), _ph: PhantomData }) } } @@ -554,7 +553,7 @@ impl Drop for ModuleLlvm { #[derive(Clone, Copy)] pub(crate) struct ModuleRefs<'ll> { pub(crate) llcx: llvm::Context<'ll>, - pub(crate) llmod: &'ll llvm::Module, + pub(crate) llmod: llvm::Module<'ll>, pub(crate) tm: &'ll llvm::TargetMachine, /// Make sure `'ll` is invariant, even if no fields happen to require that. /// diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 4ba4ca6e03b89..8258c1f7c8d20 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -695,7 +695,7 @@ pub struct ModuleBuffer { } impl ModuleBuffer { - pub(crate) fn new(m: &llvm::Module, is_thin: bool) -> ModuleBuffer { + pub(crate) fn new<'ll>(m: llvm::Module<'ll>, is_thin: bool) -> ModuleBuffer { unsafe { let buffer = llvm::LLVMRustModuleSerialize(m, is_thin); ModuleBuffer { data: Buffer(buffer) } @@ -859,7 +859,7 @@ pub(crate) fn parse_module<'ll>( name: &CStr, data: &[u8], dcx: DiagCtxtHandle<'_>, -) -> &'ll llvm::Module { +) -> llvm::Module<'ll> { unsafe { llvm::LLVMRustParseBitcodeForLTO(llcx, data.as_ptr(), data.len(), name.as_ptr()) .unwrap_or_else(|| write::llvm_err(dcx, LlvmError::ParseBitcode)) diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 56f855b340440..2a130a9358c4f 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -54,7 +54,7 @@ fn write_output_file<'ll>( dcx: DiagCtxtHandle<'_>, target: &'ll llvm::TargetMachine, no_builtins: bool, - m: &'ll llvm::Module, + m: llvm::Module<'ll>, output: &Path, dwo_output: Option<&Path>, file_type: llvm::FileType, @@ -1207,7 +1207,7 @@ pub(crate) fn bitcode_section_name(cgcx: &CodegenContext) -> &'static CStr { fn embed_bitcode<'ll>( cgcx: &CodegenContext, llcx: llvm::Context<'ll>, - llmod: &'ll llvm::Module, + llmod: llvm::Module<'ll>, bitcode: &[u8], ) { // We're adding custom sections to the output object file, but we definitely @@ -1293,7 +1293,7 @@ fn embed_bitcode<'ll>( fn create_msvc_imps<'ll>( cgcx: &CodegenContext, llcx: llvm::Context<'ll>, - llmod: &'ll llvm::Module, + llmod: llvm::Module<'ll>, ) { if !cgcx.msvc_imps_needed { return; @@ -1359,7 +1359,11 @@ fn record_artifact_size( } } -fn record_llvm_cgu_instructions_stats(prof: &SelfProfilerRef, name: &str, llmod: &llvm::Module) { +fn record_llvm_cgu_instructions_stats<'ll>( + prof: &SelfProfilerRef, + name: &str, + llmod: llvm::Module<'ll>, +) { if !prof.enabled() { return; } diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 87eb157a8892f..7909072db1d8d 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -50,11 +50,11 @@ impl<'ll> Iterator for ValueIter<'ll> { } } -pub(crate) fn iter_globals(llmod: &llvm::Module) -> ValueIter<'_> { +pub(crate) fn iter_globals<'ll>(llmod: llvm::Module<'ll>) -> ValueIter<'ll> { unsafe { ValueIter { cur: llvm::LLVMGetFirstGlobal(llmod), step: llvm::LLVMGetNextGlobal } } } -pub(crate) fn iter_global_aliases(llmod: &llvm::Module) -> ValueIter<'_> { +pub(crate) fn iter_global_aliases<'ll>(llmod: llvm::Module<'ll>) -> ValueIter<'ll> { unsafe { ValueIter { cur: llvm::LLVMGetFirstGlobalAlias(llmod), step: llvm::LLVMGetNextGlobalAlias } } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 8ef5ce26e1c2d..9915e7af1cd1b 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -47,7 +47,7 @@ use crate::{attributes, common, coverageinfo, debuginfo, llvm_util}; /// other compiler pieces. Here we define a small subset which has enough information and can be /// moved around more freely. pub(crate) struct SCx<'ll> { - pub llmod: &'ll llvm::Module, + pub llmod: llvm::Module<'ll>, pub llcx: llvm::Context<'ll>, pub isize_ty: &'ll Type, } @@ -186,15 +186,14 @@ pub(crate) unsafe fn create_module<'ll>( tcx: TyCtxt<'_>, llcx: llvm::Context<'ll>, mod_name: &str, -) -> *const llvm::Module { +) -> llvm::Module<'ll> { let mod_name_cstr = SmallCStr::new(mod_name); - let llmod_raw = - unsafe { llvm::LLVMModuleCreateWithNameInContext(mod_name_cstr.as_ptr(), llcx) }; - init_module(tcx, llcx, unsafe { &*llmod_raw }); - llmod_raw + let llmod = unsafe { llvm::LLVMModuleCreateWithNameInContext(mod_name_cstr.as_ptr(), llcx) }; + init_module(tcx, llcx, llmod); + llmod } -fn init_module<'ll, 'tcx>(tcx: TyCtxt<'tcx>, llcx: llvm::Context<'ll>, llmod: &'ll llvm::Module) { +fn init_module<'ll, 'tcx>(tcx: TyCtxt<'tcx>, llcx: llvm::Context<'ll>, llmod: llvm::Module<'ll>) { let sess = tcx.sess; let cx = SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size()); @@ -880,7 +879,7 @@ impl<'ll> SimpleCx<'ll> { } impl<'ll> SimpleCx<'ll> { pub(crate) fn new( - llmod: &'ll llvm::Module, + llmod: llvm::Module<'ll>, llcx: llvm::Context<'ll>, pointer_size: Size, ) -> Self { @@ -1226,7 +1225,7 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { /// - `LLVMAddNamedMetadataOperand` (to set that node as metadata of `kind_name` for `module`) pub(crate) fn module_add_named_metadata_node( &self, - module: &'ll Module, + module: Module<'ll>, kind_name: &CStr, md_list: &[&'ll Metadata], ) { diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs index 93d0b09578315..92abe16eb4109 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs @@ -12,14 +12,14 @@ pub(crate) fn covmap_var_name() -> CString { .expect("covmap variable name should not contain NUL") } -pub(crate) fn covmap_section_name(llmod: &llvm::Module) -> CString { +pub(crate) fn covmap_section_name<'ll>(llmod: llvm::Module<'ll>) -> CString { CString::new(llvm::build_byte_buffer(|s| { llvm::LLVMRustCoverageWriteCovmapSectionNameToString(llmod, s); })) .expect("covmap section name should not contain NUL") } -pub(crate) fn covfun_section_name(llmod: &llvm::Module) -> CString { +pub(crate) fn covfun_section_name<'ll>(llmod: llvm::Module<'ll>) -> CString { CString::new(llvm::build_byte_buffer(|s| { llvm::LLVMRustCoverageWriteCovfunSectionNameToString(llmod, s); })) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs b/compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs index 0b5ef6c687404..9237c8be87473 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/di_builder.rs @@ -14,7 +14,7 @@ pub(crate) struct DIBuilderBox<'ll> { } impl<'ll> DIBuilderBox<'ll> { - pub(crate) fn new(llmod: &'ll Module) -> Self { + pub(crate) fn new(llmod: Module<'ll>) -> Self { let raw = unsafe { llvm::LLVMCreateDIBuilder(llmod) }; let raw = ptr::NonNull::new(raw).unwrap(); Self { raw } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index eb923316cc649..8654b0dd29992 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -61,7 +61,7 @@ pub(crate) struct CodegenUnitDebugContext<'ll, 'tcx> { } impl<'ll, 'tcx> CodegenUnitDebugContext<'ll, 'tcx> { - pub(crate) fn new(llmod: &'ll llvm::Module, sess: &Session) -> Self { + pub(crate) fn new(llmod: llvm::Module<'ll>, sess: &Session) -> Self { debug!("CodegenUnitDebugContext::new"); match sess.target.debuginfo_kind { diff --git a/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs index b0641b70d1189..9ec5b667f7c6c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs @@ -55,7 +55,7 @@ unsafe extern "C" { Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: libc::size_t, ); - pub(crate) fn LLVMGetFirstFunction(M: &Module) -> Option<&Value>; + pub(crate) fn LLVMGetFirstFunction<'ll>(M: Module<'ll>) -> Option<&'ll Value>; pub(crate) fn LLVMGetNextFunction(Fn: &Value) -> Option<&Value>; pub(crate) fn LLVMRustRemoveEnumAttributeAtIndex( Fn: &Value, @@ -75,11 +75,14 @@ unsafe extern "C" { unsafe extern "C" { // Enzyme - pub(crate) fn LLVMDumpModule(M: &Module); + pub(crate) fn LLVMDumpModule<'ll>(M: Module<'ll>); pub(crate) fn LLVMDumpValue(V: &Value); pub(crate) fn LLVMGetFunctionCallConv(F: &Value) -> c_uint; pub(crate) fn LLVMGetParams(Fnc: &Value, params: *mut &Value); - pub(crate) fn LLVMGetNamedFunction(M: &Module, Name: *const c_char) -> Option<&Value>; + pub(crate) fn LLVMGetNamedFunction<'ll>( + M: Module<'ll>, + Name: *const c_char, + ) -> Option<&'ll Value>; } #[repr(C)] diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index b9e30e5b7d3ae..5c49f5830444b 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -20,7 +20,7 @@ use std::ptr; use bitflags::bitflags; use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_void, size_t}; -pub(crate) use self::branded::{Context, ContextRaw, wrap_llcx}; +pub(crate) use self::branded::{Context, ContextRaw, Module, ModuleRaw, wrap_llcx, wrap_llmod}; use super::RustString; use super::debuginfo::{ DIArray, DIBuilder, DIDerivedType, DIDescriptor, DIFile, DIFlags, DILocation, DISPFlags, @@ -705,7 +705,6 @@ struct InvariantOpaque<'a> { // Opaque pointer types unsafe extern "C" { - pub(crate) type Module; pub(crate) type Type; pub(crate) type Value; pub(crate) type ConstantInt; @@ -911,12 +910,12 @@ unsafe extern "C" { pub(crate) fn LLVMModuleCreateWithNameInContext<'ll>( ModuleID: *const c_char, C: Context<'ll>, - ) -> *const Module; - pub(crate) safe fn LLVMCloneModule(M: &Module) -> &Module; + ) -> Module<'ll>; + pub(crate) safe fn LLVMCloneModule<'ll>(M: Module<'ll>) -> Module<'ll>; /// Data layout. See Module::getDataLayout. - pub(crate) fn LLVMGetDataLayoutStr(M: &Module) -> *const c_char; - pub(crate) fn LLVMSetDataLayout(M: &Module, Triple: *const c_char); + pub(crate) fn LLVMGetDataLayoutStr<'ll>(M: Module<'ll>) -> *const c_char; + pub(crate) fn LLVMSetDataLayout<'ll>(M: Module<'ll>, Triple: *const c_char); /// Create the specified uniqued inline asm string. See `InlineAsm::get()`. pub(crate) fn LLVMGetInlineAsm<'ll>( @@ -1029,10 +1028,10 @@ unsafe extern "C" { Vals: *const &'ll Metadata, Count: size_t, ) -> &'ll Metadata; - pub(crate) fn LLVMAddNamedMetadataOperand<'a>( - M: &'a Module, + pub(crate) fn LLVMAddNamedMetadataOperand<'ll>( + M: Module<'ll>, Name: *const c_char, - Val: &'a Value, + Val: &'ll Value, ); pub(crate) fn LLVMReplaceMDNodeOperandWith(Val: &Value, index: u32, replacement: &Metadata); @@ -1100,9 +1099,16 @@ unsafe extern "C" { // Operations on global variables pub(crate) safe fn LLVMIsAGlobalVariable(GlobalVar: &Value) -> Option<&Value>; - pub(crate) fn LLVMAddGlobal<'a>(M: &'a Module, Ty: &'a Type, Name: *const c_char) -> &'a Value; - pub(crate) fn LLVMGetNamedGlobal(M: &Module, Name: *const c_char) -> Option<&Value>; - pub(crate) fn LLVMGetFirstGlobal(M: &Module) -> Option<&Value>; + pub(crate) fn LLVMAddGlobal<'ll>( + M: Module<'ll>, + Ty: &'ll Type, + Name: *const c_char, + ) -> &'ll Value; + pub(crate) fn LLVMGetNamedGlobal<'ll>( + M: Module<'ll>, + Name: *const c_char, + ) -> Option<&'ll Value>; + pub(crate) fn LLVMGetFirstGlobal<'ll>(M: Module<'ll>) -> Option<&'ll Value>; pub(crate) fn LLVMGetNextGlobal(GlobalVar: &Value) -> Option<&Value>; pub(crate) fn LLVMDeleteGlobal(GlobalVar: &Value); pub(crate) safe fn LLVMGetInitializer(GlobalVar: &Value) -> Option<&Value>; @@ -1117,13 +1123,13 @@ unsafe extern "C" { // Operations on global aliases pub(crate) fn LLVMAddAlias2<'ll>( - M: &'ll Module, + M: Module<'ll>, ValueTy: &Type, AddressSpace: c_uint, Aliasee: &Value, Name: *const c_char, ) -> &'ll Value; - pub(crate) fn LLVMGetFirstGlobalAlias(M: &Module) -> Option<&Value>; + pub(crate) fn LLVMGetFirstGlobalAlias<'ll>(M: Module<'ll>) -> Option<&'ll Value>; pub(crate) fn LLVMGetNextGlobalAlias(GlobalAlias: &Value) -> Option<&Value>; // Operations on attributes @@ -1137,18 +1143,18 @@ unsafe extern "C" { // Operations on functions pub(crate) fn LLVMSetFunctionCallConv(Fn: &Value, CC: c_uint); - pub(crate) fn LLVMAddFunction<'a>( - Mod: &'a Module, + pub(crate) fn LLVMAddFunction<'ll>( + Mod: Module<'ll>, Name: *const c_char, - FunctionTy: &'a Type, - ) -> &'a Value; + FunctionTy: &'ll Type, + ) -> &'ll Value; pub(crate) fn LLVMDeleteFunction(Fn: &Value); - pub(crate) fn LLVMGetOrInsertFunction<'a>( - M: &'a Module, + pub(crate) fn LLVMGetOrInsertFunction<'ll>( + M: Module<'ll>, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, - FunctionTy: &'a Type, - ) -> &'a Value; + FunctionTy: &'ll Type, + ) -> &'ll Value; // Operations about llvm intrinsics pub(crate) fn LLVMLookupIntrinsicID( @@ -1156,12 +1162,12 @@ unsafe extern "C" { NameLen: size_t, ) -> c_uint; pub(crate) fn LLVMIntrinsicIsOverloaded(ID: NonZero) -> Bool; - pub(crate) fn LLVMGetIntrinsicDeclaration<'a>( - Mod: &'a Module, + pub(crate) fn LLVMGetIntrinsicDeclaration<'ll>( + Mod: Module<'ll>, ID: NonZero, - ParamTypes: *const &'a Type, + ParamTypes: *const &'ll Type, ParamCount: size_t, - ) -> &'a Value; + ) -> &'ll Value; pub(crate) fn LLVMRustUpgradeIntrinsicFunction<'a>( Fn: &'a Value, NewFn: &mut Option<&'a Value>, @@ -1202,7 +1208,7 @@ unsafe extern "C" { ); // Instruction builders - pub(crate) fn LLVMCreateBuilderForModule<'ll>(M: &'ll Module) -> ptr::NonNull>; + pub(crate) fn LLVMCreateBuilderForModule<'ll>(M: Module<'ll>) -> ptr::NonNull>; pub(crate) fn LLVMPositionBuilderAtEnd<'a>(Builder: &Builder<'a>, Block: &'a BasicBlock); pub(crate) fn LLVMGetInsertBlock<'a>(Builder: &Builder<'a>) -> &'a BasicBlock; pub(crate) fn LLVMDisposeBuilder<'ll>(Builder: ptr::NonNull>); @@ -1660,7 +1666,7 @@ unsafe extern "C" { ) -> &'a Value; /// Writes a module to the specified path. Returns 0 on success. - pub(crate) fn LLVMWriteBitcodeToFile(M: &Module, Path: *const c_char) -> c_int; + pub(crate) fn LLVMWriteBitcodeToFile<'ll>(M: Module<'ll>, Path: *const c_char) -> c_int; pub(crate) fn LLVMGetHostCPUFeatures() -> *mut c_char; @@ -1686,7 +1692,7 @@ unsafe extern "C" { pub(crate) fn LLVMIsAConstantInt(value_ref: &Value) -> Option<&ConstantInt>; - pub(crate) fn LLVMGetOrInsertComdat(M: &Module, Name: *const c_char) -> &Comdat; + pub(crate) fn LLVMGetOrInsertComdat<'ll>(M: Module<'ll>, Name: *const c_char) -> &'ll Comdat; pub(crate) fn LLVMSetComdat(V: &Value, C: &Comdat); pub(crate) fn LLVMCreateOperandBundle( @@ -1741,7 +1747,7 @@ unsafe extern "C" { // that they really are nullable on the C/C++ side. LLVM doesn't appear to // actually document which ones are nullable. unsafe extern "C" { - pub(crate) fn LLVMCreateDIBuilder<'ll>(M: &'ll Module) -> *mut DIBuilder<'ll>; + pub(crate) fn LLVMCreateDIBuilder<'ll>(M: Module<'ll>) -> *mut DIBuilder<'ll>; pub(crate) fn LLVMDisposeDIBuilder<'ll>(Builder: ptr::NonNull>); pub(crate) fn LLVMDIBuilderFinalize<'ll>(Builder: &DIBuilder<'ll>); @@ -2005,24 +2011,24 @@ unsafe extern "C" { pub(crate) fn LLVMRustSetDSOLocal(Global: &Value, is_dso_local: bool); // Operations on global variables - pub(crate) fn LLVMRustGetOrInsertGlobal<'a>( - M: &'a Module, + pub(crate) fn LLVMRustGetOrInsertGlobal<'ll>( + M: Module<'ll>, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, - T: &'a Type, - ) -> &'a Value; - pub(crate) fn LLVMRustGetOrInsertGlobalInAddrspace<'a>( - M: &'a Module, + T: &'ll Type, + ) -> &'ll Value; + pub(crate) fn LLVMRustGetOrInsertGlobalInAddrspace<'ll>( + M: Module<'ll>, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, - T: &'a Type, + T: &'ll Type, AddressSpace: c_uint, - ) -> &'a Value; - pub(crate) fn LLVMRustGetNamedValue( - M: &Module, + ) -> &'ll Value; + pub(crate) fn LLVMRustGetNamedValue<'ll>( + M: Module<'ll>, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, - ) -> Option<&Value>; + ) -> Option<&'ll Value>; // Operations on attributes pub(crate) fn LLVMRustCreateAttrNoValue<'ll>( @@ -2150,8 +2156,8 @@ unsafe extern "C" { ) -> bool; /// Append inline assembly to a module. See `Module::appendModuleInlineAsm`. - pub(crate) fn LLVMRustAppendModuleInlineAsm( - M: &Module, + pub(crate) fn LLVMRustAppendModuleInlineAsm<'ll>( + M: Module<'ll>, Asm: *const c_uchar, // See "PTR_LEN_STR". AsmLen: size_t, TargetFeatures: *const c_uchar, // See "PTR_LEN_STR". @@ -2195,12 +2201,12 @@ unsafe extern "C" { NumBytes: size_t, ) -> u64; - pub(crate) safe fn LLVMRustCoverageWriteCovmapSectionNameToString( - M: &Module, + pub(crate) safe fn LLVMRustCoverageWriteCovmapSectionNameToString<'ll>( + M: Module<'ll>, OutStr: &RustString, ); - pub(crate) safe fn LLVMRustCoverageWriteCovfunSectionNameToString( - M: &Module, + pub(crate) safe fn LLVMRustCoverageWriteCovfunSectionNameToString<'ll>( + M: Module<'ll>, OutStr: &RustString, ); pub(crate) safe fn LLVMRustCoverageWriteCovmapVarNameToString(OutStr: &RustString); @@ -2219,16 +2225,16 @@ unsafe extern "C" { /// /// In order for Rust-C LTO to work, module flags must be compatible with Clang. What /// "compatible" means depends on the merge behaviors involved. - pub(crate) fn LLVMRustAddModuleFlagU32( - M: &Module, + pub(crate) fn LLVMRustAddModuleFlagU32<'ll>( + M: Module<'ll>, MergeBehavior: ModuleFlagMergeBehavior, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, Value: u32, ); - pub(crate) fn LLVMRustAddModuleFlagString( - M: &Module, + pub(crate) fn LLVMRustAddModuleFlagString<'ll>( + M: Module<'ll>, MergeBehavior: ModuleFlagMergeBehavior, Name: *const c_uchar, // See "PTR_LEN_STR". NameLen: size_t, @@ -2458,18 +2464,18 @@ unsafe extern "C" { pub(crate) fn LLVMRustDisposeMCSubtargetInfo(MCInfo: ptr::NonNull); - pub(crate) fn LLVMRustWriteOutputFile<'a>( - T: &'a TargetMachine, - M: &'a Module, + pub(crate) fn LLVMRustWriteOutputFile<'ll>( + T: &'ll TargetMachine, + M: Module<'ll>, Output: *const c_char, DwoOutput: *const c_char, FileType: FileType, VerifyIR: bool, DisableSimplifyLibCalls: bool, ) -> LLVMRustResult; - pub(crate) fn LLVMRustOptimize<'a>( - M: &'a Module, - TM: &'a TargetMachine, + pub(crate) fn LLVMRustOptimize<'ll>( + M: Module<'ll>, + TM: &'ll TargetMachine, OptLevel: PassBuilderOptLevel, OptStage: OptStage, IsLinkerPluginLTO: bool, @@ -2505,15 +2511,19 @@ unsafe extern "C" { LLVMPlugins: *const c_uchar, // See "PTR_LEN_STR". LLVMPluginsLen: size_t, ) -> LLVMRustResult; - pub(crate) fn LLVMRustPrintModule( - M: &Module, + pub(crate) fn LLVMRustPrintModule<'ll>( + M: Module<'ll>, Output: *const c_char, Demangle: extern "C" fn(*const c_char, size_t, *mut c_char, size_t) -> size_t, ) -> LLVMRustResult; pub(crate) fn LLVMRustSetLLVMOptions(Argc: c_int, Argv: *const *const c_char); pub(crate) fn LLVMRustPrintPasses(); - pub(crate) fn LLVMRustSetNormalizedTarget(M: &Module, triple: *const c_char); - pub(crate) fn LLVMRustRunRestrictionPass(M: &Module, syms: *const *const c_char, len: size_t); + pub(crate) fn LLVMRustSetNormalizedTarget<'ll>(M: Module<'ll>, triple: *const c_char); + pub(crate) fn LLVMRustRunRestrictionPass<'ll>( + M: Module<'ll>, + syms: *const *const c_char, + len: size_t, + ); pub(crate) fn LLVMRustWriteTwineToString(T: &Twine, s: &RustString); @@ -2552,38 +2562,47 @@ unsafe extern "C" { num_ranges: &mut usize, ) -> bool; - pub(crate) fn LLVMRustSetDataLayoutFromTargetMachine<'a>(M: &'a Module, TM: &'a TargetMachine); + pub(crate) fn LLVMRustSetDataLayoutFromTargetMachine<'ll>(M: Module<'ll>, TM: &TargetMachine); pub(crate) fn LLVMRustPositionBuilderPastAllocas<'a>(B: &Builder<'a>, Fn: &'a Value); pub(crate) fn LLVMRustPositionBuilderAtStart<'a>(B: &Builder<'a>, BB: &'a BasicBlock); - pub(crate) fn LLVMRustSetModulePICLevel(M: &Module); - pub(crate) fn LLVMRustSetModulePIELevel(M: &Module); - pub(crate) fn LLVMRustSetModuleCodeModel(M: &Module, Model: CodeModel); - pub(crate) fn LLVMRustSetModuleLargeDataThreshold(M: &Module, Threshold: u64); + pub(crate) fn LLVMRustSetModulePICLevel<'ll>(M: Module<'ll>); + pub(crate) fn LLVMRustSetModulePIELevel<'ll>(M: Module<'ll>); + pub(crate) fn LLVMRustSetModuleCodeModel<'ll>(M: Module<'ll>, Model: CodeModel); + pub(crate) fn LLVMRustSetModuleLargeDataThreshold<'ll>(M: Module<'ll>, Threshold: u64); pub(crate) fn LLVMRustBufferPtr(p: &Buffer) -> *const u8; pub(crate) fn LLVMRustBufferLen(p: &Buffer) -> usize; pub(crate) fn LLVMRustBufferFree(p: &'static mut Buffer); - pub(crate) fn LLVMRustModuleCost(M: &Module) -> u64; - pub(crate) fn LLVMRustModuleInstructionStats(M: &Module) -> u64; + pub(crate) fn LLVMRustModuleCost<'ll>(M: Module<'ll>) -> u64; + pub(crate) fn LLVMRustModuleInstructionStats<'ll>(M: Module<'ll>) -> u64; - pub(crate) fn LLVMRustModuleSerialize(M: &Module, is_thin: bool) -> &'static mut Buffer; + pub(crate) fn LLVMRustModuleSerialize<'ll>( + M: Module<'ll>, + is_thin: bool, + ) -> &'static mut Buffer; pub(crate) fn LLVMRustCreateThinLTOData( Modules: *const ThinLTOModule, NumModules: size_t, PreservedSymbols: *const *const c_char, PreservedSymbolsLen: size_t, ) -> Option<&'static mut ThinLTOData>; - pub(crate) fn LLVMRustPrepareThinLTORename( + pub(crate) fn LLVMRustPrepareThinLTORename<'ll>( Data: &ThinLTOData, - Module: &Module, + Module: Module<'ll>, Target: &TargetMachine, ); - pub(crate) fn LLVMRustPrepareThinLTOResolveWeak(Data: &ThinLTOData, Module: &Module) -> bool; - pub(crate) fn LLVMRustPrepareThinLTOInternalize(Data: &ThinLTOData, Module: &Module) -> bool; - pub(crate) fn LLVMRustPrepareThinLTOImport( + pub(crate) fn LLVMRustPrepareThinLTOResolveWeak<'ll>( + Data: &ThinLTOData, + Module: Module<'ll>, + ) -> bool; + pub(crate) fn LLVMRustPrepareThinLTOInternalize<'ll>( + Data: &ThinLTOData, + Module: Module<'ll>, + ) -> bool; + pub(crate) fn LLVMRustPrepareThinLTOImport<'ll>( Data: &ThinLTOData, - Module: &Module, + Module: Module<'ll>, Target: &TargetMachine, ) -> bool; pub(crate) fn LLVMRustFreeThinLTOData(Data: &'static mut ThinLTOData); @@ -2592,9 +2611,9 @@ unsafe extern "C" { Data: *const u8, len: usize, Identifier: *const c_char, - ) -> Option<&'ll Module>; + ) -> Option>; - pub(crate) fn LLVMRustLinkerNew(M: &Module) -> &mut Linker<'_>; + pub(crate) fn LLVMRustLinkerNew<'ll>(M: Module<'ll>) -> &'ll mut Linker<'ll>; pub(crate) fn LLVMRustLinkerAdd( linker: &Linker<'_>, bytecode: *const c_uchar, // See "PTR_LEN_STR". diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs index be5233c9b2d9c..c57ffef589737 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi/branded.rs @@ -11,9 +11,25 @@ pub(crate) struct Context<'ll> { _invariant_ll: PhantomData &'ll ()>, } +/// Corresponds to `LLVMModuleRef`. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub(crate) struct Module<'ll> { + llmod_raw: ptr::NonNull, + _invariant_ll: PhantomData &'ll ()>, +} + +impl<'ll> Module<'ll> { + pub(crate) fn llmod_raw(&self) -> ptr::NonNull { + self.llmod_raw + } +} + unsafe extern "C" { /// Pointee of the raw pointer that is wrapped by a lifetime-branded [`Context`]. pub(crate) type ContextRaw; + /// Pointee of the raw pointer that is wrapped by a lifetime-branded [`Module`]. + pub(crate) type ModuleRaw; } /// Converts an unbranded pointer-to-context to a lifetime-branded [`Context`]. @@ -25,3 +41,15 @@ unsafe extern "C" { pub(crate) unsafe fn wrap_llcx<'ll>(llcx_raw: ptr::NonNull) -> Context<'ll> { Context { llcx_raw, _invariant_ll: PhantomData } } + +/// Converts an unbranded pointer-to-module to a lifetime-branded [`Module`]. +/// +/// # Safety +/// - The pointer must be a valid LLVM module within the specified context. +/// - The module must not be disposed during the lifetime of its context. +pub(crate) unsafe fn wrap_llmod<'ll>( + _llcx: Context<'ll>, // The context constrains `'ll` but isn't actually used. + llmod_raw: ptr::NonNull, +) -> Module<'ll> { + Module { llmod_raw, _invariant_ll: PhantomData } +} diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index 54b31bc450cfb..0cd26ca835971 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -220,7 +220,7 @@ pub(crate) fn SetFunctionCallConv(fn_: &Value, cc: CallConv) { // value's name as the comdat value to make sure that it is in a 1-to-1 relationship to the // function. // For more details on COMDAT sections see e.g., https://www.airs.com/blog/archives/52 -pub(crate) fn SetUniqueComdat(llmod: &Module, val: &Value) { +pub(crate) fn SetUniqueComdat<'ll>(llmod: Module<'ll>, val: &'ll Value) { let name_buf = get_value_name(val); let name = CString::from_vec_with_nul(name_buf).or_else(|buf| CString::new(buf.into_bytes())).unwrap(); @@ -257,7 +257,7 @@ pub(crate) fn set_section(llglobal: &Value, section_name: &CStr) { } } -pub(crate) fn add_global<'a>(llmod: &'a Module, ty: &'a Type, name_cstr: &CStr) -> &'a Value { +pub(crate) fn add_global<'ll>(llmod: Module<'ll>, ty: &'ll Type, name_cstr: &CStr) -> &'ll Value { unsafe { LLVMAddGlobal(llmod, ty, name_cstr.as_ptr()) } } @@ -309,7 +309,7 @@ pub(crate) fn set_externally_initialized(llglobal: &Value, is_ext_init: bool) { /// /// Inserts the comdat into `llmod` if it does not exist. /// It is an error to call this if the target does not support comdat. -pub(crate) fn set_comdat(llmod: &Module, llglobal: &Value, name: &CStr) { +pub(crate) fn set_comdat<'ll>(llmod: Module<'ll>, llglobal: &'ll Value, name: &CStr) { unsafe { let comdat = LLVMGetOrInsertComdat(llmod, name.as_ptr()); LLVMSetComdat(llglobal, comdat); @@ -365,7 +365,7 @@ impl Intrinsic { pub(crate) fn get_declaration<'ll>( self, - llmod: &'ll Module, + llmod: Module<'ll>, type_params: &[&'ll Type], ) -> &'ll Value { unsafe { @@ -440,8 +440,8 @@ impl Drop for OperandBundleBox<'_> { } } -pub(crate) fn add_module_flag_u32( - module: &Module, +pub(crate) fn add_module_flag_u32<'ll>( + module: Module<'ll>, merge_behavior: ModuleFlagMergeBehavior, key: &str, value: u32, @@ -451,8 +451,8 @@ pub(crate) fn add_module_flag_u32( } } -pub(crate) fn add_module_flag_str( - module: &Module, +pub(crate) fn add_module_flag_str<'ll>( + module: Module<'ll>, merge_behavior: ModuleFlagMergeBehavior, key: &str, value: &str, @@ -484,7 +484,7 @@ pub(crate) fn set_dso_local<'ll>(v: &'ll Value) { /// Safe wrapper for `LLVMRustAppendModuleInlineAsm`, which delegates to /// `Module::appendModuleInlineAsm`. pub(crate) fn append_module_inline_asm<'ll>( - llmod: &'ll Module, + llmod: Module<'ll>, asm: &[u8], target_features: &str, target_cpu: &str, @@ -504,7 +504,7 @@ pub(crate) fn append_module_inline_asm<'ll>( /// Safe wrapper for `LLVMAddAlias2` pub(crate) fn add_alias<'ll>( - module: &'ll Module, + module: Module<'ll>, ty: &Type, address_space: AddressSpace, aliasee: &Value, diff --git a/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs index 840d07a1ed2ae..60e0836a35d88 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs @@ -6,7 +6,7 @@ use super::ffi::{Module, Value}; type LLVMRustOffloadMapperFn = unsafe extern "C" fn(&Value, &Value, *const &Value); type LLVMRustOffloadWrapImagesFn = - unsafe extern "C" fn(&Module, *const c_char, *const c_char) -> bool; + for<'ll> unsafe extern "C" fn(Module<'ll>, *const c_char, *const c_char) -> bool; use rustc_fs_util::path_to_c_string; use rustc_session::config::host_tuple; @@ -56,9 +56,9 @@ impl RustOffloadWrapper { unsafe { (self.LLVMRustOffloadMapper)(v1, v2, vs.as_ptr()) } } - pub(crate) unsafe fn llvm_rust_offload_wrap_images( + pub(crate) unsafe fn llvm_rust_offload_wrap_images<'ll>( &self, - host_m: &Module, + host_m: Module<'ll>, device_bitcode_path: &CStr, ) -> bool { let lld_c = self.lld_path.as_deref().map(path_to_c_string).unwrap_or_default(); diff --git a/compiler/rustc_codegen_llvm/src/type_.rs b/compiler/rustc_codegen_llvm/src/type_.rs index 0244706033641..8d2ae43bd3205 100644 --- a/compiler/rustc_codegen_llvm/src/type_.rs +++ b/compiler/rustc_codegen_llvm/src/type_.rs @@ -152,7 +152,7 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { (**self).borrow().llcx } - pub(crate) fn llmod(&self) -> &'ll llvm::Module { + pub(crate) fn llmod(&self) -> llvm::Module<'ll> { (**self).borrow().llmod } diff --git a/compiler/rustc_codegen_llvm/src/typetree.rs b/compiler/rustc_codegen_llvm/src/typetree.rs index e781ffea99479..efda11f399ba4 100644 --- a/compiler/rustc_codegen_llvm/src/typetree.rs +++ b/compiler/rustc_codegen_llvm/src/typetree.rs @@ -92,7 +92,7 @@ pub(crate) fn add_tt<'tcx, 'll>(cx: &FullCx<'ll, 'tcx>, fn_def: &'ll Value, tt: let inputs = tt.args; let ret_tt: RustTypeTree = tt.ret; - let llvm_data_layout: *const c_char = unsafe { llvm::LLVMGetDataLayoutStr(&*llmod) }; + let llvm_data_layout: *const c_char = unsafe { llvm::LLVMGetDataLayoutStr(llmod) }; let llvm_data_layout = std::str::from_utf8(unsafe { std::ffi::CStr::from_ptr(llvm_data_layout) }.to_bytes()) .expect("got a non-UTF8 data-layout from LLVM"); From 53b5ab38b90e0c065d75cb690a7f5ae5e93d97ff Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 31 Oct 2026 19:56:24 +1100 Subject: [PATCH 16/44] Respect `--color=never` when printing `--explain` output to the terminal The color flag was being respected when sending explain output to a pager, but not in the fallback path for printing it directly to the terminal. --- compiler/rustc_driver_impl/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index b878affae386a..9976fd3dedbb0 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -518,9 +518,11 @@ fn show_md_content_with_pager(content: &str, color: ColorConfig) { } // The pager failed. Try to print pretty output to stdout. - if let Some((bufwtr, mdbuf)) = &mut pretty_data + if matches!(color, ColorConfig::Auto | ColorConfig::Always) + && let Some((bufwtr, mdbuf)) = &mut pretty_data && bufwtr.write_all(&mdbuf).is_ok() { + // Colored output was successfully printed to stdout. return; } From 1d6ca7251531dee60c4dcd6a00222319a9035443 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 31 Oct 2026 19:56:25 +0100 Subject: [PATCH 17/44] Remove useless test --- tests/rustdoc-html/smoke.rs | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 tests/rustdoc-html/smoke.rs diff --git a/tests/rustdoc-html/smoke.rs b/tests/rustdoc-html/smoke.rs deleted file mode 100644 index 415fca313fc55..0000000000000 --- a/tests/rustdoc-html/smoke.rs +++ /dev/null @@ -1,25 +0,0 @@ -//@ has smoke/index.html - -//! Very docs - -//@ has smoke/bar/index.html -pub mod bar { - - /// So correct - //@ has smoke/bar/baz/index.html - pub mod baz { - /// Much detail - //@ has smoke/bar/baz/fn.baz.html - pub fn baz() { } - } - - /// *wow* - //@ has smoke/bar/trait.Doge.html - pub trait Doge { fn dummy(&self) { } } - - //@ has smoke/bar/struct.Foo.html - pub struct Foo { x: isize, y: usize } - - //@ has smoke/bar/fn.prawns.html - pub fn prawns((a, b): (isize, usize), Foo { x, y }: Foo) { } -} From 9f832e0bdbdac2f179f590d51a4fd08bfb8fdad5 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 20:04:13 +0100 Subject: [PATCH 18/44] reflow src/tests/directives.md --- src/doc/rustc-dev-guide/src/tests/directives.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index ed6c8b9f93951..ab878b88eafa5 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -274,9 +274,11 @@ For example: - A test with the `//@ edition: 2015..2021` directive can be run under the 2015 and the 2018 edition, so the upper bound is exclusive just like in Rust (note that there's no equivalent to Rust's `..=` where the upper bound is inclusive). - However, CI will only run the test with the lowest edition in the range (which is 2015 in this example). + However, + CI will only run the test with the lowest edition in the range (which is 2015 in this example). - A test with the `//@ edition: 2018..` directive will run under 2018 edition or greater. - However, CI will only run the test with the lowest edition in the range (which is 2018 in this example). + However, + CI will only run the test with the lowest edition in the range (which is 2018 in this example). You can also force `./x test` to use a specific edition by passing the `-- --edition=` argument. However, tests with the `//@ edition` directive will clamp the value passed to the argument. @@ -300,7 +302,8 @@ Asked in #### Test-suite-specific directives -The test suites [`rustdoc-html`][rustdoc-html-tests], [`rustdoc-js`/`rustdoc-js-std`][rustdoc-js-tests] +The test suites [`rustdoc-html`][rustdoc-html-tests], +[`rustdoc-js`/`rustdoc-js-std`][rustdoc-js-tests] and [`rustdoc-json`][rustdoc-json-tests] each feature an additional set of directives whose basic syntax resembles the one of compiletest directives but which are ultimately read and checked by separate tools. From 1dcce6b106ca185118f880b3589c6f237ac2429a Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 20:06:29 +0100 Subject: [PATCH 19/44] improve tests/directives.md --- src/doc/rustc-dev-guide/src/tests/directives.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index ab878b88eafa5..7d8fc182ba453 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -303,10 +303,10 @@ Asked in #### Test-suite-specific directives The test suites [`rustdoc-html`][rustdoc-html-tests], -[`rustdoc-js`/`rustdoc-js-std`][rustdoc-js-tests] +[`rustdoc-js`/`rustdoc-js-std`][rustdoc-js-tests], and [`rustdoc-json`][rustdoc-json-tests] each feature an additional set of directives whose basic -syntax resembles the one of compiletest directives but which are ultimately read and checked by -separate tools. +syntax resembles one of the compiletest directives, +but which are ultimately read and checked by separate tools. For more information, please read their respective chapters as linked above. [rustdoc-html-tests]: ../rustdoc-internals/rustdoc-html-test-suite.md From 4e0f172c2ac5ba676b974b28399f716fe90c02bd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 31 Oct 2026 20:13:42 +0100 Subject: [PATCH 20/44] Improve test --- tests/rustdoc-html/mut-params.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/rustdoc-html/mut-params.rs b/tests/rustdoc-html/mut-params.rs index b3d4fe8c0599c..f2ebd3a21d0ff 100644 --- a/tests/rustdoc-html/mut-params.rs +++ b/tests/rustdoc-html/mut-params.rs @@ -6,7 +6,8 @@ pub struct Foo; //@ count foo/struct.Foo.html '//*[@class="impl-items"]//*[@class="method"]' 2 -//@ !has - '//*[@class="impl-items"]//*[@class="method"]' 'mut' +//@ has - '//*[@class="impl-items"]//*[@class="method"]' 'pub fn foo(self)' +//@ has - '//*[@class="impl-items"]//*[@class="method"]' 'pub fn bar(bar: ())' impl Foo { pub fn foo(mut self) {} @@ -14,5 +15,5 @@ impl Foo { } //@ count foo/fn.baz.html '//pre[@class="rust item-decl"]' 1 -//@ !has - '//pre[@class="rust item-decl"]' 'mut' +//@ has - '//pre[@class="rust item-decl"]' 'fn baz(foo: Foo)' pub fn baz(mut foo: Foo) {} From 08059b7b8ed5b2b55a2d122e070162a460541fb6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 31 Oct 2026 20:15:06 +0100 Subject: [PATCH 21/44] Move tests in the right folder in `tests/rustdoc-html` --- tests/rustdoc-html/README.md | 4 ++++ .../rustdoc-html/{ => attributes}/attributes-2021-edition.rs | 0 tests/rustdoc-html/{ => attributes}/attributes.rs | 0 tests/rustdoc-html/{ => attributes}/auxiliary/masked.rs | 0 tests/rustdoc-html/{ => attributes}/masked.rs | 0 tests/rustdoc-html/{ => attributes}/stability.rs | 0 tests/rustdoc-html/{ => fn}/splat-variadic-ellipsis.rs | 0 tests/rustdoc-html/{ => impl}/sort-53812.rs | 0 8 files changed, 4 insertions(+) rename tests/rustdoc-html/{ => attributes}/attributes-2021-edition.rs (100%) rename tests/rustdoc-html/{ => attributes}/attributes.rs (100%) rename tests/rustdoc-html/{ => attributes}/auxiliary/masked.rs (100%) rename tests/rustdoc-html/{ => attributes}/masked.rs (100%) rename tests/rustdoc-html/{ => attributes}/stability.rs (100%) rename tests/rustdoc-html/{ => fn}/splat-variadic-ellipsis.rs (100%) rename tests/rustdoc-html/{ => impl}/sort-53812.rs (100%) diff --git a/tests/rustdoc-html/README.md b/tests/rustdoc-html/README.md index 31657403f2858..009b0d09ea8e4 100644 --- a/tests/rustdoc-html/README.md +++ b/tests/rustdoc-html/README.md @@ -20,6 +20,10 @@ docs. Contains tests checking the rendering of `async` items. +## `tests/rustdoc-html/attributes` + +Contains tests checking the rendering of attributes in the docs. + ## `tests/rustdoc-html/auto` Contains tests related to auto traits implementations. diff --git a/tests/rustdoc-html/attributes-2021-edition.rs b/tests/rustdoc-html/attributes/attributes-2021-edition.rs similarity index 100% rename from tests/rustdoc-html/attributes-2021-edition.rs rename to tests/rustdoc-html/attributes/attributes-2021-edition.rs diff --git a/tests/rustdoc-html/attributes.rs b/tests/rustdoc-html/attributes/attributes.rs similarity index 100% rename from tests/rustdoc-html/attributes.rs rename to tests/rustdoc-html/attributes/attributes.rs diff --git a/tests/rustdoc-html/auxiliary/masked.rs b/tests/rustdoc-html/attributes/auxiliary/masked.rs similarity index 100% rename from tests/rustdoc-html/auxiliary/masked.rs rename to tests/rustdoc-html/attributes/auxiliary/masked.rs diff --git a/tests/rustdoc-html/masked.rs b/tests/rustdoc-html/attributes/masked.rs similarity index 100% rename from tests/rustdoc-html/masked.rs rename to tests/rustdoc-html/attributes/masked.rs diff --git a/tests/rustdoc-html/stability.rs b/tests/rustdoc-html/attributes/stability.rs similarity index 100% rename from tests/rustdoc-html/stability.rs rename to tests/rustdoc-html/attributes/stability.rs diff --git a/tests/rustdoc-html/splat-variadic-ellipsis.rs b/tests/rustdoc-html/fn/splat-variadic-ellipsis.rs similarity index 100% rename from tests/rustdoc-html/splat-variadic-ellipsis.rs rename to tests/rustdoc-html/fn/splat-variadic-ellipsis.rs diff --git a/tests/rustdoc-html/sort-53812.rs b/tests/rustdoc-html/impl/sort-53812.rs similarity index 100% rename from tests/rustdoc-html/sort-53812.rs rename to tests/rustdoc-html/impl/sort-53812.rs From 99b62e44ea9c291bfbdd2f6303cf31ea1d352ae6 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 20:26:28 +0100 Subject: [PATCH 22/44] that made me think files should have this name --- .../src/rustdoc-internals/rustdoc-html-test-suite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md index a88e60c183b0d..76105c6c9e0e7 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md @@ -3,7 +3,7 @@ This page is about the test suite named `rustdoc-html` used to test the HTML output of `rustdoc`. For other rustdoc-specific test suites, see [Rustdoc test suites]. -Each test file in this test suite is simply a Rust source file `file.rs` sprinkled with +Each test file in this test suite is simply a Rust source file sprinkled with so-called *directives* located inside normal Rust code comments. These come in two flavors: *Compiletest* and *HtmlDocCk*. From 73da0c1c8a4a8f07f0c270c7946fe15fa2c152e9 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 20:27:01 +0100 Subject: [PATCH 23/44] reflow src/rustdoc-internals/rustdoc-html-test-suite.md --- .../src/rustdoc-internals/rustdoc-html-test-suite.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md index 76105c6c9e0e7..13b618e7d4e44 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals/rustdoc-html-test-suite.md @@ -145,8 +145,8 @@ Checks that the directory given by `PATH` contains exactly `ENTRIES`. ## Compiletest Directives (Brief) As mentioned in the introduction, you also have access to [compiletest directives]. -Most importantly, they allow you to register auxiliary crates and -to pass flags to the `rustdoc` binary under test. +Most importantly, +they allow you to register auxiliary crates and to pass flags to the `rustdoc` binary under test. It's *strongly recommended* to read that chapter if you don't know anything about them yet. Here are some details that are relevant to this test suite specifically: @@ -177,9 +177,9 @@ the original negative check failing. **Note**: Please avoid the use of *negated* checks! -**Tip**: If you can't avoid it, please **always** pair it with an analogous positive check in the -immediate vicinity, so people changing "the shape" have a chance to notice and to update the -negated check! +**Tip**: If you can't avoid it, +please **always** pair it with an analogous positive check in the immediate vicinity, +so people changing "the shape" have a chance to notice and to update the negated check! ## Limitations From 2e541c310c7a2c3d13974cd330b5b77928a90db2 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:04:41 +0100 Subject: [PATCH 24/44] sembr src/traits/canonicalization.md --- .../src/traits/canonicalization.md | 159 +++++++++--------- 1 file changed, 80 insertions(+), 79 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/canonicalization.md b/src/doc/rustc-dev-guide/src/traits/canonicalization.md index 4bd56a020f14f..97d138ec43490 100644 --- a/src/doc/rustc-dev-guide/src/traits/canonicalization.md +++ b/src/doc/rustc-dev-guide/src/traits/canonicalization.md @@ -4,52 +4,52 @@ > [Next-gen trait solving Canonicalization chapter](../solve/canonicalization.md). > It is suggested to reorganize these contents in the future. -Canonicalization is the process of **isolating** an inference value -from its context. It is a key part of implementing -[canonical queries][cq], and you may wish to read the parent chapter +Canonicalization is the process of **isolating** an inference value from its context. +It is a key part of implementing [canonical queries][cq], +and you may wish to read the parent chapter to get more context. Canonicalization is really based on a very simple concept: every [inference variable](../type-inference.md#vars) is always in one of -two states: either it is **unbound**, in which case we don't know yet -what type it is, or it is **bound**, in which case we do. So to -isolate some data-structure T that contains types/regions from its +two states: either it is **unbound**, in which case we don't know yet what type it is, +or it is **bound**, in which case we do. +So to isolate some data-structure T that contains types/regions from its environment, we just walk down and find the unbound variables that appear in T; those variables get replaced with "canonical variables", -starting from zero and numbered in a fixed order (left to right, for -the most part, but really it doesn't matter as long as it is +starting from zero and numbered in a fixed order (left to right, for the most part, +but really it doesn't matter as long as it is consistent). [cq]: ./canonical-queries.md -So, for example, if we have the type `X = (?T, ?U)`, where `?T` and -`?U` are distinct, unbound inference variables, then the canonical -form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these -**canonical placeholders**. Note that the type `Y = (?U, ?T)` also -canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would -canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). In other words, the -exact identity of the inference variables is not important – unless +So, for example, if we have the type `X = (?T, ?U)`, where `?T` and `?U` are distinct, +unbound inference variables, then the canonical +form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these **canonical placeholders**. +Note that the type `Y = (?U, ?T)` also canonicalizes to `(?0, ?1)`. +But the type `Z = (?T, ?T)` would canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). +In other words, the exact identity of the inference variables is not important – unless they are repeated. -We use this to improve caching as well as to detect cycles and other -things during trait resolution. Roughly speaking, the idea is that if -two trait queries have the same canonical form, then they will get -the same answer. That answer will be expressed in terms of the -canonical variables (`?0`, `?1`), which we can then map back to the +We use this to improve caching as well as to detect cycles and other things during trait resolution. +Roughly speaking, the idea is that if two trait queries have the same canonical form, +then they will get +the same answer. +That answer will be expressed in terms of the canonical variables (`?0`, +`?1`), which we can then map back to the original variables (`?T`, `?U`). ## Canonicalizing the query To see how it works, imagine that we are asking to solve the following trait query: `?A: Foo<'static, ?B>`, where `?A` and `?B` are unbound. -This query contains two unbound variables, but it also contains the -lifetime `'static`. The trait system generally ignores all lifetimes -and treats them equally, so when canonicalizing, we will *also* +This query contains two unbound variables, but it also contains the lifetime `'static`. +The trait system generally ignores all lifetimes and treats them equally, +so when canonicalizing, we will *also* replace any [free lifetime](../appendix/background.md#free-vs-bound) with a -canonical variable (Note that `'static` is actually a _free_ lifetime -variable here. We are not considering it in the typing context of the whole -program but only in the context of this trait reference. Mathematically, we -are not quantifying over the whole program, but only this obligation). +canonical variable (Note that `'static` is actually a _free_ lifetime variable here. +We are not considering it in the typing context of the whole +program but only in the context of this trait reference. +Mathematically, we are not quantifying over the whole program, but only this obligation). Therefore, we get the following result: ```text @@ -62,12 +62,11 @@ Sometimes we write this differently, like so: for { ?0: Foo<'?1, ?2> } ``` -This `for<>` gives some information about each of the canonical -variables within. In this case, each `T` indicates a type variable, -so `?0` and `?2` are types; the `L` indicates a lifetime variable, so -`?1` is a lifetime. The `canonicalize` method *also* gives back a -`CanonicalVarValues` array OV with the "original values" for each -canonicalized variable: +This `for<>` gives some information about each of the canonical variables within. + In this case, each `T` indicates a type variable, +so `?0` and `?2` are types; the `L` indicates a lifetime variable, so `?1` is a lifetime. +The `canonicalize` method *also* gives back a +`CanonicalVarValues` array OV with the "original values" for each canonicalized variable: ```text [?A, 'static, ?B] @@ -79,8 +78,8 @@ We'll need this vector OV later, when we process the query response. Once we've constructed the canonical query, we can try to solve it. To do so, we will wind up creating a fresh inference context and -**instantiating** the canonical query in that context. The idea is that -we create a substitution S from the canonical form containing a fresh +**instantiating** the canonical query in that context. +The idea is that we create a substitution S from the canonical form containing a fresh inference variable (of suitable kind) for each canonical variable. So, for our example query: @@ -94,22 +93,24 @@ the substitution S might be: S = [?A, '?B, ?C] ``` -We can then replace the bound canonical variables (`?0`, etc) with -these inference variables, yielding the following fully instantiated +We can then replace the bound canonical variables (`?0`, etc) with these inference variables, +yielding the following fully instantiated query: ```text ?A: Foo<'?B, ?C> ``` -Remember that substitution S though! We're going to need it later. +Remember that substitution S though! +We're going to need it later. -OK, now that we have a fresh inference context and an instantiated -query, we can go ahead and try to solve it. The trait solver itself is -explained in more detail in [another section](../solve/the-solver.md), but +OK, now that we have a fresh inference context and an instantiated query, +we can go ahead and try to solve it. +The trait solver itself is explained in more detail in [another section](../solve/the-solver.md), +but suffice to say that it will compute a [certainty value][cqqr] (`Proven` or -`Ambiguous`) and have side-effects on the inference variables we've -created. For example, if there were only one impl of `Foo`, like so: +`Ambiguous`) and have side-effects on the inference variables we've created. +For example, if there were only one impl of `Foo`, like so: [cqqr]: ./canonical-queries.md#query-response @@ -119,29 +120,29 @@ where X: 'a { ... } ``` -then we might wind up with a certainty value of `Proven`, as well as -creating fresh inference variables `'?D` and `?E` (to represent the +then we might wind up with a certainty value of `Proven`, +as well as creating fresh inference variables `'?D` and `?E` (to represent the parameters on the impl) and unifying as follows: - `'?B = '?D` - `?A = Vec` - `?C = ?E` -We would also accumulate the region constraint `?E: '?D`, due to the -where clause. +We would also accumulate the region constraint `?E: '?D`, due to the where clause. -In order to create our final query result, we have to "lift" these -values out of the query's inference context and into something that -can be reapplied in our original inference context. We do that by -**re-applying canonicalization**, but to the **query result**. +In order to create our final query result, +we have to "lift" these values out of the query's inference context and into something that +can be reapplied in our original inference context. +We do that by **re-applying canonicalization**, but to the **query result**. ## Canonicalizing the query result As discussed in [the parent section][cqqr], most trait queries wind up with a result that brings together a "certainty value" `certainty`, a -result substitution `var_values`, and some region constraints. To -create this, we wind up re-using the substitution S that we created -when first instantiating our query. To refresh your memory, we had a query +result substitution `var_values`, and some region constraints. +To create this, we wind up re-using the substitution S that we created +when first instantiating our query. +To refresh your memory, we had a query ```text for { ?0: Foo<'?1, ?2> } @@ -160,10 +161,10 @@ If we "refresh" S with the latest results, we get: S = [Vec, '?D, ?E] ``` -These are precisely the new values for the three input variables from -our original query. Note though that they include some new variables -(like `?E`). We can make those go away by canonicalizing again! We don't -just canonicalize S, though, we canonicalize the whole query response QR: +These are precisely the new values for the three input variables from our original query. +Note though that they include some new variables (like `?E`). +We can make those go away by canonicalizing again! +We don't just canonicalize S, though, we canonicalize the whole query response QR: ```text QR = { @@ -187,20 +188,20 @@ Canonical(QR) = for { } ``` -(One subtle point: when we canonicalize the query **result**, we do not -use any special treatment for free lifetimes. Note that both -references to `'?D`, for example, were converted into the same -canonical variable (`?1`). This is in contrast to the original query, -where we canonicalized every free lifetime into a fresh canonical -variable.) +(One subtle point: when we canonicalize the query **result**, +we do not use any special treatment for free lifetimes. +Note that both references to `'?D`, for example, were converted into the same +canonical variable (`?1`). +This is in contrast to the original query, +where we canonicalized every free lifetime into a fresh canonical variable.) Now, this result must be reapplied in each context where needed. ## Processing the canonicalized query result -In the previous section we produced a canonical query result. We now have -to apply that result in our original context. If you recall, way back in the -beginning, we were trying to prove this query: +In the previous section we produced a canonical query result. +We now have to apply that result in our original context. +If you recall, way back in the beginning, we were trying to prove this query: ```text ?A: Foo<'static, ?B> @@ -223,11 +224,11 @@ for { } ``` -We now want to apply that response to our context. Conceptually, how -we do that is to (a) instantiate each of the canonical variables in +We now want to apply that response to our context. +Conceptually, how we do that is to (a) instantiate each of the canonical variables in the result with a fresh inference variable, (b) unify the values in -the result with the original values, and then (c) record the region -constraints for later. Doing step (a) would yield a result of +the result with the original values, and then (c) record the region constraints for later. +Doing step (a) would yield a result of ```text { @@ -247,13 +248,13 @@ Step (b) would then unify: ?B with ?C ``` -And finally the region constraint of `?C: 'static` would be recorded -for later verification. +And finally the region constraint of `?C: 'static` would be recorded for later verification. (What we *actually* do is a mildly optimized variant of that: Rather -than eagerly instantiating all of the canonical values in the result -with variables, we instead walk the vector of values, looking for -cases where the value is just a canonical variable. In our example, -`values[2]` is `?C`, so that means we can deduce that `?C := ?B` and -`'?D := 'static`. This gives us a partial set of values. Anything for -which we do not find a value, we create an inference variable.) +than eagerly instantiating all of the canonical values in the result with variables, +we instead walk the vector of values, looking for +cases where the value is just a canonical variable. +In our example, +`values[2]` is `?C`, so that means we can deduce that `?C := ?B` and `'?D := 'static`. +This gives us a partial set of values. +Anything for which we do not find a value, we create an inference variable.) From 63fdb59964a698598e30c4fdc5dde1ea1060a0aa Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:22:02 +0100 Subject: [PATCH 25/44] improve traits/canonicalization.md --- .../src/traits/canonicalization.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/canonicalization.md b/src/doc/rustc-dev-guide/src/traits/canonicalization.md index 97d138ec43490..7564fbe9072d1 100644 --- a/src/doc/rustc-dev-guide/src/traits/canonicalization.md +++ b/src/doc/rustc-dev-guide/src/traits/canonicalization.md @@ -13,7 +13,7 @@ Canonicalization is really based on a very simple concept: every [inference variable](../type-inference.md#vars) is always in one of two states: either it is **unbound**, in which case we don't know yet what type it is, or it is **bound**, in which case we do. -So to isolate some data-structure T that contains types/regions from its +So, to isolate some data-structure T that contains types/regions from its environment, we just walk down and find the unbound variables that appear in T; those variables get replaced with "canonical variables", starting from zero and numbered in a fixed order (left to right, for the most part, @@ -22,13 +22,13 @@ consistent). [cq]: ./canonical-queries.md -So, for example, if we have the type `X = (?T, ?U)`, where `?T` and `?U` are distinct, +So, for example, if we have the type `X = (?T, ?U)`, where `?T` and `?U` are distinct unbound inference variables, then the canonical form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these **canonical placeholders**. Note that the type `Y = (?U, ?T)` also canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). -In other words, the exact identity of the inference variables is not important – unless -they are repeated. +In other words, the exact identity of the inference variables is not important, +unless they are repeated. We use this to improve caching as well as to detect cycles and other things during trait resolution. Roughly speaking, the idea is that if two trait queries have the same canonical form, @@ -47,8 +47,8 @@ The trait system generally ignores all lifetimes and treats them equally, so when canonicalizing, we will *also* replace any [free lifetime](../appendix/background.md#free-vs-bound) with a canonical variable (Note that `'static` is actually a _free_ lifetime variable here. -We are not considering it in the typing context of the whole -program but only in the context of this trait reference. +We are not considering it in the typing context of the whole program, +but only in the context of this trait reference. Mathematically, we are not quantifying over the whole program, but only this obligation). Therefore, we get the following result: @@ -63,7 +63,7 @@ for { ?0: Foo<'?1, ?2> } ``` This `for<>` gives some information about each of the canonical variables within. - In this case, each `T` indicates a type variable, +In this case, each `T` indicates a type variable, so `?0` and `?2` are types; the `L` indicates a lifetime variable, so `?1` is a lifetime. The `canonicalize` method *also* gives back a `CanonicalVarValues` array OV with the "original values" for each canonicalized variable: @@ -142,13 +142,13 @@ with a result that brings together a "certainty value" `certainty`, a result substitution `var_values`, and some region constraints. To create this, we wind up re-using the substitution S that we created when first instantiating our query. -To refresh your memory, we had a query +To refresh your memory, we had a query: ```text for { ?0: Foo<'?1, ?2> } ``` -for which we made a substutition S: +We made a substutition S for it: ```text S = [?A, '?B, ?C] @@ -199,7 +199,7 @@ Now, this result must be reapplied in each context where needed. ## Processing the canonicalized query result -In the previous section we produced a canonical query result. +In the previous section, we produced a canonical query result. We now have to apply that result in our original context. If you recall, way back in the beginning, we were trying to prove this query: @@ -228,7 +228,7 @@ We now want to apply that response to our context. Conceptually, how we do that is to (a) instantiate each of the canonical variables in the result with a fresh inference variable, (b) unify the values in the result with the original values, and then (c) record the region constraints for later. -Doing step (a) would yield a result of +Doing step (a) would yield this result: ```text { @@ -248,7 +248,7 @@ Step (b) would then unify: ?B with ?C ``` -And finally the region constraint of `?C: 'static` would be recorded for later verification. +And finally, the region constraint of `?C: 'static` would be recorded for later verification. (What we *actually* do is a mildly optimized variant of that: Rather than eagerly instantiating all of the canonical values in the result with variables, From 4496185aebb20e45b2a0e4c2dd3006be27bd8627 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:23:09 +0100 Subject: [PATCH 26/44] reflow src/traits/canonicalization.md --- .../src/traits/canonicalization.md | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/canonicalization.md b/src/doc/rustc-dev-guide/src/traits/canonicalization.md index 7564fbe9072d1..d87e3b3031022 100644 --- a/src/doc/rustc-dev-guide/src/traits/canonicalization.md +++ b/src/doc/rustc-dev-guide/src/traits/canonicalization.md @@ -6,25 +6,23 @@ Canonicalization is the process of **isolating** an inference value from its context. It is a key part of implementing [canonical queries][cq], -and you may wish to read the parent chapter -to get more context. +and you may wish to read the parent chapter to get more context. Canonicalization is really based on a very simple concept: every [inference variable](../type-inference.md#vars) is always in one of two states: either it is **unbound**, in which case we don't know yet what type it is, or it is **bound**, in which case we do. -So, to isolate some data-structure T that contains types/regions from its -environment, we just walk down and find the unbound variables that +So, to isolate some data-structure T that contains types/regions from its environment, +we just walk down and find the unbound variables that appear in T; those variables get replaced with "canonical variables", starting from zero and numbered in a fixed order (left to right, for the most part, -but really it doesn't matter as long as it is -consistent). +but really it doesn't matter as long as it is consistent). [cq]: ./canonical-queries.md So, for example, if we have the type `X = (?T, ?U)`, where `?T` and `?U` are distinct -unbound inference variables, then the canonical -form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these **canonical placeholders**. +unbound inference variables, then the canonical form of `X` would be `(?0, +?1)`, where `?0` and `?1` represent these **canonical placeholders**. Note that the type `Y = (?U, ?T)` also canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). In other words, the exact identity of the inference variables is not important, @@ -32,11 +30,9 @@ unless they are repeated. We use this to improve caching as well as to detect cycles and other things during trait resolution. Roughly speaking, the idea is that if two trait queries have the same canonical form, -then they will get -the same answer. +then they will get the same answer. That answer will be expressed in terms of the canonical variables (`?0`, -`?1`), which we can then map back to the -original variables (`?T`, `?U`). +`?1`), which we can then map back to the original variables (`?T`, `?U`). ## Canonicalizing the query @@ -44,8 +40,8 @@ To see how it works, imagine that we are asking to solve the following trait query: `?A: Foo<'static, ?B>`, where `?A` and `?B` are unbound. This query contains two unbound variables, but it also contains the lifetime `'static`. The trait system generally ignores all lifetimes and treats them equally, -so when canonicalizing, we will *also* -replace any [free lifetime](../appendix/background.md#free-vs-bound) with a +so when canonicalizing, +we will *also* replace any [free lifetime](../appendix/background.md#free-vs-bound) with a canonical variable (Note that `'static` is actually a _free_ lifetime variable here. We are not considering it in the typing context of the whole program, but only in the context of this trait reference. @@ -94,8 +90,7 @@ S = [?A, '?B, ?C] ``` We can then replace the bound canonical variables (`?0`, etc) with these inference variables, -yielding the following fully instantiated -query: +yielding the following fully instantiated query: ```text ?A: Foo<'?B, ?C> @@ -107,8 +102,7 @@ We're going to need it later. OK, now that we have a fresh inference context and an instantiated query, we can go ahead and try to solve it. The trait solver itself is explained in more detail in [another section](../solve/the-solver.md), -but -suffice to say that it will compute a [certainty value][cqqr] (`Proven` or +but suffice to say that it will compute a [certainty value][cqqr] (`Proven` or `Ambiguous`) and have side-effects on the inference variables we've created. For example, if there were only one impl of `Foo`, like so: @@ -140,8 +134,8 @@ We do that by **re-applying canonicalization**, but to the **query result**. As discussed in [the parent section][cqqr], most trait queries wind up with a result that brings together a "certainty value" `certainty`, a result substitution `var_values`, and some region constraints. -To create this, we wind up re-using the substitution S that we created -when first instantiating our query. +To create this, +we wind up re-using the substitution S that we created when first instantiating our query. To refresh your memory, we had a query: ```text @@ -190,8 +184,8 @@ Canonical(QR) = for { (One subtle point: when we canonicalize the query **result**, we do not use any special treatment for free lifetimes. -Note that both references to `'?D`, for example, were converted into the same -canonical variable (`?1`). +Note that both references to `'?D`, for example, +were converted into the same canonical variable (`?1`). This is in contrast to the original query, where we canonicalized every free lifetime into a fresh canonical variable.) @@ -252,8 +246,8 @@ And finally, the region constraint of `?C: 'static` would be recorded for later (What we *actually* do is a mildly optimized variant of that: Rather than eagerly instantiating all of the canonical values in the result with variables, -we instead walk the vector of values, looking for -cases where the value is just a canonical variable. +we instead walk the vector of values, +looking for cases where the value is just a canonical variable. In our example, `values[2]` is `?C`, so that means we can deduce that `?C := ?B` and `'?D := 'static`. This gives us a partial set of values. From 8f09efbb093d14c52eb4101c4e5560b56f0597f6 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:26:03 +0100 Subject: [PATCH 27/44] fix sembr tool fail --- src/doc/rustc-dev-guide/src/traits/canonicalization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/canonicalization.md b/src/doc/rustc-dev-guide/src/traits/canonicalization.md index d87e3b3031022..d1cd1a2cefb49 100644 --- a/src/doc/rustc-dev-guide/src/traits/canonicalization.md +++ b/src/doc/rustc-dev-guide/src/traits/canonicalization.md @@ -21,8 +21,8 @@ but really it doesn't matter as long as it is consistent). [cq]: ./canonical-queries.md So, for example, if we have the type `X = (?T, ?U)`, where `?T` and `?U` are distinct -unbound inference variables, then the canonical form of `X` would be `(?0, -?1)`, where `?0` and `?1` represent these **canonical placeholders**. +unbound inference variables, then the canonical form of `X` would be `(?0, ?1)`, +where `?0` and `?1` represent these **canonical placeholders**. Note that the type `Y = (?U, ?T)` also canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). In other words, the exact identity of the inference variables is not important, From 2f7cf458955ee99033a8879b46b9cf8e0df93770 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:31:49 +0100 Subject: [PATCH 28/44] sembr src/type-inference.md --- src/doc/rustc-dev-guide/src/type-inference.md | 249 +++++++++--------- 1 file changed, 123 insertions(+), 126 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/type-inference.md b/src/doc/rustc-dev-guide/src/type-inference.md index 196fcdd9190c3..0a37dfbd9928b 100644 --- a/src/doc/rustc-dev-guide/src/type-inference.md +++ b/src/doc/rustc-dev-guide/src/type-inference.md @@ -1,7 +1,6 @@ # Type inference -Type inference is the process of automatic detection of the type of an -expression. +Type inference is the process of automatic detection of the type of an expression. It is what allows Rust to work with fewer or no type annotations, making things easier for users: @@ -13,55 +12,52 @@ fn main() { } ``` -Here, the type of `things` is *inferred* to be `Vec<&str>` because of the value -we push into `things`. +Here, +the type of `things` is *inferred* to be `Vec<&str>` because of the value we push into `things`. -The type inference is based on the standard Hindley-Milner (HM) type inference -algorithm, but extended in various ways to accommodate subtyping, region +The type inference is based on the standard Hindley-Milner (HM) type inference algorithm, +but extended in various ways to accommodate subtyping, region inference, and higher-ranked types. ## A note on terminology -We use the notation `?T` to refer to inference variables, also called -existential variables. +We use the notation `?T` to refer to inference variables, also called existential variables. -We use the terms "region" and "lifetime" interchangeably. Both refer to -the `'a` in `&'a T`. +We use the terms "region" and "lifetime" interchangeably. +Both refer to the `'a` in `&'a T`. -The term "bound region" refers to a region that is bound in a function -signature, such as the `'a` in `for<'a> fn(&'a u32)`. A region is -"free" if it is not bound. +The term "bound region" refers to a region that is bound in a function signature, +such as the `'a` in `for<'a> fn(&'a u32)`. +A region is "free" if it is not bound. ## Creating an inference context -You create an inference context by doing something like -the following: +You create an inference context by doing something like the following: ```rust,ignore let infcx = tcx.infer_ctxt().build(); // Use the inference context `infcx` here. ``` -`infcx` has the type `InferCtxt<'tcx>`, the same `'tcx` lifetime as on -the `tcx` it was built from. +`infcx` has the type `InferCtxt<'tcx>`, the same `'tcx` lifetime as on the `tcx` it was built from. -The `tcx.infer_ctxt` method actually returns a builder, which means -there are some kinds of configuration you can do before the `infcx` is -created. See `InferCtxtBuilder` for more information. +The `tcx.infer_ctxt` method actually returns a builder, +which means there are some kinds of configuration you can do before the `infcx` is +created. +See `InferCtxtBuilder` for more information. ## Inference variables The main purpose of the inference context is to house a bunch of -**inference variables** – these represent types or regions whose precise -value is not yet known, but will be uncovered as we perform type-checking. +**inference variables** – these represent types or regions whose precise value is not yet known, +but will be uncovered as we perform type-checking. -If you're familiar with the basic ideas of unification from H-M type -systems, or logic languages like Prolog, this is the same concept. If -you're not, you might want to read a tutorial on how H-M type -inference works, or perhaps this blog post on -[unification in the Chalk project]. +If you're familiar with the basic ideas of unification from H-M type systems, +or logic languages like Prolog, this is the same concept. +If you're not, you might want to read a tutorial on how H-M type +inference works, or perhaps this blog post on [unification in the Chalk project]. [Unification in the Chalk project]: http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/ @@ -69,30 +65,29 @@ All told, the inference context stores five kinds of inference variables (as of March 2023): - Type variables, which come in three varieties: - - General type variables (the most common). These can be unified with any - type. + - General type variables (the most common). + These can be unified with any type. - Integral type variables, which can only be unified with an integral type, and arise from an integer literal expression like `22`. - - Float type variables, which can only be unified with a float type, and - arise from a float literal expression like `22.0`. + - Float type variables, which can only be unified with a float type, + and arise from a float literal expression like `22.0`. - Region variables, which represent lifetimes, and arise all over the place. - Const variables, which represent constants. -All the type variables work in much the same way: you can create a new -type variable, and what you get is `Ty<'tcx>` representing an -unresolved type `?T`. Then later you can apply the various operations -that the inferencer supports, such as equality or subtyping, and it -will possibly **instantiate** (or **bind**) that `?T` to a specific -value as a result. +All the type variables work in much the same way: you can create a new type variable, +and what you get is `Ty<'tcx>` representing an +unresolved type `?T`. +Then later you can apply the various operations that the inferencer supports, +such as equality or subtyping, and it +will possibly **instantiate** (or **bind**) that `?T` to a specific value as a result. -The region variables work somewhat differently, and are described -below in a separate section. +The region variables work somewhat differently, and are described below in a separate section. ## Enforcing equality / subtyping -The most basic operations you can perform in the type inferencer is -**equality**, which forces two types `T` and `U` to be the same. The -recommended way to add an equality constraint is to use the `at` +The most basic operations you can perform in the type inferencer is **equality**, +which forces two types `T` and `U` to be the same. +The recommended way to add an equality constraint is to use the `at` method, roughly like so: ```rust,ignore @@ -100,71 +95,72 @@ infcx.at(...).eq(t, u); ``` The first `at()` call provides a bit of context, i.e. why you are -doing this unification, and in what environment, and the `eq` method -performs the actual equality constraint. +doing this unification, and in what environment, +and the `eq` method performs the actual equality constraint. -When you equate things, you force them to be precisely equal. Equating -returns an `InferResult` – if it returns `Err(err)`, then equating +When you equate things, you force them to be precisely equal. +Equating returns an `InferResult` – if it returns `Err(err)`, then equating failed, and the enclosing `TypeError` will tell you what went wrong. -The success case is perhaps more interesting. The "primary" return -type of `eq` is `()` – that is, when it succeeds, it doesn't return a -value of any particular interest. Rather, it is executed for its -side-effects of constraining type variables and so forth. However, the -actual return type is not `()`, but rather `InferOk<()>`. The -`InferOk` type is used to carry extra trait obligations – your job is -to ensure that these are fulfilled (typically by enrolling them in a -fulfillment context). See the [trait chapter] for more background on that. +The success case is perhaps more interesting. +The "primary" return type of `eq` is `()` – that is, when it succeeds, it doesn't return a +value of any particular interest. +Rather, it is executed for its side-effects of constraining type variables and so forth. +However, the actual return type is not `()`, but rather `InferOk<()>`. +The `InferOk` type is used to carry extra trait obligations – your job is +to ensure that these are fulfilled (typically by enrolling them in a fulfillment context). +See the [trait chapter] for more background on that. [trait chapter]: traits/resolution.md -You can similarly enforce subtyping through `infcx.at(..).sub(..)`. The same -basic concepts as above apply. +You can similarly enforce subtyping through `infcx.at(..).sub(..)`. +The same basic concepts as above apply. ## "Trying" equality -Sometimes you would like to know if it is *possible* to equate two -types without error. You can test that with `infcx.can_eq` (or -`infcx.can_sub` for subtyping). If this returns `Ok`, then equality -is possible – but in all cases, any side-effects are reversed. +Sometimes you would like to know if it is *possible* to equate two types without error. + You can test that with `infcx.can_eq` (or `infcx.can_sub` for subtyping). +If this returns `Ok`, then equality is possible – but in all cases, any side-effects are reversed. -Be aware, though, that the success or failure of these methods is always -**modulo regions**. That is, two types `&'a u32` and `&'b u32` will -return `Ok` for `can_eq`, even if `'a != 'b`. This falls out from the -"two-phase" nature of how we solve region constraints. +Be aware, though, that the success or failure of these methods is always **modulo regions**. +That is, two types `&'a u32` and `&'b u32` will return `Ok` for `can_eq`, even if `'a != 'b`. + This falls out from the "two-phase" nature of how we solve region constraints. ## Snapshots -As described in the previous section on `can_eq`, often it is useful -to be able to do a series of operations and then roll back their -side-effects. This is done for various reasons: one of them is to be -able to backtrack, trying out multiple possibilities before settling -on which path to take. Another is in order to ensure that a series of -smaller changes take place atomically or not at all. +As described in the previous section on `can_eq`, +often it is useful to be able to do a series of operations and then roll back their +side-effects. +This is done for various reasons: one of them is to be able to backtrack, +trying out multiple possibilities before settling +on which path to take. +Another is in order to ensure that a series of smaller changes take place atomically or not at all. To allow for this, the inference context supports a `snapshot` method. -When you call it, it will start recording changes that occur from the -operations you perform. When you are done, you can either invoke -`rollback_to`, which will undo those changes, or else `confirm`, which -will make them permanent. Snapshots can be nested as long as you follow -a stack-like discipline. - -Rather than use snapshots directly, it is often helpful to use the -methods like `commit_if_ok` or `probe` that encapsulate higher-level +When you call it, it will start recording changes that occur from the operations you perform. +When you are done, you can either invoke `rollback_to`, +which will undo those changes, or else `confirm`, which +will make them permanent. +Snapshots can be nested as long as you follow a stack-like discipline. + +Rather than use snapshots directly, +it is often helpful to use the methods like `commit_if_ok` or `probe` that encapsulate higher-level patterns. ## Subtyping obligations -One thing worth discussing is subtyping obligations. When you force -two types to be a subtype, like `?T <: i32`, we can often convert those -into equality constraints. This follows from Rust's rather limited notion -of subtyping: so, in the above case, `?T <: i32` is equivalent to `?T = i32`. +One thing worth discussing is subtyping obligations. +When you force two types to be a subtype, like `?T <: i32`, we can often convert those +into equality constraints. +This follows from Rust's rather limited notion of subtyping: so, +in the above case, `?T <: i32` is equivalent to `?T = i32`. -However, in some cases we have to be more careful. For example, when -regions are involved. So if you have `?T <: &'a i32`, what we would do -is to first "generalize" `&'a i32` into a type with a region variable: -`&'?b i32`, and then unify `?T` with that (`?T = &'?b i32`). We then -relate this new variable with the original bound: +However, in some cases we have to be more careful. +For example, when regions are involved. +So if you have `?T <: &'a i32`, +what we would do is to first "generalize" `&'a i32` into a type with a region variable: +`&'?b i32`, and then unify `?T` with that (`?T = &'?b i32`). +We then relate this new variable with the original bound: ```text &'?b i32 <: &'a i32 @@ -173,73 +169,74 @@ relate this new variable with the original bound: This will result in a region constraint (see below) of `'?b: 'a`. One final interesting case is relating two unbound type variables, -like `?T <: ?U`. In that case, we can't make progress, so we enqueue -an obligation `Subtype(?T, ?U)` and return it via the `InferOk` -mechanism. You'll have to try again when more details about `?T` or -`?U` are known. +like `?T <: ?U`. + In that case, we can't make progress, so we enqueue an obligation `Subtype(?T, +?U)` and return it via the `InferOk` +mechanism. +You'll have to try again when more details about `?T` or `?U` are known. ## Region constraints -Regions are inferenced somewhat differently from types. Rather than -eagerly unifying things, we simply collect constraints as we go, but -make (almost) no attempt to solve regions. These constraints have the -form of an "outlives" constraint: +Regions are inferenced somewhat differently from types. +Rather than eagerly unifying things, we simply collect constraints as we go, but +make (almost) no attempt to solve regions. +These constraints have the form of an "outlives" constraint: ```text 'a: 'b ``` -Actually the code tends to view them as a subregion relation, but it's the same -idea: +Actually the code tends to view them as a subregion relation, but it's the same idea: ```text 'b <= 'a ``` -(There are various other kinds of constraints, such as "verifys"; see -the [`region_constraints`] module for details.) +(There are various other kinds of constraints, +such as "verifys"; see the [`region_constraints`] module for details.) -There is one case where we do some amount of eager unification. If you have an -equality constraint between two regions +There is one case where we do some amount of eager unification. +If you have an equality constraint between two regions ```text 'a = 'b ``` -we will record that fact in a unification table. You can then use -[`opportunistic_resolve_var`] to convert `'b` to `'a` (or vice -versa). This is sometimes needed to ensure termination of fixed-point -algorithms. +we will record that fact in a unification table. +You can then use [`opportunistic_resolve_var`] to convert `'b` to `'a` (or vice +versa). +This is sometimes needed to ensure termination of fixed-point algorithms. [`region_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_infer/infer/region_constraints/index.html [`opportunistic_resolve_var`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_infer/infer/region_constraints/struct.RegionConstraintCollector.html#method.opportunistic_resolve_var ## Solving region constraints -Region constraints are only solved at the very end of -typechecking, once all other constraints are known and -all other obligations have been proven. There are two -ways to solve region constraints right now: lexical and -non-lexical. Eventually there will only be one. +Region constraints are only solved at the very end of typechecking, +once all other constraints are known and +all other obligations have been proven. +There are two ways to solve region constraints right now: lexical and +non-lexical. +Eventually there will only be one. An exception here is the leak-check which is used during trait solving -and relies on region constraints containing higher-ranked regions. Region +and relies on region constraints containing higher-ranked regions. +Region constraints in the root universe (i.e. not arising from a `for<'a>`) must -not influence the trait system, as these regions are all erased during -codegen. +not influence the trait system, as these regions are all erased during codegen. -To solve **lexical** region constraints, you invoke -[`resolve_regions_and_report_errors`]. This "closes" the region -constraint process and invokes the [`lexical_region_resolve`] code. Once -this is done, any further attempt to equate or create a subtyping +To solve **lexical** region constraints, you invoke [`resolve_regions_and_report_errors`]. + This "closes" the region constraint process and invokes the [`lexical_region_resolve`] code. +Once this is done, any further attempt to equate or create a subtyping relationship will yield an ICE. -The NLL solver (actually, the MIR type-checker) does things slightly -differently. It uses canonical queries for trait solving which use -[`take_and_reset_region_constraints`] at the end. This extracts all of the -outlives constraints added during the canonical query. This is required -as the NLL solver must not only know *what* regions outlive each other, -but also *where*. Finally, the NLL solver invokes [`get_region_var_infos`], +The NLL solver (actually, the MIR type-checker) does things slightly differently. +It uses canonical queries for trait solving which use +[`take_and_reset_region_constraints`] at the end. +This extracts all of the outlives constraints added during the canonical query. +This is required as the NLL solver must not only know *what* regions outlive each other, +but also *where*. +Finally, the NLL solver invokes [`get_region_var_infos`], providing all region variables to the solver. [`resolve_regions_and_report_errors`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/struct.ObligationCtxt.html#method.resolve_regions_and_report_errors @@ -249,8 +246,8 @@ providing all region variables to the solver. ## Lexical region resolution -Lexical region resolution is done by initially assigning each region -variable to an empty value. We then process each outlives constraint -repeatedly, growing region variables until a fixed-point is reached. +Lexical region resolution is done by initially assigning each region variable to an empty value. +We then process each outlives constraint repeatedly, +growing region variables until a fixed-point is reached. Region variables can be grown using a least-upper-bound relation on the region lattice in a fairly straightforward fashion. From eaeddd61678d5b4c3bf6ab848d4789fe50ac797c Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:46:06 +0100 Subject: [PATCH 29/44] improve type-inference.md --- src/doc/rustc-dev-guide/src/type-inference.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/type-inference.md b/src/doc/rustc-dev-guide/src/type-inference.md index 0a37dfbd9928b..aaedb2b825132 100644 --- a/src/doc/rustc-dev-guide/src/type-inference.md +++ b/src/doc/rustc-dev-guide/src/type-inference.md @@ -51,7 +51,8 @@ See `InferCtxtBuilder` for more information. ## Inference variables The main purpose of the inference context is to house a bunch of -**inference variables** – these represent types or regions whose precise value is not yet known, +**inference variables**. +These represent types or regions whose precise value is not yet known, but will be uncovered as we perform type-checking. If you're familiar with the basic ideas of unification from H-M type systems, @@ -77,7 +78,7 @@ All told, the inference context stores five kinds of inference variables All the type variables work in much the same way: you can create a new type variable, and what you get is `Ty<'tcx>` representing an unresolved type `?T`. -Then later you can apply the various operations that the inferencer supports, +You can then later apply the various operations that the inferencer supports, such as equality or subtyping, and it will possibly **instantiate** (or **bind**) that `?T` to a specific value as a result. @@ -119,12 +120,12 @@ The same basic concepts as above apply. ## "Trying" equality Sometimes you would like to know if it is *possible* to equate two types without error. - You can test that with `infcx.can_eq` (or `infcx.can_sub` for subtyping). +You can test that with `infcx.can_eq` (or `infcx.can_sub` for subtyping). If this returns `Ok`, then equality is possible – but in all cases, any side-effects are reversed. Be aware, though, that the success or failure of these methods is always **modulo regions**. That is, two types `&'a u32` and `&'b u32` will return `Ok` for `can_eq`, even if `'a != 'b`. - This falls out from the "two-phase" nature of how we solve region constraints. +This falls out from the "two-phase" nature of how we solve region constraints. ## Snapshots @@ -156,8 +157,8 @@ This follows from Rust's rather limited notion of subtyping: so, in the above case, `?T <: i32` is equivalent to `?T = i32`. However, in some cases we have to be more careful. -For example, when regions are involved. -So if you have `?T <: &'a i32`, +An example is when regions are involved. +So, if you have `?T <: &'a i32`, what we would do is to first "generalize" `&'a i32` into a type with a region variable: `&'?b i32`, and then unify `?T` with that (`?T = &'?b i32`). We then relate this new variable with the original bound: @@ -170,7 +171,7 @@ This will result in a region constraint (see below) of `'?b: 'a`. One final interesting case is relating two unbound type variables, like `?T <: ?U`. - In that case, we can't make progress, so we enqueue an obligation `Subtype(?T, +In that case, we can't make progress, so we enqueue an obligation `Subtype(?T, ?U)` and return it via the `InferOk` mechanism. You'll have to try again when more details about `?T` or `?U` are known. @@ -226,7 +227,7 @@ constraints in the root universe (i.e. not arising from a `for<'a>`) must not influence the trait system, as these regions are all erased during codegen. To solve **lexical** region constraints, you invoke [`resolve_regions_and_report_errors`]. - This "closes" the region constraint process and invokes the [`lexical_region_resolve`] code. +This "closes" the region constraint process and invokes the [`lexical_region_resolve`] code. Once this is done, any further attempt to equate or create a subtyping relationship will yield an ICE. From 8ce5ef2b98d2a56cbdc0e167e96c8f83a84338c1 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 21:47:05 +0100 Subject: [PATCH 30/44] reflow src/type-inference.md --- src/doc/rustc-dev-guide/src/type-inference.md | 68 ++++++++----------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/type-inference.md b/src/doc/rustc-dev-guide/src/type-inference.md index aaedb2b825132..31f2f4b198e63 100644 --- a/src/doc/rustc-dev-guide/src/type-inference.md +++ b/src/doc/rustc-dev-guide/src/type-inference.md @@ -16,8 +16,7 @@ Here, the type of `things` is *inferred* to be `Vec<&str>` because of the value we push into `things`. The type inference is based on the standard Hindley-Milner (HM) type inference algorithm, -but extended in various ways to accommodate subtyping, region -inference, and higher-ranked types. +but extended in various ways to accommodate subtyping, region inference, and higher-ranked types. ## A note on terminology @@ -42,23 +41,21 @@ let infcx = tcx.infer_ctxt().build(); `infcx` has the type `InferCtxt<'tcx>`, the same `'tcx` lifetime as on the `tcx` it was built from. The `tcx.infer_ctxt` method actually returns a builder, -which means there are some kinds of configuration you can do before the `infcx` is -created. +which means there are some kinds of configuration you can do before the `infcx` is created. See `InferCtxtBuilder` for more information. ## Inference variables -The main purpose of the inference context is to house a bunch of -**inference variables**. +The main purpose of the inference context is to house a bunch of **inference variables**. These represent types or regions whose precise value is not yet known, but will be uncovered as we perform type-checking. If you're familiar with the basic ideas of unification from H-M type systems, or logic languages like Prolog, this is the same concept. -If you're not, you might want to read a tutorial on how H-M type -inference works, or perhaps this blog post on [unification in the Chalk project]. +If you're not, you might want to read a tutorial on how H-M type inference works, +or perhaps this blog post on [unification in the Chalk project]. [Unification in the Chalk project]: http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/ @@ -76,11 +73,10 @@ All told, the inference context stores five kinds of inference variables - Const variables, which represent constants. All the type variables work in much the same way: you can create a new type variable, -and what you get is `Ty<'tcx>` representing an -unresolved type `?T`. +and what you get is `Ty<'tcx>` representing an unresolved type `?T`. You can then later apply the various operations that the inferencer supports, -such as equality or subtyping, and it -will possibly **instantiate** (or **bind**) that `?T` to a specific value as a result. +such as equality or subtyping, +and it will possibly **instantiate** (or **bind**) that `?T` to a specific value as a result. The region variables work somewhat differently, and are described below in a separate section. @@ -88,8 +84,7 @@ The region variables work somewhat differently, and are described below in a sep The most basic operations you can perform in the type inferencer is **equality**, which forces two types `T` and `U` to be the same. -The recommended way to add an equality constraint is to use the `at` -method, roughly like so: +The recommended way to add an equality constraint is to use the `at` method, roughly like so: ```rust,ignore infcx.at(...).eq(t, u); @@ -100,12 +95,12 @@ doing this unification, and in what environment, and the `eq` method performs the actual equality constraint. When you equate things, you force them to be precisely equal. -Equating returns an `InferResult` – if it returns `Err(err)`, then equating -failed, and the enclosing `TypeError` will tell you what went wrong. +Equating returns an `InferResult` – if it returns `Err(err)`, then equating failed, +and the enclosing `TypeError` will tell you what went wrong. The success case is perhaps more interesting. -The "primary" return type of `eq` is `()` – that is, when it succeeds, it doesn't return a -value of any particular interest. +The "primary" return type of `eq` is `()` – that is, when it succeeds, +it doesn't return a value of any particular interest. Rather, it is executed for its side-effects of constraining type variables and so forth. However, the actual return type is not `()`, but rather `InferOk<()>`. The `InferOk` type is used to carry extra trait obligations – your job is @@ -130,18 +125,15 @@ This falls out from the "two-phase" nature of how we solve region constraints. ## Snapshots As described in the previous section on `can_eq`, -often it is useful to be able to do a series of operations and then roll back their -side-effects. +often it is useful to be able to do a series of operations and then roll back their side-effects. This is done for various reasons: one of them is to be able to backtrack, -trying out multiple possibilities before settling -on which path to take. +trying out multiple possibilities before settling on which path to take. Another is in order to ensure that a series of smaller changes take place atomically or not at all. To allow for this, the inference context supports a `snapshot` method. When you call it, it will start recording changes that occur from the operations you perform. When you are done, you can either invoke `rollback_to`, -which will undo those changes, or else `confirm`, which -will make them permanent. +which will undo those changes, or else `confirm`, which will make them permanent. Snapshots can be nested as long as you follow a stack-like discipline. Rather than use snapshots directly, @@ -151,16 +143,16 @@ patterns. ## Subtyping obligations One thing worth discussing is subtyping obligations. -When you force two types to be a subtype, like `?T <: i32`, we can often convert those -into equality constraints. +When you force two types to be a subtype, like `?T <: i32`, +we can often convert those into equality constraints. This follows from Rust's rather limited notion of subtyping: so, in the above case, `?T <: i32` is equivalent to `?T = i32`. However, in some cases we have to be more careful. An example is when regions are involved. So, if you have `?T <: &'a i32`, -what we would do is to first "generalize" `&'a i32` into a type with a region variable: -`&'?b i32`, and then unify `?T` with that (`?T = &'?b i32`). +what we would do is to first "generalize" `&'a i32` into a type with a region variable: `&'?b i32`, +and then unify `?T` with that (`?T = &'?b i32`). We then relate this new variable with the original bound: ```text @@ -172,15 +164,14 @@ This will result in a region constraint (see below) of `'?b: 'a`. One final interesting case is relating two unbound type variables, like `?T <: ?U`. In that case, we can't make progress, so we enqueue an obligation `Subtype(?T, -?U)` and return it via the `InferOk` -mechanism. +?U)` and return it via the `InferOk` mechanism. You'll have to try again when more details about `?T` or `?U` are known. ## Region constraints Regions are inferenced somewhat differently from types. -Rather than eagerly unifying things, we simply collect constraints as we go, but -make (almost) no attempt to solve regions. +Rather than eagerly unifying things, we simply collect constraints as we go, +but make (almost) no attempt to solve regions. These constraints have the form of an "outlives" constraint: ```text @@ -204,8 +195,7 @@ If you have an equality constraint between two regions ``` we will record that fact in a unification table. -You can then use [`opportunistic_resolve_var`] to convert `'b` to `'a` (or vice -versa). +You can then use [`opportunistic_resolve_var`] to convert `'b` to `'a` (or vice versa). This is sometimes needed to ensure termination of fixed-point algorithms. [`region_constraints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_infer/infer/region_constraints/index.html @@ -214,10 +204,8 @@ This is sometimes needed to ensure termination of fixed-point algorithms. ## Solving region constraints Region constraints are only solved at the very end of typechecking, -once all other constraints are known and -all other obligations have been proven. -There are two ways to solve region constraints right now: lexical and -non-lexical. +once all other constraints are known and all other obligations have been proven. +There are two ways to solve region constraints right now: lexical and non-lexical. Eventually there will only be one. An exception here is the leak-check which is used during trait solving @@ -228,8 +216,8 @@ not influence the trait system, as these regions are all erased during codegen. To solve **lexical** region constraints, you invoke [`resolve_regions_and_report_errors`]. This "closes" the region constraint process and invokes the [`lexical_region_resolve`] code. -Once this is done, any further attempt to equate or create a subtyping -relationship will yield an ICE. +Once this is done, +any further attempt to equate or create a subtyping relationship will yield an ICE. The NLL solver (actually, the MIR type-checker) does things slightly differently. It uses canonical queries for trait solving which use From b22380e03d003a1758a7e926570071e0ebff3df5 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 22:50:03 +0100 Subject: [PATCH 31/44] double space is very common... I do not remember seeing more --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 56761cbb07487..10b41e7703449 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -122,9 +122,8 @@ fn comply(content: &str) -> String { }; let mut newly_split_lines = line.split_inclusive(&*REGEX_SPLIT); let first = newly_split_lines.next().unwrap().trim_end().to_owned(); - let mut remaining: Vec<_> = newly_split_lines - .map(|portion| format!("{:indent$}{}", "", portion.trim_end())) - .collect(); + let mut remaining: Vec<_> = + newly_split_lines.map(|portion| format!("{:indent$}{}", "", portion.trim())).collect(); let mut new_lines = Vec::new(); new_lines.push(first); new_lines.append(&mut remaining); @@ -244,13 +243,14 @@ some code. block ``` sentence with *italics* should not be ignored. truly. git log main.. compiler - foo. bar. baz o? whatever r? @reviewer r? @reviewer ~? diagnostic the queries that we do, as well as the **query DAG**. The + +trim double space. or many spaces. in this next sentence "; let expected = " # some. heading @@ -276,9 +276,6 @@ some code. block sentence with *italics* should not be ignored. truly. git log main.. compiler - foo. - bar. - baz o? whatever r? @reviewer @@ -287,6 +284,10 @@ r? @reviewer the queries that we do, as well as the **query DAG**. The + +trim double space. +or many spaces. +in this next sentence "; assert_eq!(expected, comply(original)); } From 70a37c2b6b6600ddf912b9460924b21c928ebbda Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 22:59:11 +0100 Subject: [PATCH 32/44] binding not needed --- src/doc/rustc-dev-guide/ci/sembr/src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs index 10b41e7703449..6125f2261cc77 100644 --- a/src/doc/rustc-dev-guide/ci/sembr/src/main.rs +++ b/src/doc/rustc-dev-guide/ci/sembr/src/main.rs @@ -121,11 +121,10 @@ fn comply(content: &str) -> String { line.find(|ch: char| !ch.is_whitespace()).unwrap() }; let mut newly_split_lines = line.split_inclusive(&*REGEX_SPLIT); - let first = newly_split_lines.next().unwrap().trim_end().to_owned(); + let mut new_lines = Vec::new(); + new_lines.push(newly_split_lines.next().unwrap().trim_end().to_owned()); let mut remaining: Vec<_> = newly_split_lines.map(|portion| format!("{:indent$}{}", "", portion.trim())).collect(); - let mut new_lines = Vec::new(); - new_lines.push(first); new_lines.append(&mut remaining); new_content.splice(new_n..=new_n, new_lines.clone()); new_n += new_lines.len() - 1; From 0a70cc57aee6470fc99b4b21222e699a1b8d54e1 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 22:59:52 +0100 Subject: [PATCH 33/44] sembr src/ty-module/early-binder.md --- .../src/ty-module/early-binder.md | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md index d83d73c955bfc..e5b43f3837a4a 100644 --- a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md +++ b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md @@ -10,11 +10,20 @@ fn main() { } ``` -When type checking `main` we cannot just naively look at the return type of `foo` and assign the type `T` to the variable `c`, The function `main` does not define any generic parameters, `T` is completely meaningless in this context. More generally whenever an item introduces (binds) generic parameters, when accessing types inside the item from outside, the generic parameters must be instantiated with values from the outer item. +When type checking `main` we cannot just naively look at the return type of `foo` and assign the type `T` to the variable `c`, The function `main` does not define any generic parameters, +`T` is completely meaningless in this context. +More generally whenever an item introduces (binds) generic parameters, when accessing types inside the item from outside, +the generic parameters must be instantiated with values from the outer item. -In rustc we track this via the [`EarlyBinder`] type, the return type of `foo` is represented as an `EarlyBinder` with the only way to access `Ty` being to provide arguments for any generic parameters `Ty` might be using. This is implemented via the [`EarlyBinder::instantiate`] method which discharges the binder returning the inner value with all the generic parameters replaced by the provided arguments. +In rustc we track this via the [`EarlyBinder`] type, +the return type of `foo` is represented as an `EarlyBinder` with the only way to access `Ty` being to provide arguments for any generic parameters `Ty` might be using. +This is implemented via the [`EarlyBinder::instantiate`] method which discharges the binder returning the inner value with all the generic parameters replaced by the provided arguments. -To go back to our example, when type checking `main` the return type of `foo` would be represented as `EarlyBinder(T/#0)`. Then, because we called the function with `i32, u128` for the generic arguments, we would call `EarlyBinder::instantiate` on the return type with `[i32, u128]` for the args. This would result in an instantiated return type of `i32` that we can use as the type of the local `c`. +To go back to our example, +when type checking `main` the return type of `foo` would be represented as `EarlyBinder(T/#0)`. +Then, because we called the function with `i32, u128` for the generic arguments, we would call `EarlyBinder::instantiate` on the return type with `[i32, +u128]` for the args. +This would result in an instantiated return type of `i32` that we can use as the type of the local `c`. Here are some more examples: @@ -45,12 +54,14 @@ fn bar(foo: Foo) { } ``` -In the compiler the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), at some point during type checking `bar` we will wind up calling `FieldDef::ty(x, &[u32, f32])` in order to obtain the type of `foo.x`. +In the compiler the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), at some point during type checking `bar` we will wind up calling `FieldDef::ty(x, &[u32, +f32])` in order to obtain the type of `foo.x`. -**Note on indices:** It is a bug if the index of a `Param` does not match what the `EarlyBinder` binds. For -example, if the index is out of bounds or the index of a lifetime corresponds to a type parameter. +**Note on indices:** It is a bug if the index of a `Param` does not match what the `EarlyBinder` binds. +For example, +if the index is out of bounds or the index of a lifetime corresponds to a type parameter. These sorts of errors are caught earlier in the compiler during name resolution where we disallow references -to generics parameters introduced by items that should not be nameable by the inner item. +to generics parameters introduced by items that should not be nameable by the inner item. [`FieldDef::ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.FieldDef.html#method.ty [field_def_ty_src]: https://github.com/rust-lang/rust/blob/44d679b9021f03a79133021b94e6d23e9b55b3ab/compiler/rustc_middle/src/ty/mod.rs#L1421-L1426 @@ -59,7 +70,8 @@ to generics parameters introduced by items that should not be nameable by the in --- -As mentioned previously when _outside_ of an item, it is important to instantiate the `EarlyBinder` with generic arguments before accessing the value inside, but the setup for when we are conceptually inside of the binder already is a bit different. +As mentioned previously when _outside_ of an item, it is important to instantiate the `EarlyBinder` with generic arguments before accessing the value inside, +but the setup for when we are conceptually inside of the binder already is a bit different. For example: ```rust @@ -68,9 +80,14 @@ impl Trait for Vec { } ``` -When constructing a `Ty` to represent the `b` parameter's type we need to get the type of `Self` on the impl that we are inside. This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`, however, this will return a `EarlyBinder` as the impl block binds generic parameters that may have to be discharged if we are outside of the impl. +When constructing a `Ty` to represent the `b` parameter's type we need to get the type of `Self` on the impl that we are inside. +This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`, however, +this will return a `EarlyBinder` as the impl block binds generic parameters that may have to be discharged if we are outside of the impl. -The `EarlyBinder` type provides an [`instantiate_identity`] function for discharging the binder when you are "already inside of it". This is effectively a more performant version of writing `EarlyBinder::instantiate(GenericArgs::identity_for_item(..))`. Conceptually this discharges the binder by instantiating it with placeholders in the root universe (we will talk about what this means in the next few chapters). In practice though it simply returns the inner value with no modification taking place. +The `EarlyBinder` type provides an [`instantiate_identity`] function for discharging the binder when you are "already inside of it". +This is effectively a more performant version of writing `EarlyBinder::instantiate(GenericArgs::identity_for_item(..))`. +Conceptually this discharges the binder by instantiating it with placeholders in the root universe (we will talk about what this means in the next few chapters). +In practice though it simply returns the inner value with no modification taking place. [`type_of`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.type_of [`instantiate_identity`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.EarlyBinder.html#method.instantiate_identity From 0ca475be2896cf1bc9f99c383d92fcd1de58c378 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:08:12 +0100 Subject: [PATCH 34/44] improve ty-module/early-binder.md --- .../src/ty-module/early-binder.md | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md index e5b43f3837a4a..016ac9de84d42 100644 --- a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md +++ b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md @@ -1,6 +1,9 @@ # `EarlyBinder` and instantiating parameters -Given an item that introduces a generic parameter `T`, whenever we refer to types inside of `foo` (i.e. the return type or argument types) from outside of `foo` we must take care to handle the generic parameters defined on `foo`. As an example: +Given an item that introduces a generic parameter `T`, +whenever we refer to types inside of `foo` (i.e. the return type or argument types) from outside of `foo`, +we must take care to handle the generic parameters defined on `foo`. +As an example: ```rust,ignore fn foo(a: T, _b: U) -> T { a } @@ -10,9 +13,11 @@ fn main() { } ``` -When type checking `main` we cannot just naively look at the return type of `foo` and assign the type `T` to the variable `c`, The function `main` does not define any generic parameters, -`T` is completely meaningless in this context. -More generally whenever an item introduces (binds) generic parameters, when accessing types inside the item from outside, +When type checking `main`, +we cannot just naively look at the return type of `foo` and assign the type `T` to the variable `c`. +The function `main` does not define any generic parameters, +and `T` is completely meaningless in this context. +More generally, whenever an item introduces (binds) generic parameters, when accessing types inside the item from outside, the generic parameters must be instantiated with values from the outer item. In rustc we track this via the [`EarlyBinder`] type, @@ -20,7 +25,8 @@ the return type of `foo` is represented as an `EarlyBinder` with the only wa This is implemented via the [`EarlyBinder::instantiate`] method which discharges the binder returning the inner value with all the generic parameters replaced by the provided arguments. To go back to our example, -when type checking `main` the return type of `foo` would be represented as `EarlyBinder(T/#0)`. +when type checking `main`, +the return type of `foo` would be represented as `EarlyBinder(T/#0)`. Then, because we called the function with `i32, u128` for the generic arguments, we would call `EarlyBinder::instantiate` on the return type with `[i32, u128]` for the args. This would result in an instantiated return type of `i32` that we can use as the type of the local `c`. @@ -44,7 +50,7 @@ struct Foo { .. } -fn bar(foo: Foo) { +fn bar(foo: Foo) { // the type of `foo`'s `x` field before instantiating it would be: // `EarlyBinder(Vec)` // we then instantiate the binder with `[u32, f32]` as those are the @@ -54,7 +60,7 @@ fn bar(foo: Foo) { } ``` -In the compiler the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), at some point during type checking `bar` we will wind up calling `FieldDef::ty(x, &[u32, +In the compiler, the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), and at some point during type checking `bar`, we will wind up calling `FieldDef::ty(x, &[u32, f32])` in order to obtain the type of `foo.x`. **Note on indices:** It is a bug if the index of a `Param` does not match what the `EarlyBinder` binds. @@ -80,14 +86,15 @@ impl Trait for Vec { } ``` -When constructing a `Ty` to represent the `b` parameter's type we need to get the type of `Self` on the impl that we are inside. -This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`, however, +When constructing a `Ty` to represent the `b` parameter's type, we need to get the type of `Self` on the impl that we are inside of. +This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`. However, this will return a `EarlyBinder` as the impl block binds generic parameters that may have to be discharged if we are outside of the impl. The `EarlyBinder` type provides an [`instantiate_identity`] function for discharging the binder when you are "already inside of it". This is effectively a more performant version of writing `EarlyBinder::instantiate(GenericArgs::identity_for_item(..))`. -Conceptually this discharges the binder by instantiating it with placeholders in the root universe (we will talk about what this means in the next few chapters). -In practice though it simply returns the inner value with no modification taking place. +Conceptually, this discharges the binder by instantiating it with placeholders in the root universe +(and we will talk about what this means in the next few chapters). +In practice, though, it simply returns the inner value with no modification taking place. [`type_of`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html#method.type_of [`instantiate_identity`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.EarlyBinder.html#method.instantiate_identity From 321fc64bd3db74df12a97f922f9a79dee9ecb3a6 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:10:36 +0100 Subject: [PATCH 35/44] sembr src/ty-module/early-binder.md --- .../src/ty-module/early-binder.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md index 016ac9de84d42..d46f8a5fb935c 100644 --- a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md +++ b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md @@ -17,7 +17,8 @@ When type checking `main`, we cannot just naively look at the return type of `foo` and assign the type `T` to the variable `c`. The function `main` does not define any generic parameters, and `T` is completely meaningless in this context. -More generally, whenever an item introduces (binds) generic parameters, when accessing types inside the item from outside, +More generally, whenever an item introduces (binds) generic parameters, +when accessing types inside the item from outside, the generic parameters must be instantiated with values from the outer item. In rustc we track this via the [`EarlyBinder`] type, @@ -27,7 +28,8 @@ This is implemented via the [`EarlyBinder::instantiate`] method which discharges To go back to our example, when type checking `main`, the return type of `foo` would be represented as `EarlyBinder(T/#0)`. -Then, because we called the function with `i32, u128` for the generic arguments, we would call `EarlyBinder::instantiate` on the return type with `[i32, +Then, because we called the function with `i32, u128` for the generic arguments, +we would call `EarlyBinder::instantiate` on the return type with `[i32, u128]` for the args. This would result in an instantiated return type of `i32` that we can use as the type of the local `c`. @@ -60,7 +62,8 @@ fn bar(foo: Foo) { } ``` -In the compiler, the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), and at some point during type checking `bar`, we will wind up calling `FieldDef::ty(x, &[u32, +In the compiler, the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), and at some point during type checking `bar`, we will wind up calling `FieldDef::ty(x, +&[u32, f32])` in order to obtain the type of `foo.x`. **Note on indices:** It is a bug if the index of a `Param` does not match what the `EarlyBinder` binds. @@ -76,7 +79,8 @@ to generics parameters introduced by items that should not be nameable by the in --- -As mentioned previously when _outside_ of an item, it is important to instantiate the `EarlyBinder` with generic arguments before accessing the value inside, +As mentioned previously when _outside_ of an item, +it is important to instantiate the `EarlyBinder` with generic arguments before accessing the value inside, but the setup for when we are conceptually inside of the binder already is a bit different. For example: @@ -86,8 +90,10 @@ impl Trait for Vec { } ``` -When constructing a `Ty` to represent the `b` parameter's type, we need to get the type of `Self` on the impl that we are inside of. -This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`. However, +When constructing a `Ty` to represent the `b` parameter's type, +we need to get the type of `Self` on the impl that we are inside of. +This can be acquired by calling the [`type_of`] query with the `impl`'s `DefId`. +However, this will return a `EarlyBinder` as the impl block binds generic parameters that may have to be discharged if we are outside of the impl. The `EarlyBinder` type provides an [`instantiate_identity`] function for discharging the binder when you are "already inside of it". From 3fa40ce8d3ec7d889cf7066853aa98e64b353edb Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:13:55 +0100 Subject: [PATCH 36/44] reflow src/ty-module/early-binder.md --- src/doc/rustc-dev-guide/src/ty-module/early-binder.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md index d46f8a5fb935c..d1c311827cc1b 100644 --- a/src/doc/rustc-dev-guide/src/ty-module/early-binder.md +++ b/src/doc/rustc-dev-guide/src/ty-module/early-binder.md @@ -29,8 +29,7 @@ To go back to our example, when type checking `main`, the return type of `foo` would be represented as `EarlyBinder(T/#0)`. Then, because we called the function with `i32, u128` for the generic arguments, -we would call `EarlyBinder::instantiate` on the return type with `[i32, -u128]` for the args. +we would call `EarlyBinder::instantiate` on the return type with `[i32, u128]` for the args. This would result in an instantiated return type of `i32` that we can use as the type of the local `c`. Here are some more examples: @@ -62,9 +61,9 @@ fn bar(foo: Foo) { } ``` -In the compiler, the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), and at some point during type checking `bar`, we will wind up calling `FieldDef::ty(x, -&[u32, -f32])` in order to obtain the type of `foo.x`. +In the compiler, the `instantiate` call for this is done in [`FieldDef::ty`] ([src][field_def_ty_src]), +and at some point during type checking `bar`, +we will wind up calling `FieldDef::ty(x, &[u32, f32])` in order to obtain the type of `foo.x`. **Note on indices:** It is a bug if the index of a `Param` does not match what the `EarlyBinder` binds. For example, From a0dbfe0728f17c31f5b5f81b00466b0928f14c53 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:14:32 +0100 Subject: [PATCH 37/44] sembr src/traits/resolution.md --- .../rustc-dev-guide/src/traits/resolution.md | 194 +++++++++--------- 1 file changed, 98 insertions(+), 96 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/resolution.md b/src/doc/rustc-dev-guide/src/traits/resolution.md index 394d800b5f868..1ae5bde54d53d 100644 --- a/src/doc/rustc-dev-guide/src/traits/resolution.md +++ b/src/doc/rustc-dev-guide/src/traits/resolution.md @@ -3,15 +3,15 @@ This chapter describes the general process of _trait resolution_ and points out some non-obvious things. -**Note:** This chapter (and its subchapters) describe how the trait -solver **currently** works. However, we are in the process of -designing a new trait solver. If you'd prefer to read about *that*, +**Note:** This chapter (and its subchapters) describe how the trait solver **currently** works. +However, we are in the process of designing a new trait solver. +If you'd prefer to read about *that*, see [*this* subchapter](./chalk.md). ## Major concepts -Trait resolution is the process of pairing up an impl with each -reference to a trait. So, for example, if there is a generic function like: +Trait resolution is the process of pairing up an impl with each reference to a trait. +So, for example, if there is a generic function like: ```rust,ignore fn clone_slice(x: &[T]) -> Vec { ... } @@ -26,9 +26,10 @@ let v: Vec = clone_slice(&[1, 2, 3]) it is the job of trait resolution to figure out whether there exists an impl of (in this case) `isize : Clone`. -Note that in some cases, like generic functions, we may not be able to -find a specific impl, but we can figure out that the caller must -provide an impl. For example, consider the body of `clone_slice`: +Note that in some cases, like generic functions, we may not be able to find a specific impl, +but we can figure out that the caller must +provide an impl. +For example, consider the body of `clone_slice`: ```rust,ignore fn clone_slice(x: &[T]) -> Vec { @@ -39,57 +40,55 @@ fn clone_slice(x: &[T]) -> Vec { } ``` -The line marked `(*)` is only legal if `T` (the type of `*e`) -implements the `Clone` trait. Naturally, since we don't know what `T` -is, we can't find the specific impl; but based on the bound `T:Clone`, +The line marked `(*)` is only legal if `T` (the type of `*e`) implements the `Clone` trait. +Naturally, since we don't know what `T` is, +we can't find the specific impl; but based on the bound `T:Clone`, we can say that there exists an impl which the caller must provide. -We use the term *obligation* to refer to a trait reference in need of -an impl. Basically, the trait resolution system resolves an obligation -by proving that an appropriate impl does exist. +We use the term *obligation* to refer to a trait reference in need of an impl. +Basically, +the trait resolution system resolves an obligation by proving that an appropriate impl does exist. During type checking, we do not store the results of trait selection. -We simply wish to verify that trait selection will succeed. Then -later, at codegen time, when we have all concrete types available, we -can repeat the trait selection to choose an actual implementation, which -will then be generated in the output binary. +We simply wish to verify that trait selection will succeed. +Then later, at codegen time, when we have all concrete types available, we +can repeat the trait selection to choose an actual implementation, +which will then be generated in the output binary. ## Overview Trait resolution consists of three major parts: -- **Selection**: Deciding how to resolve a specific obligation. For - example, selection might decide that a specific obligation can be +- **Selection**: Deciding how to resolve a specific obligation. + For example, selection might decide that a specific obligation can be resolved by employing an impl which matches the `Self` type, or by using a parameter bound (e.g. `T: Trait`). In the case of an impl, selecting one - obligation can create *nested obligations* because of where clauses - on the impl itself. It may also require evaluating those nested - obligations to resolve ambiguities. + obligation can create *nested obligations* because of where clauses on the impl itself. + It may also require evaluating those nested obligations to resolve ambiguities. -- **Fulfillment**: The fulfillment code is what tracks that obligations - are completely fulfilled. Basically it is a worklist of obligations - to be selected: once selection is successful, the obligation is +- **Fulfillment**: The fulfillment code is what tracks that obligations are completely fulfilled. + Basically it is a worklist of obligations to be selected: once selection is successful, + the obligation is removed from the worklist and any nested obligations are enqueued. Fulfillment constrains inference variables. -- **Evaluation**: Checks whether obligations holds without constraining - any inference variables. Used by selection. +- **Evaluation**: Checks whether obligations holds without constraining any inference variables. + Used by selection. ## Selection -Selection is the process of deciding whether an obligation can be -resolved and, if so, how it is to be resolved (via impl, where clause, etc). -The main interface is the `select()` function, which takes an obligation -and returns a `SelectionResult`. There are three possible outcomes: +Selection is the process of deciding whether an obligation can be resolved and, +if so, how it is to be resolved (via impl, where clause, etc). +The main interface is the `select()` function, +which takes an obligation and returns a `SelectionResult`. +There are three possible outcomes: -- `Ok(Some(selection))` – yes, the obligation can be resolved, and - `selection` indicates how. If the impl was resolved via an impl, - then `selection` may also indicate nested obligations that are required - by the impl. +- `Ok(Some(selection))` – yes, the obligation can be resolved, and `selection` indicates how. + If the impl was resolved via an impl, + then `selection` may also indicate nested obligations that are required by the impl. -- `Ok(None)` – we are not yet sure whether the obligation can be - resolved or not. This happens most commonly when the obligation - contains unbound type variables. +- `Ok(None)` – we are not yet sure whether the obligation can be resolved or not. + This happens most commonly when the obligation contains unbound type variables. - `Err(err)` – the obligation definitely cannot be resolved due to a type error or because there are no impls that could possibly apply. @@ -97,42 +96,42 @@ and returns a `SelectionResult`. There are three possible outcomes: The basic algorithm for selection is broken into two big phases: candidate assembly and confirmation. -Note that because of how lifetime inference works, it is not possible to -give back immediate feedback as to whether a unification or subtype -relationship between lifetimes holds or not. Therefore, lifetime -matching is *not* considered during selection. This is reflected in -the fact that subregion assignment is infallible. This may yield -lifetime constraints that will later be found to be in error (in +Note that because of how lifetime inference works, +it is not possible to give back immediate feedback as to whether a unification or subtype +relationship between lifetimes holds or not. +Therefore, lifetime matching is *not* considered during selection. +This is reflected in the fact that subregion assignment is infallible. +This may yield lifetime constraints that will later be found to be in error (in contrast, the non-lifetime-constraints have already been checked -during selection and can never cause an error, though naturally they -may lead to other errors downstream). +during selection and can never cause an error, +though naturally they may lead to other errors downstream). ### Candidate assembly **TODO**: Talk about _why_ we have different candidates, and why it needs to happen in a probe. -Searches for impls/where-clauses/etc that might -possibly be used to satisfy the obligation. Each of those is called -a candidate. To avoid ambiguity, we want to find exactly one -candidate that is definitively applicable. In some cases, we may not -know whether an impl/where-clause applies or not – this occurs when +Searches for impls/where-clauses/etc that might possibly be used to satisfy the obligation. +Each of those is called a candidate. +To avoid ambiguity, we want to find exactly one candidate that is definitively applicable. +In some cases, we may not know whether an impl/where-clause applies or not – this occurs when the obligation contains unbound inference variables. The subroutines that decide whether a particular impl/where-clause/etc applies to a particular obligation are collectively referred to as the process of _matching_. For `impl` candidates , this amounts to unifying the impl header (the `Self` type and the trait arguments) -while ignoring nested obligations. If matching succeeds then we add it -to a set of candidates. There are other rules when assembling candidates for -built-in traits such as `Copy`, `Sized`, and `CoerceUnsized`. +while ignoring nested obligations. +If matching succeeds then we add it to a set of candidates. +There are other rules when assembling candidates for built-in traits such as `Copy`, +`Sized`, and `CoerceUnsized`. -Once this first pass is done, we can examine the set of candidates. If -it is a singleton set, then we are done: this is the only impl in -scope that could possibly apply. Otherwise, we can **winnow** down the set -of candidates by using where clauses and other conditions. Winnowing uses -`evaluate_candidate` to check whether the nested obligations may apply. -If this still leaves more than 1 candidate, we use ` fn candidate_should_be_dropped_in_favor_of` -to prefer some candidates over others. +Once this first pass is done, we can examine the set of candidates. +If it is a singleton set, then we are done: this is the only impl in +scope that could possibly apply. +Otherwise, we can **winnow** down the set of candidates by using where clauses and other conditions. +Winnowing uses `evaluate_candidate` to check whether the nested obligations may apply. +If this still leaves more than 1 candidate, +we use ` fn candidate_should_be_dropped_in_favor_of` to prefer some candidates over others. If this reduced set yields a single, unambiguous entry, we're good to go, @@ -140,8 +139,8 @@ otherwise the result is considered ambiguous. #### Winnowing: Resolving ambiguities -But what happens if there are multiple impls where all the types -unify? Consider this example: +But what happens if there are multiple impls where all the types unify? +Consider this example: ```rust,ignore trait Get { @@ -161,24 +160,26 @@ impl Get for Box { } ``` -What happens when we invoke `get(&Box::new(1_u16))`, for example? In this -case, the `Self` type is `Box` – that unifies with both impls, -because the first applies to all types `T`, and the second to all -`Box`. In order for this to be unambiguous, the compiler does a *winnowing* -pass that considers `where` clauses -and attempts to remove candidates. In this case, the first impl only -applies if `Box : Copy`, which doesn't hold. After winnowing, +What happens when we invoke `get(&Box::new(1_u16))`, for example? +In this case, the `Self` type is `Box` – that unifies with both impls, +because the first applies to all types `T`, and the second to all `Box`. +In order for this to be unambiguous, +the compiler does a *winnowing* pass that considers `where` clauses +and attempts to remove candidates. +In this case, the first impl only applies if `Box : Copy`, which doesn't hold. +After winnowing, then, we are left with just one candidate, so we can proceed. #### `where` clauses -Besides an impl, the other major way to resolve an obligation is via a -where clause. The selection process is always given a [parameter -environment] which contains a list of where clauses, which are -basically obligations that we can assume are satisfiable. We will iterate -over that list and check whether our current obligation can be found -in that list. If so, it is considered satisfied. More precisely, we -want to check whether there is a where-clause obligation that is for +Besides an impl, the other major way to resolve an obligation is via a where clause. +The selection process is always given a [parameter +environment] which contains a list of where clauses, +which are basically obligations that we can assume are satisfiable. +We will iterate over that list and check whether our current obligation can be found +in that list. +If so, it is considered satisfied. +More precisely, we want to check whether there is a where-clause obligation that is for the same trait (or some subtrait) and which can match against the obligation. [parameter environment]: ../typing-parameter-envs.md @@ -201,12 +202,13 @@ fn foo(x: X) { } ``` -In the body of `foo`, clearly we can use methods of `A1`, `A2`, or `B` -on variable `x`. The line marked `(*)` will incur an obligation `X: A1`, -while the line marked `(#)` will incur an obligation `X: B`. Meanwhile, +In the body of `foo`, clearly we can use methods of `A1`, `A2`, or `B` on variable `x`. +The line marked `(*)` will incur an obligation `X: A1`, +while the line marked `(#)` will incur an obligation `X: B`. +Meanwhile, the parameter environment will contain two where-clauses: `X : A2` and `X : B`. -For each obligation, then, we search this list of where-clauses. The -obligation `X: B` trivially matches against the where-clause `X: B`. +For each obligation, then, we search this list of where-clauses. +The obligation `X: B` trivially matches against the where-clause `X: B`. To resolve an obligation `X:A1`, we would note that `X:A2` implies that `X:A1`. ### Confirmation @@ -214,8 +216,7 @@ To resolve an obligation `X:A1`, we would note that `X:A2` implies that `X:A1`. _Confirmation_ unifies the output type parameters of the trait with the values found in the obligation, possibly yielding a type error. -Suppose we have the following variation of the `Convert` example in the -previous section: +Suppose we have the following variation of the `Convert` example in the previous section: ```rust,ignore trait Convert { @@ -230,24 +231,25 @@ let y: char = x.convert(); // NOTE: `y: char` now! ``` Confirmation is where an error would be reported because the impl specified -that `Target` would be `usize`, but the obligation reported `char`. Hence the -result of selection would be an error. +that `Target` would be `usize`, but the obligation reported `char`. +Hence the result of selection would be an error. -Note that the candidate impl is chosen based on the `Self` type, but -confirmation is done based on (in this case) the `Target` type parameter. +Note that the candidate impl is chosen based on the `Self` type, +but confirmation is done based on (in this case) the `Target` type parameter. ### Selection during codegen -As mentioned above, during type checking, we do not store the results of trait -selection. At codegen time, we repeat the trait selection to choose a particular -impl for each method call. This is done using `fn codegen_select_candidate`. +As mentioned above, during type checking, we do not store the results of trait selection. +At codegen time, we repeat the trait selection to choose a particular impl for each method call. +This is done using `fn codegen_select_candidate`. In this second selection, we do not consider any where-clauses to be in scope because we know that each resolution will resolve to a particular impl. -One interesting twist has to do with nested obligations. In general, in codegen, +One interesting twist has to do with nested obligations. +In general, in codegen, we only need to figure out which candidate applies, and we do not care about nested obligations, -as these are already assumed to be true. Nonetheless, we *do* currently fulfill all of them. +as these are already assumed to be true. +Nonetheless, we *do* currently fulfill all of them. That is because it can sometimes inform the results of type inference. That is, we do not have the full substitutions in terms of the type variables -of the impl available to us, so we must run trait selection to figure -everything out. +of the impl available to us, so we must run trait selection to figure everything out. From b4ac0554a756089bdd54c0ac2d9717ffbc4f7381 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:27:59 +0100 Subject: [PATCH 38/44] improve traits/resolution.md --- src/doc/rustc-dev-guide/src/traits/resolution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/resolution.md b/src/doc/rustc-dev-guide/src/traits/resolution.md index 1ae5bde54d53d..77793bdd3e904 100644 --- a/src/doc/rustc-dev-guide/src/traits/resolution.md +++ b/src/doc/rustc-dev-guide/src/traits/resolution.md @@ -67,9 +67,9 @@ Trait resolution consists of three major parts: It may also require evaluating those nested obligations to resolve ambiguities. - **Fulfillment**: The fulfillment code is what tracks that obligations are completely fulfilled. - Basically it is a worklist of obligations to be selected: once selection is successful, + Basically, it is a worklist of obligations to be selected: once selection is successful, the obligation is - removed from the worklist and any nested obligations are enqueued. + removed from the worklist, and any nested obligations are enqueued. Fulfillment constrains inference variables. - **Evaluation**: Checks whether obligations holds without constraining any inference variables. @@ -120,7 +120,7 @@ The subroutines that decide whether a particular impl/where-clause/etc applies to a particular obligation are collectively referred to as the process of _matching_. For `impl` candidates , this amounts to unifying the impl header (the `Self` type and the trait arguments) -while ignoring nested obligations. +while ignoring nested obligations. If matching succeeds then we add it to a set of candidates. There are other rules when assembling candidates for built-in traits such as `Copy`, `Sized`, and `CoerceUnsized`. From 8abb3a52f58104430048891eecb4fe90b4725d3d Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:28:18 +0100 Subject: [PATCH 39/44] reflow src/traits/resolution.md --- .../rustc-dev-guide/src/traits/resolution.md | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/traits/resolution.md b/src/doc/rustc-dev-guide/src/traits/resolution.md index 77793bdd3e904..5d4c8a2b758fe 100644 --- a/src/doc/rustc-dev-guide/src/traits/resolution.md +++ b/src/doc/rustc-dev-guide/src/traits/resolution.md @@ -27,8 +27,7 @@ it is the job of trait resolution to figure out whether there exists an impl of (in this case) `isize : Clone`. Note that in some cases, like generic functions, we may not be able to find a specific impl, -but we can figure out that the caller must -provide an impl. +but we can figure out that the caller must provide an impl. For example, consider the body of `clone_slice`: ```rust,ignore @@ -51,8 +50,8 @@ the trait resolution system resolves an obligation by proving that an appropriat During type checking, we do not store the results of trait selection. We simply wish to verify that trait selection will succeed. -Then later, at codegen time, when we have all concrete types available, we -can repeat the trait selection to choose an actual implementation, +Then later, at codegen time, when we have all concrete types available, +we can repeat the trait selection to choose an actual implementation, which will then be generated in the output binary. ## Overview @@ -68,8 +67,7 @@ Trait resolution consists of three major parts: - **Fulfillment**: The fulfillment code is what tracks that obligations are completely fulfilled. Basically, it is a worklist of obligations to be selected: once selection is successful, - the obligation is - removed from the worklist, and any nested obligations are enqueued. + the obligation is removed from the worklist, and any nested obligations are enqueued. Fulfillment constrains inference variables. - **Evaluation**: Checks whether obligations holds without constraining any inference variables. @@ -101,8 +99,8 @@ it is not possible to give back immediate feedback as to whether a unification o relationship between lifetimes holds or not. Therefore, lifetime matching is *not* considered during selection. This is reflected in the fact that subregion assignment is infallible. -This may yield lifetime constraints that will later be found to be in error (in -contrast, the non-lifetime-constraints have already been checked +This may yield lifetime constraints that will later be found to be in error (in contrast, +the non-lifetime-constraints have already been checked during selection and can never cause an error, though naturally they may lead to other errors downstream). @@ -126,8 +124,8 @@ There are other rules when assembling candidates for built-in traits such as `Co `Sized`, and `CoerceUnsized`. Once this first pass is done, we can examine the set of candidates. -If it is a singleton set, then we are done: this is the only impl in -scope that could possibly apply. +If it is a singleton set, +then we are done: this is the only impl in scope that could possibly apply. Otherwise, we can **winnow** down the set of candidates by using where clauses and other conditions. Winnowing uses `evaluate_candidate` to check whether the nested obligations may apply. If this still leaves more than 1 candidate, @@ -176,8 +174,7 @@ Besides an impl, the other major way to resolve an obligation is via a where cla The selection process is always given a [parameter environment] which contains a list of where clauses, which are basically obligations that we can assume are satisfiable. -We will iterate over that list and check whether our current obligation can be found -in that list. +We will iterate over that list and check whether our current obligation can be found in that list. If so, it is considered satisfied. More precisely, we want to check whether there is a where-clause obligation that is for the same trait (or some subtrait) and which can match against the obligation. From 8dcc6f8d5512654a508d74d8cb734347509c995d Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:39:26 +0100 Subject: [PATCH 40/44] reflow src/tests/ci.md --- src/doc/rustc-dev-guide/src/tests/ci.md | 217 ++++++++++++------------ 1 file changed, 108 insertions(+), 109 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index 7f3c38385ceca..efda4844f4c37 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -3,19 +3,19 @@ The primary goal of our CI system is to ensure that the `main` branch of `rust-lang/rust` is always in a valid state by passing our test suite. -From a high-level point of view, when you open a pull request at -`rust-lang/rust`, the following will happen: +From a high-level point of view, when you open a pull request at `rust-lang/rust`, +the following will happen: - A small [subset](#pull-request-builds) of tests and checks are run after each push to the PR. This should help catch common errors. - When the PR is approved, the [bors] bot enqueues the PR into a [merge queue]. -- Once the PR gets to the front of the queue, bors will create a merge commit - and run the [full test suite](#auto-builds) on it. +- Once the PR gets to the front of the queue, + bors will create a merge commit and run the [full test suite](#auto-builds) on it. The merge commit either contains only one specific PR or it can be a ["rollup"](#rollups) which combines multiple PRs together, to reduce CI costs and merge delays. - Once the whole test suite finishes, two things can happen. - Either CI fails with an error that needs to be addressed by the developer, or CI succeeds and - the merge commit is then pushed to the `main` branch. + Either CI fails with an error that needs to be addressed by the developer, + or CI succeeds and the merge commit is then pushed to the `main` branch. If you want to modify what gets executed on CI, see [Modifying CI jobs](#modifying-ci-jobs). @@ -23,13 +23,13 @@ If you want to modify what gets executed on CI, see [Modifying CI jobs](#modifyi -Our CI is primarily executed on [GitHub Actions], with a single workflow defined -in [`.github/workflows/ci.yml`], which contains a bunch of steps that are -unified for all CI jobs that we execute. +Our CI is primarily executed on [GitHub Actions], +with a single workflow defined in [`.github/workflows/ci.yml`], +which contains a bunch of steps that are unified for all CI jobs that we execute. When a commit is pushed to a corresponding branch or a PR, the workflow executes the [`src/ci/citool`] crate, which dynamically generates the specific CI jobs that should be executed. -This script uses the [`jobs.yml`] file as an -input, which contains a declarative configuration of all our CI jobs. +This script uses the [`jobs.yml`] file as an input, +which contains a declarative configuration of all our CI jobs. > Almost all build steps shell out to separate scripts. This keeps the CI fairly > platform independent (i.e., we are not overly reliant on GitHub Actions). @@ -48,14 +48,14 @@ There are two broad categories of jobs that are executed, `dist` and non-`dist` The same builds are also used for actual releases: our release process basically consists of copying those artifacts from `rust-lang-ci2` to the production endpoint and signing them. -- Non-dist jobs run our full test suite on the platform, and the test suite of - all the tools we ship through rustup; +- Non-dist jobs run our full test suite on the platform, + and the test suite of all the tools we ship through rustup; The amount of stuff we test depends on - the platform (for example some tests are run only on Tier 1 platforms), and - some quicker platforms are grouped together on the same builder to avoid wasting CI resources. + the platform (for example some tests are run only on Tier 1 platforms), + and some quicker platforms are grouped together on the same builder to avoid wasting CI resources. -Based on an input event (usually a push to a branch), we execute one of three -kinds of builds (sets of jobs). +Based on an input event (usually a push to a branch), +we execute one of three kinds of builds (sets of jobs). 1. PR builds 2. Auto builds @@ -66,15 +66,15 @@ kinds of builds (sets of jobs). ### Pull Request builds After each push to a pull request, a set of `pr` jobs are executed. -Currently, these execute the `test-x86_64-gnu-llvm-X`, `test-x86_64-gnu-tools`, `test-pr-check-1`, `test-pr-check-2` -and `test-tidy` jobs, all running on Linux. +Currently, these execute the `test-x86_64-gnu-llvm-X`, `test-x86_64-gnu-tools`, `test-pr-check-1`, +`test-pr-check-2` and `test-tidy` jobs, all running on Linux. These execute a relatively short (~40 minutes) and lightweight test suite that should catch common issues. -More specifically, they run a set of lints, they try to perform a cross-compile check -build to Windows mingw (without producing any artifacts), and they test the -compiler using a *system* version of LLVM. -Unfortunately, it would take too many -resources to run the full test suite for each commit on every PR. +More specifically, they run a set of lints, +they try to perform a cross-compile check build to Windows mingw (without producing any artifacts), +and they test the compiler using a *system* version of LLVM. +Unfortunately, +it would take too many resources to run the full test suite for each commit on every PR. > **Note on doc comments** > @@ -87,8 +87,8 @@ resources to run the full test suite for each commit on every PR. > changes to help catch these early. PR jobs are defined in the `pr` section of [`jobs.yml`]. -Their results can be observed -directly on the PR, in the "CI checks" section at the bottom of the PR page. +Their results can be observed directly on the PR, +in the "CI checks" section at the bottom of the PR page. ### Auto builds @@ -110,12 +110,12 @@ At any given time, at most a single `auto` build is being executed. Find out more in [Merging PRs serially with bors](#merging-prs-serially-with-bors). Normally, when an auto job fails, the whole CI workflow immediately ends. -However, it can be useful to -create auto jobs that are "non-blocking", or optional, to test them on CI for some time before blocking -merges on them. +However, it can be useful to create auto jobs that are "non-blocking", +or optional, to test them on CI for some time before blocking merges on them. This can be useful if those jobs can be flaky. -To do that, prefix such a job with `optional-`, and set `continue_on_error: true` for it in [`jobs.yml`]. +To do that, prefix such a job with `optional-`, +and set `continue_on_error: true` for it in [`jobs.yml`]. [platform tiers]: https://forge.rust-lang.org/release/platform-support.html#rust-platform-support [auto]: https://github.com/rust-lang/rust/tree/automation/bors/auto @@ -135,20 +135,20 @@ There are several use-cases for try builds: try build that runs the [dist-x86_64-linux] CI job, which builds an optimized version of the compiler on Linux (this job is currently executed by default when you start a try build). - To create a try build and schedule it for a - performance benchmark, you can use the `@bors try @rust-timer queue` command combination. + To create a try build and schedule it for a performance benchmark, + you can use the `@bors try @rust-timer queue` command combination. - Check the impact of the PR across the Rust ecosystem, using a [Crater](crater.md) run. - Again, a working compiler build is needed for this, which can be produced by - the [dist-x86_64-linux] CI job. + Again, a working compiler build is needed for this, + which can be produced by the [dist-x86_64-linux] CI job. - Run a specific CI job (e.g. Windows tests) on a PR, to quickly test if it passes the test suite executed by that job. -By default, if you send a comment with `@bors try`, the jobs defined in the `try` section of -[`jobs.yml`] will be executed. +By default, if you send a comment with `@bors try`, +the jobs defined in the `try` section of [`jobs.yml`] will be executed. We call this mode a "fast try build". Such a try build will not execute any tests, and it will allow compilation warnings. -It is useful when you want to -get an optimized toolchain as fast as possible, for a Crater run or performance benchmarks, +It is useful when you want to get an optimized toolchain as fast as possible, +for a Crater run or performance benchmarks, even if it might not be working fully correctly. The CI job executed in fast try builds has a special suffix (`-quick`), @@ -157,13 +157,14 @@ If you want to do the full build instead, specify its job name in a job pattern (explained below). If you want to run custom CI jobs in a try build and make sure that they pass all tests and do -not produce any compilation warnings, you can select CI jobs to be executed by specifying a *job pattern*, +not produce any compilation warnings, +you can select CI jobs to be executed by specifying a *job pattern*, which can be used in one of two ways: - You can add a set of `try-job: ` directives to the PR description (described below) and then simply run `@bors try`. CI will read these directives and run the jobs that you have specified. - This is - useful if you want to rerun the same set of try jobs multiple times, after incrementally modifying a PR. + This is useful if you want to rerun the same set of try jobs multiple times, + after incrementally modifying a PR. - You can specify the job pattern using the `jobs` parameter of the try command: `@bors try jobs=`. This is useful for one-off try builds with specific jobs. Note that the `jobs` parameter has a higher priority than the PR description directives. @@ -172,8 +173,8 @@ which can be used in one of two ways: Each job pattern can either be an exact name of a job or a glob pattern that matches multiple jobs, for example `*msvc*` or `*-alt`. You can start at most 20 jobs in a single try build. -When using -glob patterns in the PR description, you can optionally wrap them in backticks (`` ` ``) to avoid GitHub rendering +When using glob patterns in the PR description, +you can optionally wrap them in backticks (`` ` ``) to avoid GitHub rendering the pattern as Markdown if it contains e.g. an asterisk. Note that this escaping will not work when using the `@bors jobs=` parameter. @@ -221,34 +222,34 @@ their results can be seen on [the GitHub Actions workflows page], although usually you will be notified of the result by a comment made by bors on the corresponding PR. -Multiple try builds can execute concurrently across different PRs, but there can be at most -a single try build running on a single PR at any given time. +Multiple try builds can execute concurrently across different PRs, +but there can be at most a single try build running on a single PR at any given time. [rustc-perf]: https://github.com/rust-lang/rustc-perf [try]: https://github.com/rust-lang/rust/tree/automation/bors/try ### Modifying CI jobs -If you want to modify what gets executed on our CI, you can simply modify the -`pr`, `auto` or `try` sections of the [`jobs.yml`] file. +If you want to modify what gets executed on our CI, you can simply modify the `pr`, +`auto` or `try` sections of the [`jobs.yml`] file. -You can also modify what gets executed temporarily, for example to test a -particular platform or configuration that is challenging to test locally (for +You can also modify what gets executed temporarily, +for example to test a particular platform or configuration that is challenging to test locally (for example, if a Windows build fails, but you don't have access to a Windows machine). Don't hesitate to use CI resources in such situations. You can perform an arbitrary CI job in two ways: -- Use the [try build](#try-builds) functionality, and specify the CI jobs that - you want to be executed in try builds in your PR description. +- Use the [try build](#try-builds) functionality, + and specify the CI jobs that you want to be executed in try builds in your PR description. - Modify the [`pr`](#pull-request-builds) section of `jobs.yml` to specify which CI jobs should be executed after each push to your PR. This might be faster than repeatedly starting try builds. -To modify the jobs executed after each push to a PR, you can simply copy one of -the job definitions from the `auto` section to the `pr` section. +To modify the jobs executed after each push to a PR, +you can simply copy one of the job definitions from the `auto` section to the `pr` section. For example, the `x86_64-msvc` job is responsible for running the 64-bit MSVC tests. -You can copy it to the `pr` section to cause it to be executed after a commit is pushed -to your PR, like this: +You can copy it to the `pr` section to cause it to be executed after a commit is pushed to your PR, +like this: ```yaml pr: @@ -276,41 +277,40 @@ A good practice is to prefix `[WIP]` in PR title while still running try jobs and `[DO NOT MERGE]` in the commit that modifies the CI jobs for testing purposes. -Although you are welcome to use CI, just be conscious that this is a shared -resource with limited concurrency. +Although you are welcome to use CI, +just be conscious that this is a shared resource with limited concurrency. Try not to enable too many jobs at once; one or two should be sufficient in most cases. ## Merging PRs serially with bors -CI services usually test the last commit of a branch merged with the last commit -in `main`, and while that’s great to check if the feature works in isolation, +CI services usually test the last commit of a branch merged with the last commit in `main`, +and while that’s great to check if the feature works in isolation, it doesn’t provide any guarantee the code is going to work once it’s merged. -Breakages like these usually happen when another, incompatible PR is merged -after the build happened. +Breakages like these usually happen when another, +incompatible PR is merged after the build happened. To ensure a `main` branch that works all the time, we forbid manual merges. Instead, all PRs have to be approved through our bot, [bors]. All the approved PRs are put in a [merge queue] (sorted by priority and creation date) and are automatically tested one at the time. -If all the builders are green, the PR is merged, otherwise the failure is -recorded and the PR will have to be re-approved again. +If all the builders are green, the PR is merged, +otherwise the failure is recorded and the PR will have to be re-approved again. Bors doesn’t interact with CI services directly, but it works by pushing the merge commit it wants to test to specific branches (like `automation/bors/auto` or `automation/bors/try`), which are configured to execute CI checks. Bors then detects the outcome of the build by listening for either Commit Statuses or Check Runs. -Since the merge commit is -based on the latest `main` and only one can be tested at the same time, when -the results are green, `main` is fast-forwarded to that merge commit. +Since the merge commit is based on the latest `main` and only one can be tested at the same time, +when the results are green, `main` is fast-forwarded to that merge commit. -Unfortunately, testing a single PR at a time, combined with our long CI (~2 -hours for a full run), means we can’t merge a lot of PRs in a single day, and a -single failure greatly impacts our throughput. +Unfortunately, testing a single PR at a time, combined with our long CI (~2 hours for a full run), +means we can’t merge a lot of PRs in a single day, +and a single failure greatly impacts our throughput. The maximum number of PRs we can merge in a day is around ~10. -The long CI run times, and requirement for a large builder pool, is largely due -to the fact that full release artifacts are built in the `dist-` builders. +The long CI run times, and requirement for a large builder pool, +is largely due to the fact that full release artifacts are built in the `dist-` builders. This is worth it because these release artifacts: - Allow perf testing even at a later date. @@ -320,18 +320,18 @@ This is worth it because these release artifacts: ### Rollups Some PRs don’t need the full test suite to be executed: trivial changes like -typo fixes or README improvements *shouldn’t* break the build, and testing every -single one of them for 2+ hours would be wasteful. -To solve this, we regularly create a "rollup", a PR where we merge several pending trivial PRs so -they can be tested together. +typo fixes or README improvements *shouldn’t* break the build, +and testing every single one of them for 2+ hours would be wasteful. +To solve this, we regularly create a "rollup", +a PR where we merge several pending trivial PRs so they can be tested together. Rollups are created manually by a team member using the "create a rollup" button on the [merge queue]. The team member uses their judgment to decide if a PR is risky or not. ## Docker -All CI jobs, except those on macOS and Windows, are executed inside that -platform’s custom [Docker container]. +All CI jobs, except those on macOS and Windows, +are executed inside that platform’s custom [Docker container]. This has a lot of advantages for us: - The build environment is consistent regardless of the changes of the @@ -352,8 +352,8 @@ The Docker images prefixed with `dist-` are used for building artifacts while those without that prefix run tests and checks. We also run tests for less common architectures (mainly Tier 2 and Tier 3 platforms) in CI. -Since those platforms are not x86, we either run everything -inside QEMU, or we just cross-compile if we don’t want to run the tests for that platform. +Since those platforms are not x86, we either run everything inside QEMU, +or we just cross-compile if we don’t want to run the tests for that platform. These builders are running on a special pool of builders set up and maintained for us by GitHub. @@ -366,20 +366,20 @@ Our CI workflow uses various caching mechanisms, mainly for two things: ### Docker images caching The Docker images we use to run most of the Linux-based builders take a *long* time to fully build. -To speed up the build, we cache them using [Docker registry -caching], with the intermediate artifacts being stored on [ghcr.io]. +To speed up the build, we cache them using [Docker registry caching], +with the intermediate artifacts being stored on [ghcr.io]. We also push the built Docker images to ghcr, so that they can be reused by other tools (rustup) or by developers running the Docker build locally (to speed up their build). -Since we test multiple, diverged branches (`main`, `beta` and `stable`), we -can’t rely on a single cache for the images, otherwise builds on a branch would -override the cache for the others. -Instead, we store the images under different -tags, identifying them with a custom hash made from the contents of all the +Since we test multiple, diverged branches (`main`, `beta` and `stable`), +we can’t rely on a single cache for the images, +otherwise builds on a branch would override the cache for the others. +Instead, we store the images under different tags, +identifying them with a custom hash made from the contents of all the Dockerfiles and related scripts. -The CI calculates a hash key, so that the cache of a Docker image is -invalidated if one of the following changes: +The CI calculates a hash key, +so that the cache of a Docker image is invalidated if one of the following changes: - Dockerfile - Files copied into the Docker image in the Dockerfile @@ -390,10 +390,10 @@ invalidated if one of the following changes: ### LLVM caching with Sccache -We build some C/C++ stuff in various CI jobs, and we rely on [Sccache] to cache -the intermediate LLVM artifacts. -Sccache is a distributed ccache developed by -Mozilla, which can use an object storage bucket as the storage backend. +We build some C/C++ stuff in various CI jobs, +and we rely on [Sccache] to cache the intermediate LLVM artifacts. +Sccache is a distributed ccache developed by Mozilla, +which can use an object storage bucket as the storage backend. With Sccache there's no need to calculate the hash key ourselves. Sccache invalidates the cache automatically when it detects changes to relevant inputs, @@ -402,8 +402,8 @@ So we just pass the Sccache wrapper on top of Cargo and Sccache does the rest. We store the persistent artifacts on the S3 bucket, `rust-lang-ci-sccache2`. So when the CI runs, if Sccache sees that LLVM is being compiled with the same C/C++ -compiler and the LLVM source code is the same, Sccache retrieves the individual -compiled translation units from S3. +compiler and the LLVM source code is the same, +Sccache retrieves the individual compiled translation units from S3. [sccache]: https://github.com/mozilla/sccache @@ -413,8 +413,8 @@ During the years, we developed some custom tooling to improve our CI experience. ### Rust Log Analyzer to show the error message in PRs -The build logs for `rust-lang/rust` are huge, and it’s not practical to find -what caused the build to fail by looking at the logs. +The build logs for `rust-lang/rust` are huge, +and it’s not practical to find what caused the build to fail by looking at the logs. We therefore developed a bot called [Rust Log Analyzer][rla] (RLA) that receives the build logs on failure, and extracts the error message automatically, posting it on the PR thread. @@ -428,26 +428,25 @@ good at identifying the relevant lines, even if it’s an error we've never seen ### Toolstate to support allowed failures -The `rust-lang/rust` repo doesn’t only test the compiler on its CI, but also a -variety of tools and documentation. +The `rust-lang/rust` repo doesn’t only test the compiler on its CI, +but also a variety of tools and documentation. Some documentation is pulled in via git submodules. If we blocked merging rustc PRs on the documentation being fixed, we would be stuck in a chicken-and-egg problem, because the documentation's CI would not pass since updating it would need the not-yet-merged version of rustc to test against (and we usually require CI to be passing). -To avoid the problem, submodules are allowed to fail, and their status is -recorded in [rust-toolstate]. -When a submodule breaks, a bot automatically pings -the maintainers so they know about the breakage, and it records the failure on -the toolstate repository. -The release process will then ignore broken tools on -nightly, removing them from the shipped nightlies. +To avoid the problem, submodules are allowed to fail, +and their status is recorded in [rust-toolstate]. +When a submodule breaks, a bot automatically pings the maintainers so they know about the breakage, +and it records the failure on the toolstate repository. +The release process will then ignore broken tools on nightly, +removing them from the shipped nightlies. While tool failures are allowed most of the time, they’re automatically forbidden a week before a release: we don’t care if tools are broken on nightly -but they must work on beta and stable, so they also need to work on nightly a -few days before we promote nightly to beta. +but they must work on beta and stable, +so they also need to work on nightly a few days before we promote nightly to beta. More information is available in the [toolstate documentation]. @@ -473,8 +472,8 @@ To learn more about the dashboard, see the [Datadog CI docs]. ## Determining the CI configuration -If you want to determine which `bootstrap.toml` settings are used in CI for a -particular job, it is probably easiest to just look at the build log. +If you want to determine which `bootstrap.toml` settings are used in CI for a particular job, +it is probably easiest to just look at the build log. To do this: 1. Go to [the Rust CI successful workflow runs page][workflow runs] From ed64975edf18fc9409ae77e7692a81855eedccfc Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Sat, 31 Oct 2026 23:50:16 +0100 Subject: [PATCH 41/44] improve tests/ci.md --- src/doc/rustc-dev-guide/src/tests/ci.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/ci.md b/src/doc/rustc-dev-guide/src/tests/ci.md index efda4844f4c37..68e35287dac4a 100644 --- a/src/doc/rustc-dev-guide/src/tests/ci.md +++ b/src/doc/rustc-dev-guide/src/tests/ci.md @@ -78,10 +78,10 @@ it would take too many resources to run the full test suite for each commit on e > **Note on doc comments** > -> Note that PR CI as of Oct 2024 by default does not try to -> run `./x doc xxx`. This means that if you have any broken intradoc links that -> would lead to `./x doc xxx` failing, it will happen very late into the full -> merge queue CI pipeline. +> Note that PR CI as of Oct 2024 by default does not try to run `./x doc xxx`. +> This means that if you have any broken intradoc links that +> would lead to `./x doc xxx` failing, +> it will happen very late into the full merge queue CI pipeline. > > Thus, it is a good idea to run `./x doc xxx` locally for any doc comment > changes to help catch these early. @@ -175,8 +175,8 @@ for example `*msvc*` or `*-alt`. You can start at most 20 jobs in a single try build. When using glob patterns in the PR description, you can optionally wrap them in backticks (`` ` ``) to avoid GitHub rendering -the pattern as Markdown if it contains e.g. an asterisk. Note that this escaping will not work when using -the `@bors jobs=` parameter. +the pattern as Markdown if it contains e.g. an asterisk. +Note that this escaping will not work when using the `@bors jobs=` parameter. The job pattern needs to match one or more jobs defined in the `auto` or `optional` sections of [`jobs.yml`]: @@ -371,7 +371,7 @@ with the intermediate artifacts being stored on [ghcr.io]. We also push the built Docker images to ghcr, so that they can be reused by other tools (rustup) or by developers running the Docker build locally (to speed up their build). -Since we test multiple, diverged branches (`main`, `beta` and `stable`), +Since we test multiple, diverged branches (`main`, `beta`, and `stable`), we can’t rely on a single cache for the images, otherwise builds on a branch would override the cache for the others. Instead, we store the images under different tags, From c0a9eade97e90ef26c40df2a132df3f341db736e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 31 Oct 2026 23:56:28 +0100 Subject: [PATCH 42/44] impl Eq for AllocRange --- compiler/rustc_middle/src/mir/interpret/allocation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs index 87dddb7b74c35..d0e35ad80bfb0 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs @@ -330,7 +330,7 @@ impl AllocError { } /// The information that makes up a memory access: offset and size. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct AllocRange { pub start: Size, pub size: Size, From ff0d7147b8c07513bd033a261bd4f1db06cfc05a Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 1 Aug 2026 23:30:06 -0700 Subject: [PATCH 43/44] rustdoc: notable trait badge color attribute --- compiler/rustc_attr_ir/src/data_structures.rs | 37 ++++- .../rustc_attr_parsing/src/attributes/doc.rs | 109 +++++++++++-- .../rustc_attr_parsing/src/diagnostics.rs | 18 +++ compiler/rustc_middle/src/queries.rs | 8 +- compiler/rustc_middle/src/ty/util.rs | 15 +- compiler/rustc_span/src/symbol.rs | 1 + library/alloc/src/io/read.rs | 2 +- library/core/src/future/future.rs | 2 +- library/core/src/io/write.rs | 2 +- library/core/src/iter/traits/iterator.rs | 2 +- src/doc/rustdoc/src/unstable-features.md | 15 +- src/librustdoc/clean/types.rs | 2 +- src/librustdoc/html/render/mod.rs | 152 ++++++++++++------ src/librustdoc/html/render/print_item.rs | 31 ++-- src/librustdoc/html/static/css/noscript.css | 20 ++- src/librustdoc/html/static/css/rustdoc.css | 98 ++++++++--- src/librustdoc/html/templates/print_item.html | 19 +-- src/librustdoc/json/conversions.rs | 8 +- src/librustdoc/passes/collect_trait_impls.rs | 2 +- tests/rustdoc-gui/notable-trait-badges.goml | 97 +++++++++++ tests/rustdoc-gui/notable-traits.goml | 7 +- tests/rustdoc-gui/src/lib2/lib.rs | 32 +++- .../doc-notable_trait-negative.positive.html | 2 +- ...c-notable_trait-slice.bare_fn_matches.html | 2 +- .../doc-notable_trait.bare-fn.html | 2 +- .../doc-notable_trait.some-struct-new.html | 2 +- .../doc-notable_trait.wrap-me.html | 2 +- .../notable-trait-badge-negative.rs | 3 +- .../notable-trait-badge-supertrait.rs | 3 +- ...able-trait-badge-unlinkable-cross-crate.rs | 3 +- .../notable-trait/notable-trait-badge.rs | 22 ++- .../spotlight-from-dependency.odd.html | 2 +- .../lints/doc-notable_trait-check.rs | 44 +++++ .../lints/doc-notable_trait-check.stderr | 55 +++++++ .../feature-gate-doc_notable_trait.rs | 2 + .../feature-gate-doc_notable_trait.stderr | 11 +- 36 files changed, 675 insertions(+), 159 deletions(-) create mode 100644 tests/rustdoc-gui/notable-trait-badges.goml create mode 100644 tests/rustdoc-ui/lints/doc-notable_trait-check.rs create mode 100644 tests/rustdoc-ui/lints/doc-notable_trait-check.stderr diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index ebc5c20da7f04..ce33e5cba69cc 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -503,7 +503,7 @@ pub struct DocAttribute { pub keyword: Option<(Symbol, Span)>, pub attribute: Option<(Symbol, Span)>, pub masked: Option, - pub notable_trait: Option, + pub notable_trait: Option<(Option<(NotableTraitColor, Span)>, Span)>, pub search_unbox: Option, // valid on crate @@ -520,6 +520,41 @@ pub struct DocAttribute { pub no_crate_inject: Option, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(StableHash, Encodable, Decodable, PrintAttribute)] +pub enum NotableTraitColor { + Grey, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + Transparent, +} + +impl From for &'static str { + fn from(color: NotableTraitColor) -> &'static str { + use NotableTraitColor::*; + match color { + Grey => "grey", + Red => "red", + Green => "green", + Yellow => "yellow", + Blue => "blue", + Magenta => "magenta", + Cyan => "cyan", + Transparent => "transparent", + } + } +} + +impl std::fmt::Display for NotableTraitColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str((*self).into()) + } +} + impl rustc_serialize::Encodable for DocAttribute { fn encode(&self, encoder: &mut E) { let DocAttribute { diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index e1181e5eb056d..bcc4b71b0ca23 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -2,7 +2,7 @@ use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; use rustc_attr_ir::target::{AstTarget, ConstTypeAstTarget, Target}; use rustc_attr_ir::{ AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue, - DocInline, HideOrShow, + DocInline, HideOrShow, NotableTraitColor, }; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; use rustc_errors::{Applicability, MultiSpan, msg}; @@ -15,13 +15,14 @@ use super::{AcceptMapping, AttributeParser, template}; use crate::context::{AcceptContext, FinalizeContext}; use crate::diagnostics::{ DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, - DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow, - DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues, - DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues, - DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocInlineConflict, DocInlineOnlyUse, - DocKeywordAttributeNotAnonConst, DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, - DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, - DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc, + DocAttrNotCrateLevel, DocAttrNotTraitLevel, DocAttributeNotAttribute, + DocAutoCfgExpectsHideOrShow, DocAutoCfgHideShowExpectsList, + DocAutoCfgHideShowNoIdentBeforeValues, DocAutoCfgHideShowUnexpectedItem, + DocAutoCfgHideShowUnexpectedItemAfterValues, DocAutoCfgHideShowValuesMix, + DocAutoCfgWrongLiteral, DocInlineConflict, DocInlineOnlyUse, DocKeywordAttributeNotAnonConst, + DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny, + DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, + ExpectedNoArgs, IllFormedAttributeInput, InvalidNotableTraitAttr, MalformedDoc, UnusedDuplicate, }; use crate::parser::{ @@ -85,6 +86,82 @@ fn expected_string_literal( cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span); } +fn parse_notable_trait( + cx: &mut AcceptContext<'_, '_>, + path: &OwnedPathParser, + args: &ArgParser, + attr_value: &mut Option<(Option<(NotableTraitColor, Span)>, Span)>, + attr_name: Symbol, +) { + let span = path.span(); + + let notable_trait_color_attr = match args { + ArgParser::NoArgs => None, + ArgParser::List(meta_item_list_parser) => { + if meta_item_list_parser.is_empty() { + None + } else if let Some(meta_item) = meta_item_list_parser.as_single() { + Some(meta_item) + } else { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + } + } + ArgParser::NameValue(_) => { + // FIXME(164728): every invalid notable trait attribute lint produces exactly + // the same warning and we should switch to more specific diagnostics + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + } + }; + + let notable_trait_color_and_span = + if let Some(notable_trait_color_attr) = notable_trait_color_attr { + let Some(notable_trait_color_attr) = notable_trait_color_attr.meta_item() else { + // FIXME(164728): every invalid notable trait attribute lint produces exactly + // the same warning and we should switch to more specific diagnostics + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + }; + if !notable_trait_color_attr.path().word_is(sym::color) { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + } + let Some(notable_trait_color) = notable_trait_color_attr.args().as_name_value() else { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + }; + let Some(notable_trait_color) = notable_trait_color.value_as_str() else { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + }; + let notable_trait_color = match notable_trait_color.as_str() { + "grey" | "gray" => NotableTraitColor::Grey, + "red" => NotableTraitColor::Red, + "green" => NotableTraitColor::Green, + "yellow" => NotableTraitColor::Yellow, + "blue" => NotableTraitColor::Blue, + "magenta" => NotableTraitColor::Magenta, + "cyan" => NotableTraitColor::Cyan, + "transparent" => NotableTraitColor::Transparent, + _ => { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); + return; + } + }; + Some((notable_trait_color, notable_trait_color_attr.span())) + } else { + None + }; + + if cx.shared.target != Target::Trait { + cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAttrNotTraitLevel { span, attr_name }, span); + return; + } + + *attr_value = Some((notable_trait_color_and_span, span)); +} + fn parse_keyword_and_attribute( cx: &mut AcceptContext<'_, '_>, path: &OwnedPathParser, @@ -174,14 +251,14 @@ impl DocParser { } Some(sym::attr) => { let Some(list) = args.as_list() else { - // FIXME: remove this method once merged and uncomment the line below instead. - // cx.expected_list(cx.attr_span, args); + // FIXME(164728): remove this method once merged and uncomment the line + // below instead. cx.expected_list(cx.attr_span, args); let span = cx.attr_span; cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span); return; }; - - // FIXME: convert list into a Vec of `AttributeKind` because current code is awful. + // FIXME(164728): remove this method once merged and uncomment the line + // below instead. cx.expected_list(cx.attr_span, args); for attr in list.mixed() { // Arguments of `attr` are checked via the span, so can be safely ignored attr.ignore_args(); @@ -621,7 +698,13 @@ impl DocParser { } Some(sym::notable_trait) => { gated!(doc_notable_trait); - no_args!(notable_trait) + parse_notable_trait( + cx, + path, + args, + &mut self.attribute.notable_trait, + sym::notable_trait, + ) } Some(sym::keyword) => { gated!(rustdoc_internals); diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index fb7b04a6437b6..4059114456431 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -400,6 +400,13 @@ pub(crate) struct ExpectedNoArgs; )] pub(crate) struct ExpectedNameValue; +#[derive(Diagnostic)] +#[diag("expected either `doc(notable_trait)` or `doc(notable_trait(color=\"...\"))`")] +#[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" +)] +pub(crate) struct InvalidNotableTraitAttr; + #[derive(Diagnostic)] #[diag("malformed `{$attribute}` attribute")] #[help("{$options}")] @@ -1052,6 +1059,17 @@ pub(crate) struct DocAttributeNotAttribute { pub attribute: Symbol, } +#[derive(Diagnostic)] +#[diag("`#![doc({$attr_name})]` must be a trait attribute")] +#[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" +)] +pub(crate) struct DocAttrNotTraitLevel { + #[primary_span] + pub span: Span, + pub attr_name: Symbol, +} + #[derive(Diagnostic)] #[diag( "`#[target_feature]` cannot be applied to a {$kind -> diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 0326e118ce832..aadb863e753ec 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -55,7 +55,7 @@ use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::tokenstream::TokenStream; use rustc_attr_ir::diagnostic_items::DiagnosticItems; use rustc_attr_ir::lang_items::{LangItem, LanguageItems}; -use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem}; +use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, NotableTraitColor, StrippedCfgItem}; use rustc_crate_store::{ CrateDepKind, CrateSource, ExternCrate, ForeignModule, LinkagePreference, NativeLib, }; @@ -1529,8 +1529,10 @@ rustc_queries! { separate_provide_extern } - /// Determines whether an item is annotated with `#[doc(notable_trait)]`. - query is_doc_notable_trait(def_id: DefId) -> bool { + /// If an item is annotated with `#[doc(notable_trait)]`, + /// returns the color used to render its badge. If the crate specifies + /// no color, `Transparent` is used. + query doc_notable_trait(def_id: DefId) -> Option<&'tcx NotableTraitColor> { desc { "checking whether `{}` is `doc(notable_trait)`", tcx.def_path_str(def_id) } } diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 12228ae721734..8e6850ca0f07a 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -4,7 +4,7 @@ use std::{fmt, iter}; use rustc_abi::{Float, Integer, IntegerType, Size}; use rustc_apfloat::Float as _; -use rustc_attr_ir::find_attr; +use rustc_attr_ir::{NotableTraitColor, find_attr}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_errors::ErrorGuaranteed; @@ -1701,8 +1701,15 @@ fn is_doc_hidden(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { } /// Determines whether an item is annotated with `doc(notable_trait)`. -pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { - find_attr!(tcx, def_id, Doc(doc) if doc.notable_trait.is_some()) +pub fn doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&'_ NotableTraitColor> { + find_attr!(tcx, def_id, Doc(doc) if doc.notable_trait.is_some() => { + let (color, _span) = doc.notable_trait.as_ref()?; + if let Some((color, _span)) = color { + color + } else { + &NotableTraitColor::Transparent + } + }) } /// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute). @@ -1732,7 +1739,7 @@ pub fn provide(providers: &mut Providers) { *providers = Providers { reveal_opaque_types_in_bounds, is_doc_hidden, - is_doc_notable_trait, + doc_notable_trait, intrinsic_raw, ..*providers } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index eec3dad3802af..3bfe79ee63477 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -669,6 +669,7 @@ symbols! { cold, cold_path, collapse_debuginfo, + color, column, compare_bytes, compare_exchange, diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index a05dade2bdcf6..145cee958aeb3 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -81,7 +81,7 @@ use crate::vec::Vec; /// [`&str`]: prim@str /// [`std::io`]: crate::io #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] +#[doc(notable_trait(color = "transparent"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoRead")] #[rustc_must_implement_one_of(read_buf, read)] // Keep this order, it's important for rust-analyzer (the preferred-to-implement method should come first). pub trait Read { diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs index fab13bb7c8535..b84b00510c41e 100644 --- a/library/core/src/future/future.rs +++ b/library/core/src/future/future.rs @@ -25,7 +25,7 @@ use crate::task::{Context, Poll}; /// /// [`async`]: ../../std/keyword.async.html /// [`Waker`]: crate::task::Waker -#[doc(notable_trait)] +#[doc(notable_trait(color = "transparent"))] #[doc(search_unbox)] #[must_use = "futures do nothing unless you `.await` or poll them"] #[stable(feature = "futures_api", since = "1.36.0")] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index cdddd380885f4..1318d4bc0ee8a 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -47,7 +47,7 @@ use crate::io::{Error, IoSlice, Result}; /// /// [`write_all`]: Write::write_all #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] +#[doc(notable_trait(color = "transparent"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] pub trait Write { /// Writes a buffer into this writer, returning how many bytes were written. diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index cc4077d0ee26f..a5165217aca43 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -34,7 +34,7 @@ fn _assert_is_dyn_compatible(_: &dyn Iterator) {} label = "`{Self}` is not an iterator", message = "`{Self}` is not an iterator" )] -#[doc(notable_trait)] +#[doc(notable_trait(color = "transparent"))] #[lang = "iterator"] #[rustc_diagnostic_item = "Iterator"] #[must_use = "iterators are lazy and do nothing unless consumed"] diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index ab317ba1048a4..9bd795b759ec4 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -75,8 +75,21 @@ on them: `#[doc(notable_trait)]`. This means that you can apply this attribute to your own trait to include it in the "Notable traits" dialog in documentation. In addition to the "Notable traits" dialog, every type that implements a -`#[doc(notable_trait)]` trait renders a colored badge for that trait at the top +`#[doc(notable_trait)]` trait renders a badge for that trait at the top of its page, making the relationship easy to spot when browsing the type. +To set a color for the badge, write `#[doc(notable_trait(color="red"))]` or +one of the other colors in the list (from the [ANSI 3 bit terminal palette][]): + +[ANSI 3 bit terminal palette]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit + +- grey +- red +- green +- yellow +- blue +- magenta +- cyan +- transparent The `#[doc(notable_trait)]` attribute currently requires the `#![feature(doc_notable_trait)]` feature gate. For more information, see [its chapter in the Unstable Book][unstable-notable_trait] diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c34b33d9c92f5..56d651aff2731 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1354,7 +1354,7 @@ impl Trait { tcx.trait_is_auto(self.def_id) } pub(crate) fn is_notable_trait(&self, tcx: TyCtxt<'_>) -> bool { - tcx.is_doc_notable_trait(self.def_id) + tcx.doc_notable_trait(self.def_id).is_some() } pub(crate) fn safety(&self, tcx: TyCtxt<'_>) -> hir::Safety { tcx.trait_def(self.def_id).safety diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index c2d8cc9ebae3e..bee0f61c3a69e 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -52,8 +52,8 @@ use indexmap::IndexMap; use itertools::Either; use rustc_ast::join_path_syms; use rustc_attr_ir::{ - AttributeKind, ConstStability, DeprecatedSince, Deprecation, RustcVersion, StabilityLevel, - StableSince, find_attr, + AttributeKind, ConstStability, DeprecatedSince, Deprecation, NotableTraitColor, RustcVersion, + StabilityLevel, StableSince, find_attr, }; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::Mutability; @@ -1729,13 +1729,60 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option) -> (String, String) { +fn notable_traits_decl_write_html<'cx>( + notable_impls: impl Iterator, + f: &mut fmt::Formatter<'_>, + cx: &Context<'_>, +) -> Result<(), fmt::Error> { + let mut notable_impls = notable_impls.peekable(); + let code_tags_closing = if let Some((impl_, _)) = notable_impls.peek() { + write!( + f, + "

Notable traits for {}

\ +
",
+            print_type(&impl_.for_, cx),
+        )?;
+        "
" + } else { + "" + }; + + for (impl_, trait_did) in notable_impls { + write!(f, "
{}
", print_impl(impl_, false, cx))?; + for it in &impl_.items { + let clean::AssocTypeItem(tydef, ..) = &it.kind else { + continue; + }; + + let empty_set = FxIndexSet::default(); + let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + + write!( + f, + "
{};
", + assoc_type( + it, + &tydef.generics, + &[], // intentionally leaving out bounds + Some(&tydef.type_), + src_link, + 0, + cx, + ) + )?; + } + } + + f.write_str(code_tags_closing) +} + +fn notable_traits_decl_for_type(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this"); let out = fmt::from_fn(|f| { - let mut notable_impls = impls + let notable_impls = impls .iter() .map(|impl_| impl_.inner_impl()) .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) @@ -1753,60 +1800,57 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { } else { None } - }) - .peekable(); - - let has_notable_impl = if let Some((impl_, _)) = notable_impls.peek() { - write!( - f, - "

Notable traits for {}

\ -
",
-                print_type(&impl_.for_, cx),
-            )?;
-            true
-        } else {
-            false
-        };
-
-        for (impl_, trait_did) in notable_impls {
-            write!(f, "
{}
", print_impl(impl_, false, cx))?; - for it in &impl_.items { - let clean::AssocTypeItem(tydef, ..) = &it.kind else { - continue; - }; - - let empty_set = FxIndexSet::default(); - let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + }); - write!( - f, - "
{};
", - assoc_type( - it, - &tydef.generics, - &[], // intentionally leaving out bounds - Some(&tydef.type_), - src_link, - 0, - cx, - ) - )?; - } - } + notable_traits_decl_write_html(notable_impls, f, cx) + }) + .to_string(); - if !has_notable_impl { - f.write_str("
")?; - } + (format!("{:#}", print_type(ty, cx)), out) +} - Ok(()) +fn notable_traits_decl_for_item(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { + let out = fmt::from_fn(|f| { + let notable_impls = if let Some(def_id) = item.def_id() + && !is_notable_trait_passthrough(def_id, cx) + && let Some(impls) = cx.cache().impls.get(&def_id) + { + impls + .iter() + .map(Impl::inner_impl) + .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) + .filter_map(|impl_| { + if let Some(trait_) = &impl_.trait_ + && let trait_did = trait_.def_id() + && let Some(trait_) = cx.cache().traits.get(&trait_did) + && trait_.is_notable_trait(cx.tcx()) + { + Some((impl_, trait_did)) + } else { + None + } + }) + } else { + return Ok(()); + }; + notable_traits_decl_write_html(notable_impls, f, cx) }) .to_string(); - (format!("{:#}", print_type(ty, cx)), out) + Some((item.name?.to_string(), out)) } -fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { - let mut mp = tys.map(|ty| notable_traits_decl(ty, cx)).collect::>(); +fn notable_traits_json<'a>( + tys: impl Iterator, + item: &clean::Item, + cx: &Context<'_>, +) -> String { + let mut mp = tys.map(|ty| notable_traits_decl_for_type(ty, cx)).collect::>(); + if let Some((item_name, item_decl)) = notable_traits_decl_for_item(item, cx) + && !item_decl.is_empty() + { + mp.insert(item_name, item_decl); + } mp.sort_unstable_keys(); serde_json::to_string(&mp).expect("serialize (string, string) -> json object cannot fail") } @@ -1816,6 +1860,8 @@ pub(crate) struct NotableTraitBadge { pub full_path: String, /// Relative URL to the trait page, or `None` if it cannot be linked. pub href: Option, + /// One of the color names. + pub color: NotableTraitColor, } /// Returns all `#[doc(notable_trait)]` traits that `item` implements, to be @@ -1833,15 +1879,15 @@ pub(crate) fn notable_trait_badges(item: &clean::Item, cx: &Context<'_>) -> Vec< .filter_map(|impl_| { if let Some(trait_) = &impl_.trait_ && let trait_did = trait_.def_id() - && let Some(trait_) = cx.cache().traits.get(&trait_did) - && trait_.is_notable_trait(tcx) + && cx.cache().traits.contains_key(&trait_did) + && let Some(&color) = tcx.doc_notable_trait(trait_did) { let name = tcx.item_name(trait_did).to_string(); let (full_path, href) = match href(trait_did, cx) { Ok(info) => (join_path_syms(&info.rust_path), Some(info.url)), Err(_) => (tcx.def_path_str(trait_did), None), }; - Some((name.clone(), NotableTraitBadge { name, full_path, href })) + Some((name.clone(), NotableTraitBadge { name, full_path, href, color })) } else { None } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 544d57d92613c..586e7e26ecef3 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,13 +1,12 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::hash_map::DefaultHasher; use std::fmt::{self, Display, Write as _}; -use std::hash::{Hash, Hasher}; use std::iter; use askama::Template; use rustc_abi::VariantIdx; use rustc_ast::join_path_syms; +use rustc_attr_ir::NotableTraitColor; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, MacroKinds}; @@ -58,8 +57,8 @@ struct NotableTraitBadgeVars { full_path: String, /// Relative URL to the trait page, or `None` when not linkable. href: Option, - /// Index of the `.notable-trait-badge-{n}` color class. - color_index: u8, + /// Name of the `.notable-trait-badge-{color}` color class. + color: NotableTraitColor, } #[derive(Template)] @@ -126,22 +125,14 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let notable_trait_badges: Vec = notable_trait_badges(item, cx) .into_iter() - .map(|info| { - // Stable per-trait color from a hash of the trait path so the - // same trait gets the same badge color across pages. - // This won't be stable between releases though. - let mut h = DefaultHasher::new(); - info.full_path.hash(&mut h); - const BADGE_COLORS: u8 = 6; - let color_index = (h.finish() as u8) % BADGE_COLORS; - NotableTraitBadgeVars { - name: info.name, - full_path: info.full_path, - href: info.href, - color_index, - } + .map(|info| NotableTraitBadgeVars { + name: info.name, + full_path: info.full_path, + href: info.href, + color: info.color, }) .collect(); + let has_notable_trait_badges = !notable_trait_badges.is_empty(); let path_components = if item.is_fake_item() { vec![] @@ -219,11 +210,11 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp // Render notable-traits.js used for all methods in this module. let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); - if !types_with_notable_traits.is_empty() { + if !types_with_notable_traits.is_empty() || has_notable_trait_badges { write!( buf, r#""#, - notable_traits_json(types_with_notable_traits.iter(), cx), + notable_traits_json(types_with_notable_traits.iter(), item, cx), )?; types_with_notable_traits.clear(); } diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index 085880b215d14..564e785a19f93 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -141,12 +141,14 @@ nav.sub { --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); --sidebar-resizer-hover: hsl(207, 90%, 66%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.88 0.21 0); - --notable-badge-red: oklch(0.88 0.21 40); - --notable-badge-orange: oklch(0.88 0.21 70); + --notable-badge-grey: oklch(0.88 0 0); + --notable-badge-red: oklch(0.88 0.21 20); --notable-badge-green: oklch(0.88 0.21 150); + --notable-badge-yellow: oklch(0.88 0.21 70); --notable-badge-blue: oklch(0.88 0.21 240); - --notable-badge-violet: oklch(0.88 0.21 300); + --notable-badge-magenta: oklch(0.88 0.21 320); + --notable-badge-cyan: oklch(0.88 0.21 180); + --notable-badge-transparent: transparent; } /* End theme: light */ @@ -258,12 +260,14 @@ nav.sub { --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); --sidebar-resizer-hover: hsl(207, 30%, 54%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.55 0.21 0); - --notable-badge-red: oklch(0.55 0.21 40); - --notable-badge-orange: oklch(0.55 0.21 70); + --notable-badge-grey: oklch(0.55 0 0); + --notable-badge-red: oklch(0.55 0.21 20); --notable-badge-green: oklch(0.55 0.21 150); + --notable-badge-yellow: oklch(0.55 0.21 100); --notable-badge-blue: oklch(0.55 0.21 240); - --notable-badge-violet: oklch(0.55 0.21 300); + --notable-badge-magenta: oklch(0.55 0.21 320); + --notable-badge-cyan: oklch(0.55 0.21 180); + --notable-badge-transparent: transparent; } /* End theme: dark */ } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index acf60114bd47a..b851943b8ee0f 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1668,13 +1668,24 @@ so that we can apply CSS-filters to change the arrow color in themes */ } .notable-trait-badge-container { - padding: 0.5rem 0; + padding-top: 8px; display: flex; flex-wrap: wrap; - gap: 0.5rem; + gap: 8px; + margin-left: var(--docblock-indent); + position: relative; } -.notable-trait-badge-container > a { +.notable-trait-badge-container > a.tooltip { + position: absolute; + width: 16px; + line-height: 1.5rem; + vertical-align: center; + left: calc(-1 * var(--docblock-indent)); + text-decoration: none; +} + +.notable-trait-badge-container > a:not(.tooltip) { display: flex; align-items: center; width: fit-content; @@ -1686,16 +1697,47 @@ so that we can apply CSS-filters to change the arrow color in themes */ color: var(--main-color); } -.notable-trait-badge-container > a:hover { - text-decoration: none; +/* this list of colors is lifted from the ANSI escape codes list: + https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit + This way, CLI-based tooling can adapt them without having to do + complex platform support dances. */ +.notable-trait-badge-container > .notable-trait-badge-grey { + background: var(--notable-badge-grey); + border: solid 1px var(--notable-badge-grey); +} +.notable-trait-badge-container > .notable-trait-badge-red { + background: var(--notable-badge-red); + border: solid 1px var(--notable-badge-red); +} +.notable-trait-badge-container > .notable-trait-badge-green { + background: var(--notable-badge-green); + border: solid 1px var(--notable-badge-green); +} +.notable-trait-badge-container > .notable-trait-badge-yellow { + background: var(--notable-badge-yellow); + border: solid 1px var(--notable-badge-yellow); +} +.notable-trait-badge-container > .notable-trait-badge-blue { + background: var(--notable-badge-blue); + border: solid 1px var(--notable-badge-blue); +} +.notable-trait-badge-container > .notable-trait-badge-magenta { + background: var(--notable-badge-magenta); + border: solid 1px var(--notable-badge-magenta); +} +.notable-trait-badge-container > .notable-trait-badge-cyan { + background: var(--notable-badge-cyan); + border: solid 1px var(--notable-badge-cyan); +} +.notable-trait-badge-container > .notable-trait-badge-transparent { + background: var(--notable-badge-transparent); + border: solid 1px var(--border-color); } -.notable-trait-badge-container > .badge-0 { background: var(--notable-badge-pink); } -.notable-trait-badge-container > .badge-1 { background: var(--notable-badge-red); } -.notable-trait-badge-container > .badge-2 { background: var(--notable-badge-orange); } -.notable-trait-badge-container > .badge-3 { background: var(--notable-badge-green); } -.notable-trait-badge-container > .badge-4 { background: var(--notable-badge-blue); } -.notable-trait-badge-container > .badge-5 { background: var(--notable-badge-violet); } +.notable-trait-badge-container > a:hover:not(.tooltip) { + text-decoration: none; + border: solid 1px var(--settings-button-border-focus); +} .rightside { padding-left: 12px; @@ -2941,6 +2983,10 @@ in src-script.js and main.js left: -11px; } + .notable-trait-badge-container > a.tooltip { + left: calc(-1 * var(--docblock-indent) - 11px); + } + /* sidebar button becomes topbar button */ #sidebar-button > a::before { content: url('data:image/svg+xml, {# #} {# #} - {% if !notable_trait_badges.is_empty() %} -
- {% endif %} {% if !stability_since_raw.is_empty() %} {{ stability_since_raw|safe +}} {% endif %} @@ -38,5 +29,15 @@

Source {#+ #} {% else %} {% endmatch %} + {% if !notable_trait_badges.is_empty() %} +
{# #} + ⓘ + {% for badge in notable_trait_badges.iter() %} + {{badge.name}} + {% endfor %} +
+ {% endif %} {# #} {# #} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index f4cecb08bb982..85af793308935 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1225,7 +1225,13 @@ fn maybe_from_hir_attr( name_value_attr(&mut ret, "keyword", keyword); name_value_attr(&mut ret, "attribute", attribute); toggle_attr(&mut ret, "masked", masked); - toggle_attr(&mut ret, "notable_trait", notable_trait); + if let Some((notable_trait, _span)) = notable_trait { + if let Some((color, _span)) = notable_trait { + ret.push(Attribute::Other(format!("#[doc(notable_trait = \"{color}\")]"))); + } else { + ret.push(Attribute::Other(format!("#[doc(notable_trait)]"))); + } + } toggle_attr(&mut ret, "search_unbox", search_unbox); name_value_attr(&mut ret, "html_favicon_url", html_favicon_url); name_value_attr(&mut ret, "html_logo_url", html_logo_url); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 8720e09bd85cd..3262255a3fcf9 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -48,7 +48,7 @@ pub(super) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> debug!("considering extern trait impl {trait_ref:?}"); if crate_items.contains(&ItemId::DefId(trait_ref.def_id())) || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait() - || tcx.is_doc_notable_trait(trait_ref.def_id()) + || tcx.doc_notable_trait(trait_ref.def_id()).is_some() { debug!("-> inlining due to trait"); cx.with_param_env(impl_def_id, |cx| { diff --git a/tests/rustdoc-gui/notable-trait-badges.goml b/tests/rustdoc-gui/notable-trait-badges.goml new file mode 100644 index 0000000000000..927c4e34cccd4 --- /dev/null +++ b/tests/rustdoc-gui/notable-trait-badges.goml @@ -0,0 +1,97 @@ +// Ensures that the theme change is working as expected. +include: "utils.goml" +go-to: "file://" + |DOC_PATH| + "/lib2/notable_trait_colors/struct.NotableTraitColors.html" + +define-function: ( + "test-badges", + [ + grey, red, green, yellow, blue, magenta, cyan, transparent, transparent_border, + ], + block { + assert-css: (".notable-trait-badge-grey", {"background-color": |grey|, "border-color": |grey|}) + assert-css: (".notable-trait-badge-red", {"background-color": |red|, "border-color": |red|}) + assert-css: (".notable-trait-badge-green", {"background-color": |green|, "border-color": |green|}) + assert-css: (".notable-trait-badge-yellow", {"background-color": |yellow|, "border-color": |yellow|}) + assert-css: (".notable-trait-badge-blue", {"background-color": |blue|, "border-color": |blue|}) + assert-css: (".notable-trait-badge-magenta", {"background-color": |magenta|, "border-color": |magenta|}) + assert-css: (".notable-trait-badge-cyan", {"background-color": |cyan|, "border-color": |cyan|}) + assert-css: (".notable-trait-badge-transparent", { + "background-color": |transparent|, + "border-color": |transparent_border|, + }) + store-css: (".top-doc > .docblock", {"margin-left": top_docblock_margin_left}) + assert-css: (".notable-trait-badge-container", {"margin-left": |top_docblock_margin_left|}) + + set-window-size: (1280, 1024) + store-position: (".top-doc > .docblock", {"x": top_docblock_x}) + assert-position: (".notable-trait-badge-container", {"x": |top_docblock_x|}) + store-position: (".top-doc > summary::before", {"x": top_docblock_toggle_x}) + assert-position: (".notable-trait-badge-container > a.tooltip", {"x": |top_docblock_toggle_x|}) + store-position: (".notable-trait-badge-container > a.tooltip + a", {"x": notable_trait_badge_first_x}) + store-position: (".notable-trait-badge-container > a.tooltip", {"x": notable_trait_tooltip_x}) + store-size: (".notable-trait-badge-container > a.tooltip", {"width": notable_trait_tooltip_width}) + assert: |notable_trait_tooltip_x| + |notable_trait_tooltip_width| < |notable_trait_badge_first_x| + + set-window-size: (320, 480) + store-position: (".top-doc > .docblock", {"x": top_docblock_x}) + assert-position: (".notable-trait-badge-container", {"x": |top_docblock_x|}) + store-position: (".top-doc > summary::before", {"x": top_docblock_toggle_x}) + assert-position: (".notable-trait-badge-container > a.tooltip", {"x": |top_docblock_toggle_x|}) + store-position: (".notable-trait-badge-container > a.tooltip + a", {"x": notable_trait_badge_first_x}) + store-position: (".notable-trait-badge-container > a.tooltip", {"x": notable_trait_tooltip_x}) + store-size: (".notable-trait-badge-container > a.tooltip", {"width": notable_trait_tooltip_width}) + assert: |notable_trait_tooltip_x| + |notable_trait_tooltip_width| < |notable_trait_badge_first_x| + } +) +call-function: ("switch-theme", {"theme": "dark"}) +call-function: ("test-badges", { + "grey": "oklch(0.55 0 0)", + "red": "oklch(0.55 0.21 20)", + "green": "oklch(0.55 0.21 150)", + "yellow": "oklch(0.55 0.21 100)", + "blue": "oklch(0.55 0.21 240)", + "magenta": "oklch(0.55 0.21 320)", + "cyan": "oklch(0.55 0.21 180)", + "transparent": "transparent", + "transparent_border": "#e0e0e0", +}) + +call-function: ("switch-theme", {"theme": "light"}) +call-function: ("test-badges", { + "grey": "oklch(0.88 0 0)", + "red": "oklch(0.88 0.21 20)", + "green": "oklch(0.88 0.21 150)", + "yellow": "oklch(0.88 0.21 70)", + "blue": "oklch(0.88 0.21 240)", + "magenta": "oklch(0.88 0.21 320)", + "cyan": "oklch(0.88 0.21 180)", + "transparent": "transparent", + "transparent_border": "#e0e0e0", +}) + +call-function: ("switch-theme", {"theme": "ayu"}) +call-function: ("test-badges", { + "grey": "oklch(0.49 0 0)", + "red": "oklch(0.49 0.21 20)", + "green": "oklch(0.49 0.21 150)", + "yellow": "oklch(0.49 0.21 100)", + "blue": "oklch(0.49 0.21 240)", + "magenta": "oklch(0.49 0.21 320)", + "cyan": "oklch(0.49 0.21 180)", + "transparent": "transparent", + "transparent_border": "#5c6773", +}) + +assert-false: ".tooltip.popover" +click: ".notable-trait-badge-container > a.tooltip" +wait-for: ".tooltip.popover" +assert-text: (".tooltip.popover h3", "Notable traits for NotableTraitColors") +assert-count: (".tooltip.popover .where", 8) +assert-text: (".tooltip.popover .where:nth-child(1)", "impl Grey for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(2)", "impl Red for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(3)", "impl Green for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(4)", "impl Yellow for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(5)", "impl Blue for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(6)", "impl Magenta for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(7)", "impl Cyan for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(8)", "impl Transparent for NotableTraitColors") diff --git a/tests/rustdoc-gui/notable-traits.goml b/tests/rustdoc-gui/notable-traits.goml index f31b83e12edf0..60a66b219b2f8 100644 --- a/tests/rustdoc-gui/notable-traits.goml +++ b/tests/rustdoc-gui/notable-traits.goml @@ -2,11 +2,12 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/notable/struct.Wrapper.html" show-text: true store-css: (".notable-trait-badge-container", {"background-color": background_color}) -// The background color should be different. -assert-css-false: (".notable-trait-badge-container > a", {"background-color": |background_color|}) +// The border color should be different. +assert-css-false: (".notable-trait-badge-container > a:not(.tooltip)", {"border-color": |background_color|}) +assert-css-false: (".notable-trait-badge-container > a:not(.tooltip)", {"border-color": "transparent"}) store-css: ("#trait-implementations", {"color": text_color}) -assert-css: (".notable-trait-badge-container > a", { +assert-css: (".notable-trait-badge-container > a:not(.tooltip)", { // The color should be the same as the "main color". "color": |text_color|, // Checking some additional CSS values. diff --git a/tests/rustdoc-gui/src/lib2/lib.rs b/tests/rustdoc-gui/src/lib2/lib.rs index 28dfa56ea1ad4..3104b88b183e3 100644 --- a/tests/rustdoc-gui/src/lib2/lib.rs +++ b/tests/rustdoc-gui/src/lib2/lib.rs @@ -1,7 +1,6 @@ // ignore-tidy-file-linelength -#![feature(doc_cfg)] -#![feature(negative_impls)] +#![feature(doc_cfg, negative_impls, doc_notable_trait)] pub mod another_folder; pub mod another_mod; @@ -411,3 +410,32 @@ pub mod deprecated { fn depr_deprecated_fn() {} } } + +pub mod notable_trait_colors { + /// Top doc + pub struct NotableTraitColors; + #[doc(notable_trait(color="grey"))] + pub trait Grey {} + impl Grey for NotableTraitColors {} + #[doc(notable_trait(color="red"))] + pub trait Red {} + impl Red for NotableTraitColors {} + #[doc(notable_trait(color="green"))] + pub trait Green {} + impl Green for NotableTraitColors {} + #[doc(notable_trait(color="yellow"))] + pub trait Yellow {} + impl Yellow for NotableTraitColors {} + #[doc(notable_trait(color="blue"))] + pub trait Blue {} + impl Blue for NotableTraitColors {} + #[doc(notable_trait(color="magenta"))] + pub trait Magenta {} + impl Magenta for NotableTraitColors {} + #[doc(notable_trait(color="cyan"))] + pub trait Cyan {} + impl Cyan for NotableTraitColors {} + #[doc(notable_trait(color="transparent"))] + pub trait Transparent {} + impl Transparent for NotableTraitColors {} +} diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait-negative.positive.html b/tests/rustdoc-html/notable-trait/doc-notable_trait-negative.positive.html index a3d0fedaaf47e..250d90567aa08 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait-negative.positive.html +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait-negative.positive.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait-slice.bare_fn_matches.html b/tests/rustdoc-html/notable-trait/doc-notable_trait-slice.bare_fn_matches.html index 7d2a2589133be..8b5a27551ed0b 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait-slice.bare_fn_matches.html +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait-slice.bare_fn_matches.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait.bare-fn.html b/tests/rustdoc-html/notable-trait/doc-notable_trait.bare-fn.html index 9f71677cf4ffc..cfc3573c6d8f7 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait.bare-fn.html +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait.bare-fn.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait.some-struct-new.html b/tests/rustdoc-html/notable-trait/doc-notable_trait.some-struct-new.html index 38689c79f1ace..7577ffa4c7091 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait.some-struct-new.html +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait.some-struct-new.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/notable-trait/doc-notable_trait.wrap-me.html b/tests/rustdoc-html/notable-trait/doc-notable_trait.wrap-me.html index c137be9e3a5d8..761a2c220935b 100644 --- a/tests/rustdoc-html/notable-trait/doc-notable_trait.wrap-me.html +++ b/tests/rustdoc-html/notable-trait/doc-notable_trait.wrap-me.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs index 225572513c40f..dc7b349ede2f3 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs @@ -8,7 +8,8 @@ pub trait Pos {} // A negative impl must not produce a badge. //@ has 'foo/struct.T.html' -//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Pos.html"]' 'Pos' pub struct T; impl !Neg for T {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs index 9bb3226099a51..2d29fbeedf80e 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs @@ -9,7 +9,8 @@ pub trait Derived: Base {} //@ has 'foo/struct.S.html' // Implementing `Derived` requires implementing the notable supertrait `Base`, // so its badge shows up. -//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Base.html"]' 'Base' pub struct S; impl Base for S {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs index de7008cf5ab0c..a249a100e6541 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs @@ -12,6 +12,7 @@ use notable_dep::Spaceship; // The badge is present... //@ has - '//div[@class="notable-trait-badge-container"]/a' 'Spaceship' // ...but unlinked: no badge carries an `href`. -//@ count - '//div[@class="notable-trait-badge-container"]/a[@href]' 0 +//@ count - '//div[@class="notable-trait-badge-container"]/a[@href]' 1 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' pub struct Rocket; impl Spaceship for Rocket {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs index b53d989d0f819..7692201bc6140 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs @@ -12,8 +12,9 @@ pub trait Plain {} //@ has 'foo/struct.Tagged.html' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Labeled.html"][@title="foo::Labeled"]' 'Labeled' // Badges are sorted by trait name, so `AlsoLabeled` precedes `Labeled`. -//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'AlsoLabeled' -//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'Labeled' +//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'ⓘ' +//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'AlsoLabeled' +//@ has - '//div[@class="notable-trait-badge-container"]/a[3]' 'Labeled' pub struct Tagged; impl Labeled for Tagged {} impl AlsoLabeled for Tagged {} @@ -23,3 +24,20 @@ impl Plain for Tagged {} //@ count - '//div[@class="notable-trait-badge-container"]' 0 pub struct Untagged; impl Plain for Untagged {} + +#[doc(notable_trait(color="grey"))] +pub trait Grey {} + +#[doc(notable_trait(color="gray"))] +pub trait Gray {} + +#[doc(notable_trait(color="blue"))] +pub trait Blue {} + +//@ has 'foo/struct.Colors.html' +//@ count - '//a[@class="notable-trait-badge-grey"]' 2 +//@ count - '//a[@class="notable-trait-badge-blue"]' 1 +pub struct Colors; +impl Grey for Colors {} +impl Gray for Colors {} +impl Blue for Colors {} diff --git a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html index 910698a5854e9..d7b6686e9d139 100644 --- a/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html +++ b/tests/rustdoc-html/notable-trait/spotlight-from-dependency.odd.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/rustdoc-ui/lints/doc-notable_trait-check.rs b/tests/rustdoc-ui/lints/doc-notable_trait-check.rs new file mode 100644 index 0000000000000..e56ecd573ede1 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-notable_trait-check.rs @@ -0,0 +1,44 @@ +#![feature(doc_notable_trait)] +#![deny(invalid_doc_attributes)] + +#[doc(notable_trait)] +trait NoColor {} + +#[doc(notable_trait())] +trait NoColor2 {} + +#[doc(notable_trait(color="transparent"))] +trait Transparent {} + +#[doc(notable_trait(color="red"))] +trait Red {} + +#[doc(notable_trait="red")] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidKV {} + +#[doc(notable_trait)] +//~^ ERROR +//~| WARN previously accepted by the compiler +struct InvalidNotTrait; + +#[doc(notable_trait(check="red"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidKVArg {} + +#[doc(notable_trait(color))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidAtomArg {} + +#[doc(notable_trait(color="invalid_color"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidColor {} + +#[doc(notable_trait(color="transparent", color="red"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidMultiple {} diff --git a/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr b/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr new file mode 100644 index 0000000000000..4d0f99d34b4d3 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr @@ -0,0 +1,55 @@ +error: expected either `doc(notable_trait)` or `doc(notable_trait(color="..."))` + --> $DIR/doc-notable_trait-check.rs:16:7 + | +LL | #[doc(notable_trait="red")] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +note: the lint level is defined here + --> $DIR/doc-notable_trait-check.rs:2:9 + | +LL | #![deny(invalid_doc_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: `#![doc(notable_trait)]` must be a trait attribute + --> $DIR/doc-notable_trait-check.rs:21:7 + | +LL | #[doc(notable_trait)] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait(color="..."))` + --> $DIR/doc-notable_trait-check.rs:26:7 + | +LL | #[doc(notable_trait(check="red"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait(color="..."))` + --> $DIR/doc-notable_trait-check.rs:31:7 + | +LL | #[doc(notable_trait(color))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait(color="..."))` + --> $DIR/doc-notable_trait-check.rs:36:7 + | +LL | #[doc(notable_trait(color="invalid_color"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait(color="..."))` + --> $DIR/doc-notable_trait-check.rs:41:7 + | +LL | #[doc(notable_trait(color="transparent", color="red"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: aborting due to 6 previous errors + diff --git a/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs b/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs index 1bc1028b9e0ef..b06b17650da30 100644 --- a/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs +++ b/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs @@ -4,5 +4,7 @@ trait SomeTrait {} fn main() { #[doc(notable_trait)] //~^ ERROR the `doc(notable_trait)` attribute is experimental [E0658] + //~| WARN `#![doc(notable_trait)]` must be a trait attribute + //~| WARN this was previously accepted println!(); } diff --git a/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr b/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr index 632c91b929185..a7be03385fc3f 100644 --- a/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr +++ b/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr @@ -8,6 +8,15 @@ LL | #[doc(notable_trait)] = help: add `#![feature(doc_notable_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +warning: `#![doc(notable_trait)]` must be a trait attribute + --> $DIR/feature-gate-doc_notable_trait.rs:5:11 + | +LL | #[doc(notable_trait)] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: `#[warn(invalid_doc_attributes)]` on by default + error[E0658]: the `doc(notable_trait)` attribute is experimental --> $DIR/feature-gate-doc_notable_trait.rs:1:7 | @@ -18,6 +27,6 @@ LL | #[doc(notable_trait)] = help: add `#![feature(doc_notable_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 2 previous errors +error: aborting due to 2 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0658`. From 930372b3c457f154f430c4cd98586d2cae585710 Mon Sep 17 00:00:00 2001 From: Bee Date: Mon, 5 Oct 2026 18:38:31 +0100 Subject: [PATCH 44/44] Fix RISC-V inline ASM `clobber_abi` when extra target features are enabled --- compiler/rustc_target/src/asm/mod.rs | 51 ++++++- compiler/rustc_target/src/asm/riscv.rs | 6 +- tests/codegen-llvm/asm/riscv-clobbers.rs | 26 ++-- tests/ui/asm/riscv/bad-reg.riscv32e.stderr | 130 +++++++++--------- tests/ui/asm/riscv/bad-reg.riscv32gc.stderr | 26 ++-- tests/ui/asm/riscv/bad-reg.riscv32i.stderr | 34 ++--- .../ui/asm/riscv/bad-reg.riscv32imafc.stderr | 30 ++-- tests/ui/asm/riscv/bad-reg.riscv64gc.stderr | 26 ++-- tests/ui/asm/riscv/bad-reg.riscv64imac.stderr | 34 ++--- tests/ui/asm/riscv/bad-reg.rs | 40 +++--- 10 files changed, 221 insertions(+), 182 deletions(-) diff --git a/compiler/rustc_target/src/asm/mod.rs b/compiler/rustc_target/src/asm/mod.rs index 992ef8da29b55..0d1ca5f85416a 100644 --- a/compiler/rustc_target/src/asm/mod.rs +++ b/compiler/rustc_target/src/asm/mod.rs @@ -5,7 +5,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_macros::{Decodable, Encodable, StableHash}; use rustc_span::Symbol; -use crate::spec::{Arch, RelocModel, Target}; +use crate::spec::{Arch, LlvmAbi, RelocModel, Target}; pub struct ModifierInfo { pub modifier: char, @@ -1024,6 +1024,7 @@ pub enum InlineAsmClobberAbi { Arm64EC, Avr, RiscV, + RiscVD, RiscVE, LoongArch, PowerPC, @@ -1077,11 +1078,15 @@ impl InlineAsmClobberAbi { _ => Err(&["C", "system"]), }, InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => match name { - "C" | "system" | "efiapi" => Ok(if riscv::is_e(target_features) { - InlineAsmClobberAbi::RiscVE - } else { - InlineAsmClobberAbi::RiscV - }), + "C" | "system" | "efiapi" => { + Ok(match target.llvm_abiname { + LlvmAbi::Ilp32e | LlvmAbi::Lp64e => InlineAsmClobberAbi::RiscVE, + // FIXME: Once LLVM adds support for the `q` extension, `d` will no longer + // be the largest supported FLEN, so change this to Lp64q => RiscVQ. + LlvmAbi::Ilp32d | LlvmAbi::Lp64d => InlineAsmClobberAbi::RiscVD, + _ => InlineAsmClobberAbi::RiscV, + }) + } _ => Err(&["C", "system", "efiapi"]), }, InlineAsmArch::Avr => match name { @@ -1298,6 +1303,36 @@ impl InlineAsmClobberAbi { v24, v25, v26, v27, v28, v29, v30, v31, } }, + InlineAsmClobberAbi::RiscVD => clobbered_regs! { + RiscV RiscVInlineAsmReg { + // ra + x1, + // t0-t2 + x5, x6, x7, + // a0-a7 + x10, x11, x12, x13, x14, x15, x16, x17, + // t3-t6 + x28, x29, x30, x31, + + // Only ABI_FLEN bits of the callee-saved floating point registers are + // preserved. As `d` (FLEN == 64) is the largest floating point register LLVM + // currently supports (`q` is currently assembly only), and ABI_FLEN == 64, + // ABI_FLEN must equal FLEN and therefore there is no need to clobber the + // callee-saved registers. + // ft0-ft7 + f0, f1, f2, f3, f4, f5, f6, f7, + // fa0-fa7 + f10, f11, f12, f13, f14, f15, f16, f17, + // ft8-ft11 + f28, f29, f30, f31, + + // v0-v31 + v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, + } + }, InlineAsmClobberAbi::RiscVE => clobbered_regs! { RiscV RiscVInlineAsmReg { // Refs: @@ -1310,6 +1345,9 @@ impl InlineAsmClobberAbi { x5, x6, x7, // a0-a5 x10, x11, x12, x13, x14, x15, + // x16-x31 + x16, x17, x18, x19, x20, x21, x22, x23, + x24, x25, x26, x27, x28, x29, x30, x31, // f0-f31 f0, f1, f2, f3, f4, f5, f6, f7, @@ -1318,7 +1356,6 @@ impl InlineAsmClobberAbi { f24, f25, f26, f27, f28, f29, f30, f31, // v0-v31 - v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, diff --git a/compiler/rustc_target/src/asm/riscv.rs b/compiler/rustc_target/src/asm/riscv.rs index 3976d9890256f..d67d1373a1048 100644 --- a/compiler/rustc_target/src/asm/riscv.rs +++ b/compiler/rustc_target/src/asm/riscv.rs @@ -54,7 +54,7 @@ impl RiscVInlineAsmRegClass { } } -pub(crate) fn is_e(target_features: &FxIndexSet) -> bool { +fn is_e(target_features: &FxIndexSet) -> bool { target_features.contains(&sym::character('e')) } @@ -63,9 +63,9 @@ fn not_e( _reloc_model: RelocModel, target_features: &FxIndexSet, _target: &Target, - _is_clobber: bool, + is_clobber: bool, ) -> Result<(), &'static str> { - if is_e(target_features) { + if is_e(target_features) && !is_clobber { Err("register can't be used with the `e` target feature") } else { Ok(()) diff --git a/tests/codegen-llvm/asm/riscv-clobbers.rs b/tests/codegen-llvm/asm/riscv-clobbers.rs index fb2bbebd8883a..bebf313d37068 100644 --- a/tests/codegen-llvm/asm/riscv-clobbers.rs +++ b/tests/codegen-llvm/asm/riscv-clobbers.rs @@ -1,12 +1,14 @@ //@ add-minicore //@ assembly-output: emit-asm -//@ revisions: rv32i rv64i rv32e -//@[rv32i] compile-flags: --target riscv32i-unknown-none-elf -//@[rv32i] needs-llvm-components: riscv -//@[rv64i] compile-flags: --target riscv64imac-unknown-none-elf -//@[rv64i] needs-llvm-components: riscv -//@[rv32e] compile-flags: --target riscv32e-unknown-none-elf -//@[rv32e] needs-llvm-components: riscv +//@ revisions: ilp32 ilp32f ilp32d ilp32e ilp32e_extra lp64 lp64d +//@[ilp32] compile-flags: --target riscv32i-unknown-none-elf +//@[ilp32f] compile-flags: --target riscv32imfc-unknown-none-elf +//@[ilp32d] compile-flags: --target riscv32gc-unknown-linux-gnu +//@[ilp32e] compile-flags: --target riscv32e-unknown-none-elf +//@[ilp32e_extra] compile-flags:--target riscv32e-unknown-none-elf -Ctarget-feature=-e,+f +//@[lp64] compile-flags: --target riscv64im-unknown-none-elf +//@[lp64d] compile-flags: --target riscv64gc-unknown-none-elf +//@ needs-llvm-components: riscv // ignore-tidy-linelength #![crate_type = "rlib"] @@ -31,9 +33,13 @@ pub unsafe fn no_clobber() { } // CHECK-LABEL: @clobber_abi -// rv32i: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() -// rv64i: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() -// rv32e: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// ilp32: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// ilp32f: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},={f0},={f1},={f2},={f3},={f4},={f5},={f6},={f7},={f8},={f9},={f10},={f11},={f12},={f13},={f14},={f15},={f16},={f17},={f18},={f19},={f20},={f21},={f22},={f23},={f24},={f25},={f26},={f27},={f28},={f29},={f30},={f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// ilp32d: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},={f0},={f1},={f2},={f3},={f4},={f5},={f6},={f7},={f10},={f11},={f12},={f13},={f14},={f15},={f16},={f17},={f28},={f29},={f30},={f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// ilp32e: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x18},={x19},={x20},={x21},={x22},={x23},={x24},={x25},={x26},={x27},={x28},={x29},={x30},={x31},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// ilp32e_extra: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x18},={x19},={x20},={x21},={x22},={x23},={x24},={x25},={x26},={x27},={x28},={x29},={x30},={x31},={f0},={f1},={f2},={f3},={f4},={f5},={f6},={f7},={f8},={f9},={f10},={f11},={f12},={f13},={f14},={f15},={f16},={f17},={f18},={f19},={f20},={f21},={f22},={f23},={f24},={f25},={f26},={f27},={f28},={f29},={f30},={f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// lp64: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() +// lp64d: asm sideeffect "", "={x1},={x5},={x6},={x7},={x10},={x11},={x12},={x13},={x14},={x15},={x16},={x17},={x28},={x29},={x30},={x31},={f0},={f1},={f2},={f3},={f4},={f5},={f6},={f7},={f10},={f11},={f12},={f13},={f14},={f15},={f16},={f17},={f28},={f29},={f30},={f31},~{v0},~{v1},~{v2},~{v3},~{v4},~{v5},~{v6},~{v7},~{v8},~{v9},~{v10},~{v11},~{v12},~{v13},~{v14},~{v15},~{v16},~{v17},~{v18},~{v19},~{v20},~{v21},~{v22},~{v23},~{v24},~{v25},~{v26},~{v27},~{v28},~{v29},~{v30},~{v31}"() #[no_mangle] pub unsafe fn clobber_abi() { asm!("", clobber_abi("C"), options(nostack, nomem, preserves_flags)); diff --git a/tests/ui/asm/riscv/bad-reg.riscv32e.stderr b/tests/ui/asm/riscv/bad-reg.riscv32e.stderr index 27c8e958e536a..ceffe0fbd6304 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv32e.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv32e.stderr @@ -1,185 +1,185 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: cannot use register `x16`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:46:18 + --> $DIR/bad-reg.rs:41:18 | -LL | asm!("", out("x16") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x16") x); + | ^^^^^^^^^^^ error: cannot use register `x17`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:48:18 + --> $DIR/bad-reg.rs:43:18 | -LL | asm!("", out("x17") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x17") x); + | ^^^^^^^^^^^ error: cannot use register `x18`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:50:18 + --> $DIR/bad-reg.rs:45:18 | -LL | asm!("", out("x18") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x18") x); + | ^^^^^^^^^^^ error: cannot use register `x19`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:52:18 + --> $DIR/bad-reg.rs:47:18 | -LL | asm!("", out("x19") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x19") x); + | ^^^^^^^^^^^ error: cannot use register `x20`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:54:18 + --> $DIR/bad-reg.rs:49:18 | -LL | asm!("", out("x20") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x20") x); + | ^^^^^^^^^^^ error: cannot use register `x21`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:56:18 + --> $DIR/bad-reg.rs:51:18 | -LL | asm!("", out("x21") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x21") x); + | ^^^^^^^^^^^ error: cannot use register `x22`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:58:18 + --> $DIR/bad-reg.rs:53:18 | -LL | asm!("", out("x22") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x22") x); + | ^^^^^^^^^^^ error: cannot use register `x23`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:60:18 + --> $DIR/bad-reg.rs:55:18 | -LL | asm!("", out("x23") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x23") x); + | ^^^^^^^^^^^ error: cannot use register `x24`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:62:18 + --> $DIR/bad-reg.rs:57:18 | -LL | asm!("", out("x24") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x24") x); + | ^^^^^^^^^^^ error: cannot use register `x25`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:64:18 + --> $DIR/bad-reg.rs:59:18 | -LL | asm!("", out("x25") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x25") x); + | ^^^^^^^^^^^ error: cannot use register `x26`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:66:18 + --> $DIR/bad-reg.rs:61:18 | -LL | asm!("", out("x26") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x26") x); + | ^^^^^^^^^^^ error: cannot use register `x27`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:68:18 + --> $DIR/bad-reg.rs:63:18 | -LL | asm!("", out("x27") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x27") x); + | ^^^^^^^^^^^ error: cannot use register `x28`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:70:18 + --> $DIR/bad-reg.rs:65:18 | -LL | asm!("", out("x28") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x28") x); + | ^^^^^^^^^^^ error: cannot use register `x29`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:72:18 + --> $DIR/bad-reg.rs:67:18 | -LL | asm!("", out("x29") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x29") x); + | ^^^^^^^^^^^ error: cannot use register `x30`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:74:18 + --> $DIR/bad-reg.rs:69:18 | -LL | asm!("", out("x30") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x30") x); + | ^^^^^^^^^^^ error: cannot use register `x31`: register can't be used with the `e` target feature - --> $DIR/bad-reg.rs:76:18 + --> $DIR/bad-reg.rs:71:18 | -LL | asm!("", out("x31") _); - | ^^^^^^^^^^^^ +LL | asm!("", in("x31") x); + | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:80:26 + --> $DIR/bad-reg.rs:75:26 | LL | asm!("/* {} */", in(freg) f); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:82:26 + --> $DIR/bad-reg.rs:77:26 | LL | asm!("/* {} */", out(freg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:84:26 + --> $DIR/bad-reg.rs:79:26 | LL | asm!("/* {} */", in(freg) d); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:87:26 + --> $DIR/bad-reg.rs:82:26 | LL | asm!("/* {} */", out(freg) d); | ^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -187,7 +187,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -195,7 +195,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.riscv32gc.stderr b/tests/ui/asm/riscv/bad-reg.riscv32gc.stderr index 4ff03d819e60f..5acc779b2308c 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv32gc.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv32gc.stderr @@ -1,65 +1,65 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -67,7 +67,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -75,7 +75,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.riscv32i.stderr b/tests/ui/asm/riscv/bad-reg.riscv32i.stderr index fbe63eb0563c3..324a8ba50df8e 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv32i.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv32i.stderr @@ -1,89 +1,89 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:80:26 + --> $DIR/bad-reg.rs:75:26 | LL | asm!("/* {} */", in(freg) f); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:82:26 + --> $DIR/bad-reg.rs:77:26 | LL | asm!("/* {} */", out(freg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:84:26 + --> $DIR/bad-reg.rs:79:26 | LL | asm!("/* {} */", in(freg) d); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:87:26 + --> $DIR/bad-reg.rs:82:26 | LL | asm!("/* {} */", out(freg) d); | ^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -91,7 +91,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -99,7 +99,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.riscv32imafc.stderr b/tests/ui/asm/riscv/bad-reg.riscv32imafc.stderr index 57664cfe893b7..e8b10dbb0d74d 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv32imafc.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv32imafc.stderr @@ -1,65 +1,65 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: `d` target feature is not enabled - --> $DIR/bad-reg.rs:84:35 + --> $DIR/bad-reg.rs:79:35 | LL | asm!("/* {} */", in(freg) d); | ^ @@ -67,7 +67,7 @@ LL | asm!("/* {} */", in(freg) d); = note: this is required to use type `f64` with register class `freg` error: `d` target feature is not enabled - --> $DIR/bad-reg.rs:87:36 + --> $DIR/bad-reg.rs:82:36 | LL | asm!("/* {} */", out(freg) d); | ^ @@ -75,7 +75,7 @@ LL | asm!("/* {} */", out(freg) d); = note: this is required to use type `f64` with register class `freg` error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -83,7 +83,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -91,7 +91,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.riscv64gc.stderr b/tests/ui/asm/riscv/bad-reg.riscv64gc.stderr index 4ff03d819e60f..5acc779b2308c 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv64gc.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv64gc.stderr @@ -1,65 +1,65 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -67,7 +67,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -75,7 +75,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.riscv64imac.stderr b/tests/ui/asm/riscv/bad-reg.riscv64imac.stderr index fbe63eb0563c3..324a8ba50df8e 100644 --- a/tests/ui/asm/riscv/bad-reg.riscv64imac.stderr +++ b/tests/ui/asm/riscv/bad-reg.riscv64imac.stderr @@ -1,89 +1,89 @@ error: invalid register `s1`: s1 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("s1") _); | ^^^^^^^^^^^ error: invalid register `fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:35:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("fp") _); | ^^^^^^^^^^^ error: invalid register `sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:37:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("sp") _); | ^^^^^^^^^^^ error: invalid register `gp`: the global pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:39:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", out("gp") _); | ^^^^^^^^^^^ error: invalid register `tp`: the thread pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:41:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", out("tp") _); | ^^^^^^^^^^^ error: invalid register `zero`: the zero register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:43:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", out("zero") _); | ^^^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:94:18 + --> $DIR/bad-reg.rs:90:18 | LL | asm!("", in("v0") x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:97:18 + --> $DIR/bad-reg.rs:93:18 | LL | asm!("", out("v0") x); | ^^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:100:26 + --> $DIR/bad-reg.rs:96:26 | LL | asm!("/* {} */", in(vreg) x); | ^^^^^^^^^^ error: register class `vreg` can only be used as a clobber, not as an input or output - --> $DIR/bad-reg.rs:103:26 + --> $DIR/bad-reg.rs:99:26 | LL | asm!("/* {} */", out(vreg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:80:26 + --> $DIR/bad-reg.rs:75:26 | LL | asm!("/* {} */", in(freg) f); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:82:26 + --> $DIR/bad-reg.rs:77:26 | LL | asm!("/* {} */", out(freg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:84:26 + --> $DIR/bad-reg.rs:79:26 | LL | asm!("/* {} */", in(freg) d); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:87:26 + --> $DIR/bad-reg.rs:82:26 | LL | asm!("/* {} */", out(freg) d); | ^^^^^^^^^^^ error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:94:27 + --> $DIR/bad-reg.rs:90:27 | LL | asm!("", in("v0") x); | ^ @@ -91,7 +91,7 @@ LL | asm!("", in("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:97:28 + --> $DIR/bad-reg.rs:93:28 | LL | asm!("", out("v0") x); | ^ @@ -99,7 +99,7 @@ LL | asm!("", out("v0") x); = note: register class `vreg` supports these types: error: type `i32` cannot be used with this register class - --> $DIR/bad-reg.rs:100:35 + --> $DIR/bad-reg.rs:96:35 | LL | asm!("/* {} */", in(vreg) x); | ^ diff --git a/tests/ui/asm/riscv/bad-reg.rs b/tests/ui/asm/riscv/bad-reg.rs index 9332df0cc5830..1a3784f084a08 100644 --- a/tests/ui/asm/riscv/bad-reg.rs +++ b/tests/ui/asm/riscv/bad-reg.rs @@ -1,18 +1,13 @@ //@ add-minicore //@ revisions: riscv32i riscv32imafc riscv32gc riscv32e riscv64imac riscv64gc //@[riscv32i] compile-flags: --target riscv32i-unknown-none-elf -//@[riscv32i] needs-llvm-components: riscv //@[riscv32imafc] compile-flags: --target riscv32imafc-unknown-none-elf -//@[riscv32imafc] needs-llvm-components: riscv //@[riscv32gc] compile-flags: --target riscv32gc-unknown-linux-gnu -//@[riscv32gc] needs-llvm-components: riscv //@[riscv32e] compile-flags: --target riscv32e-unknown-none-elf -//@[riscv32e] needs-llvm-components: riscv //@[riscv64imac] compile-flags: --target riscv64imac-unknown-none-elf -//@[riscv64imac] needs-llvm-components: riscv //@[riscv64gc] compile-flags: --target riscv64gc-unknown-linux-gnu -//@[riscv64gc] needs-llvm-components: riscv //@ ignore-backends: gcc +//@ needs-llvm-components: riscv // Unlike riscv32e-registers.rs, this tests if the rustc can reject invalid registers // usage in the asm! API (in, out, inout, etc.). @@ -43,37 +38,37 @@ fn f() { asm!("", out("zero") _); //~^ ERROR invalid register `zero`: the zero register cannot be used as an operand for inline asm - asm!("", out("x16") _); + asm!("", in("x16") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x17") _); + asm!("", in("x17") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x18") _); + asm!("", in("x18") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x19") _); + asm!("", in("x19") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x20") _); + asm!("", in("x20") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x21") _); + asm!("", in("x21") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x22") _); + asm!("", in("x22") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x23") _); + asm!("", in("x23") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x24") _); + asm!("", in("x24") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x25") _); + asm!("", in("x25") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x26") _); + asm!("", in("x26") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x27") _); + asm!("", in("x27") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x28") _); + asm!("", in("x28") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x29") _); + asm!("", in("x29") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x30") _); + asm!("", in("x30") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature - asm!("", out("x31") _); + asm!("", in("x31") x); //[riscv32e]~^ ERROR register can't be used with the `e` target feature asm!("", out("f0") _); // ok @@ -91,6 +86,7 @@ fn f() { // Clobber-only registers // vreg asm!("", out("v0") _); // ok + asm!("", out("x31") _); // ok asm!("", in("v0") x); //~^ ERROR can only be used as a clobber //~| ERROR type `i32` cannot be used with this register class