From 9fb9225cf498b6f215502df4bf0023809c2f3056 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Sun, 15 Feb 2026 14:41:46 +0300 Subject: [PATCH 01/15] Disable debug_assert_not_in_new_nodes for multiple threads --- compiler/rustc_middle/src/dep_graph/graph.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index 3ef0511795b90..1c79de7600990 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::profiling::QueryInvocationId; use rustc_data_structures::sharded::{self, ShardedHashMap}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_data_structures::sync::{AtomicU64, Lock}; +use rustc_data_structures::sync::{AtomicU64, Lock, is_dyn_thread_safe}; use rustc_data_structures::unord::UnordMap; use rustc_data_structures::{assert_matches, outline}; use rustc_errors::DiagInner; @@ -1311,7 +1311,9 @@ impl CurrentDepGraph { prev_graph: &SerializedDepGraph, prev_index: SerializedDepNodeIndex, ) { - if let Some(ref nodes_in_current_session) = self.nodes_in_current_session { + if !is_dyn_thread_safe() + && let Some(ref nodes_in_current_session) = self.nodes_in_current_session + { debug_assert!( !nodes_in_current_session .lock() From 448097dd2386957a36880efbb78cb83021774540 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 31 Jan 2026 13:38:34 +0000 Subject: [PATCH 02/15] Suggest async block instead of async closure when possible --- .../src/error_reporting/traits/suggestions.rs | 28 +++++++++++ .../suggest-async-block-issue-140265.rs | 20 ++++++++ .../suggest-async-block-issue-140265.stderr | 50 +++++++++++++++++++ ...arg-where-it-should-have-been-called.fixed | 14 ++++++ ...as-arg-where-it-should-have-been-called.rs | 2 + ...rg-where-it-should-have-been-called.stderr | 14 +++--- 6 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs create mode 100644 tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr create mode 100644 tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed 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 176cc8c4c2cab..0e460763d9dcb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -798,6 +798,34 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return false; } + // If this is a zero-argument async closure directly passed as an argument + // and the expected type is `Future`, suggest using `async {}` block instead + // of `async || {}`. + if let ty::CoroutineClosure(def_id, args) = *self_ty.kind() + && let sig = args.as_coroutine_closure().coroutine_closure_sig().skip_binder() + && let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind() + && inputs.is_empty() + && self.tcx.is_lang_item(trait_pred.def_id(), LangItem::Future) + && let Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::Closure(hir::Closure { + kind: hir::ClosureKind::CoroutineClosure(CoroutineDesugaring::Async), + fn_arg_span: Some(arg_span), + .. + }), + .. + })) = self.tcx.hir_get_if_local(def_id) + && obligation.cause.span.contains(*arg_span) + { + err.span_suggestion_verbose( + arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)), + "use `async {}` instead of `async || {}` to introduce an async block", + "", + Applicability::MachineApplicable, + ); + return true; + } + // Get the name of the callable and the arguments to be used in the suggestion. let msg = match def_id_or_name { DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs new file mode 100644 index 0000000000000..9dc6153be2578 --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs @@ -0,0 +1,20 @@ +//@ edition:2024 +// Test that we suggest using `async {}` block instead of `async || {}` closure if possible + +use std::future::Future; + +fn takes_future(_fut: impl Future) {} + +fn main() { + // Basic case: suggest using async block + takes_future(async || { + //~^ ERROR is not a future + println!("hi!"); + }); + + // With arguments: should suggest calling the closure, not using async block + takes_future(async |x: i32| { + //~^ ERROR is not a future + println!("{x}"); + }); +} diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr new file mode 100644 index 0000000000000..a742a358c0a3f --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr @@ -0,0 +1,50 @@ +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:10:18 + | +LL | takes_future(async || { + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("hi!"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:10:18: 10:26}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use `async {}` instead of `async || {}` to introduce an async block + | +LL - takes_future(async || { +LL + takes_future(async { + | + +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:16:18 + | +LL | takes_future(async |x: i32| { + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("{x}"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use parentheses to call this closure + | +LL | }(/* i32 */)); + | +++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed new file mode 100644 index 0000000000000..88a94058bd54a --- /dev/null +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.fixed @@ -0,0 +1,14 @@ +//@ edition:2018 +//@ run-rustfix +#![allow(unused_variables)] +use std::future::Future; + +async fn foo() {} + +fn bar(f: impl Future) {} + +fn main() { + bar(foo()); //~ERROR E0277 + let async_closure = async || (); + bar(async_closure()); //~ERROR E0277 +} diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs index 8e67f4e7398cd..a05773497707c 100644 --- a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs @@ -1,4 +1,6 @@ //@ edition:2018 +//@ run-rustfix +#![allow(unused_variables)] use std::future::Future; async fn foo() {} diff --git a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr index 696b156d5a5f0..761a8d529f509 100644 --- a/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr +++ b/tests/ui/suggestions/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.stderr @@ -1,5 +1,5 @@ error[E0277]: `fn() -> impl Future {foo}` is not a future - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:9:9 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9 | LL | bar(foo); | --- ^^^ `fn() -> impl Future {foo}` is not a future @@ -8,7 +8,7 @@ LL | bar(foo); | = help: the trait `Future` is not implemented for fn item `fn() -> impl Future {foo}` note: required by a bound in `bar` - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16 | LL | fn bar(f: impl Future) {} | ^^^^^^^^^^^^^^^^^ required by this bound in `bar` @@ -17,17 +17,17 @@ help: use parentheses to call this function LL | bar(foo()); | ++ -error[E0277]: `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` is not a future - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:11:9 +error[E0277]: `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` is not a future + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:13:9 | LL | bar(async_closure); - | --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` is not a future + | --- ^^^^^^^^^^^^^ `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` is not a future | | | required by a bound introduced by this call | - = help: the trait `Future` is not implemented for `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:10:25: 10:33}` + = help: the trait `Future` is not implemented for `{async closure@$DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:12:25: 12:33}` note: required by a bound in `bar` - --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:6:16 + --> $DIR/async-fn-ctor-passed-as-arg-where-it-should-have-been-called.rs:8:16 | LL | fn bar(f: impl Future) {} | ^^^^^^^^^^^^^^^^^ required by this bound in `bar` From 37364585a10e368918e4a79466be24354ff5cf87 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 5 Feb 2026 10:09:46 +0000 Subject: [PATCH 03/15] Fix async closure suggestion when no space between || and { --- .../src/error_reporting/traits/suggestions.rs | 14 ++++++-- .../suggest-async-block-issue-140265.rs | 6 ++++ .../suggest-async-block-issue-140265.stderr | 32 ++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) 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 0e460763d9dcb..386c75dad9dd3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -800,7 +800,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // If this is a zero-argument async closure directly passed as an argument // and the expected type is `Future`, suggest using `async {}` block instead - // of `async || {}`. + // of `async || {}` if let ty::CoroutineClosure(def_id, args) = *self_ty.kind() && let sig = args.as_coroutine_closure().coroutine_closure_sig().skip_binder() && let ty::Tuple(inputs) = *sig.tupled_inputs_ty.kind() @@ -817,8 +817,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { })) = self.tcx.hir_get_if_local(def_id) && obligation.cause.span.contains(*arg_span) { + let sm = self.tcx.sess.source_map(); + let removal_span = if let Ok(snippet) = + sm.span_to_snippet(arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1))) + && snippet.ends_with(' ') + { + // There's a space after `||`, include it in the removal + arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)) + } else { + *arg_span + }; err.span_suggestion_verbose( - arg_span.with_hi(arg_span.hi() + rustc_span::BytePos(1)), + removal_span, "use `async {}` instead of `async || {}` to introduce an async block", "", Applicability::MachineApplicable, diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs index 9dc6153be2578..b5120d196f4ee 100644 --- a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.rs @@ -12,6 +12,12 @@ fn main() { println!("hi!"); }); + // Without space between `||` and `{`: should also suggest using async block + takes_future(async||{ + //~^ ERROR is not a future + println!("no space!"); + }); + // With arguments: should suggest calling the closure, not using async block takes_future(async |x: i32| { //~^ ERROR is not a future diff --git a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr index a742a358c0a3f..d81cfaac7f7d1 100644 --- a/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr +++ b/tests/ui/async-await/async-closures/suggest-async-block-issue-140265.stderr @@ -22,9 +22,33 @@ LL - takes_future(async || { LL + takes_future(async { | -error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` is not a future --> $DIR/suggest-async-block-issue-140265.rs:16:18 | +LL | takes_future(async||{ + | _____------------_^ + | | | + | | required by a bound introduced by this call +LL | | +LL | | println!("no space!"); +LL | | }); + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` is not a future + | + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:25}` +note: required by a bound in `takes_future` + --> $DIR/suggest-async-block-issue-140265.rs:6:28 + | +LL | fn takes_future(_fut: impl Future) {} + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `takes_future` +help: use `async {}` instead of `async || {}` to introduce an async block + | +LL - takes_future(async||{ +LL + takes_future(async{ + | + +error[E0277]: `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` is not a future + --> $DIR/suggest-async-block-issue-140265.rs:22:18 + | LL | takes_future(async |x: i32| { | _____------------_^ | | | @@ -32,9 +56,9 @@ LL | takes_future(async |x: i32| { LL | | LL | | println!("{x}"); LL | | }); - | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` is not a future + | |_____^ `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` is not a future | - = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:16:18: 16:32}` + = help: the trait `Future` is not implemented for `{async closure@$DIR/suggest-async-block-issue-140265.rs:22:18: 22:32}` note: required by a bound in `takes_future` --> $DIR/suggest-async-block-issue-140265.rs:6:28 | @@ -45,6 +69,6 @@ help: use parentheses to call this closure LL | }(/* i32 */)); | +++++++++++ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0277`. From d41e16bceaad145c82fcd0b38b4a316ca7a29984 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Sat, 28 Feb 2026 18:38:30 +0100 Subject: [PATCH 04/15] rustfmt: add test for field representing type builtin syntax --- src/tools/rustfmt/tests/target/field-representing-types.rs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/tools/rustfmt/tests/target/field-representing-types.rs diff --git a/src/tools/rustfmt/tests/target/field-representing-types.rs b/src/tools/rustfmt/tests/target/field-representing-types.rs new file mode 100644 index 0000000000000..a31ccbfb461f2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/field-representing-types.rs @@ -0,0 +1,2 @@ +type FRT = builtin # field_of(Type, ident); +type FRT2 = builtin # field_of(Type, ident); From af299893fdbf2bef1716705f7c458ea61eb4ddf0 Mon Sep 17 00:00:00 2001 From: Shunpoco Date: Sat, 28 Feb 2026 21:37:32 +0000 Subject: [PATCH 05/15] Introduce --ci flag in tidy * add --ci flag in tidy This commit introduces --ci flag in tidy because currently bootstrap can't pass its ci env information to tidy. It also modifies how CiInfo initialize its ci_env variable. tidy codes which uses CiEnv::is_ci for checking ci are now using ci_env in CiInfo. * address review - Fix comment - Use Option for ci flag in order to have true/false explicitly or unspecified (implicit false) * integrate CiInfo into TidyCtx * remove CiInfo * CiEnv::current() should be called when ci flag is not added * extract base_commit() to a separate function * use &TidyCtx instead of clone --- src/bootstrap/src/core/build_steps/test.rs | 3 + src/tools/tidy/src/alphabetical/tests.rs | 4 +- src/tools/tidy/src/arg_parser.rs | 3 + src/tools/tidy/src/arg_parser/tests.rs | 46 +++++++++++++ src/tools/tidy/src/deps.rs | 7 +- src/tools/tidy/src/diagnostics.rs | 73 +++++++++++++++++++-- src/tools/tidy/src/error_codes.rs | 8 +-- src/tools/tidy/src/extdeps.rs | 2 +- src/tools/tidy/src/extra_checks/mod.rs | 31 +++++---- src/tools/tidy/src/lib.rs | 75 +++------------------- src/tools/tidy/src/main.rs | 9 ++- src/tools/tidy/src/rustdoc_json.rs | 8 ++- 12 files changed, 168 insertions(+), 101 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f0fe1c03e7e14..f223ecf49507c 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1320,6 +1320,9 @@ impl Step for Tidy { if builder.config.cmd.bless() { cmd.arg("--bless"); } + if builder.config.is_running_on_ci() { + cmd.arg("--ci=true"); + } if let Some(s) = builder.config.cmd.extra_checks().or(builder.config.tidy_extra_checks.as_deref()) { diff --git a/src/tools/tidy/src/alphabetical/tests.rs b/src/tools/tidy/src/alphabetical/tests.rs index 5fa0dd751b647..c1192986e63f2 100644 --- a/src/tools/tidy/src/alphabetical/tests.rs +++ b/src/tools/tidy/src/alphabetical/tests.rs @@ -5,7 +5,7 @@ use crate::diagnostics::{TidyCtx, TidyFlags}; #[track_caller] fn test(lines: &str, name: &str, expected_msg: &str, expected_bad: bool) { - let tidy_ctx = TidyCtx::new(Path::new("/"), false, TidyFlags::default()); + let tidy_ctx = TidyCtx::new(Path::new("/"), false, None, TidyFlags::default()); let mut check = tidy_ctx.start_check("alphabetical-test"); check_lines(Path::new(name), lines, &tidy_ctx, &mut check); @@ -37,7 +37,7 @@ fn bless_test(before: &str, after: &str) { let temp_path = tempfile::Builder::new().tempfile().unwrap().into_temp_path(); std::fs::write(&temp_path, before).unwrap(); - let tidy_ctx = TidyCtx::new(Path::new("/"), false, TidyFlags::new(true)); + let tidy_ctx = TidyCtx::new(Path::new("/"), false, None, TidyFlags::new(true)); let mut check = tidy_ctx.start_check("alphabetical-test"); check_lines(&temp_path, before, &tidy_ctx, &mut check); diff --git a/src/tools/tidy/src/arg_parser.rs b/src/tools/tidy/src/arg_parser.rs index 8041f739308d4..04502ac6266b6 100644 --- a/src/tools/tidy/src/arg_parser.rs +++ b/src/tools/tidy/src/arg_parser.rs @@ -15,6 +15,7 @@ pub struct TidyArgParser { pub npm: PathBuf, pub verbose: bool, pub bless: bool, + pub ci: Option, pub extra_checks: Option>, pub pos_args: Vec, } @@ -59,6 +60,7 @@ impl TidyArgParser { ) .arg(Arg::new("verbose").help("verbose").long("verbose").action(ArgAction::SetTrue)) .arg(Arg::new("bless").help("target files are modified").long("bless").action(ArgAction::SetTrue)) + .arg(Arg::new("ci").help("ci flag").long("ci").default_missing_value("true").num_args(0..=1).value_parser(value_parser!(bool))) .arg( Arg::new("extra_checks") .help("extra checks") @@ -78,6 +80,7 @@ impl TidyArgParser { npm: matches.get_one::("npm").unwrap().clone(), verbose: *matches.get_one::("verbose").unwrap(), bless: *matches.get_one::("bless").unwrap(), + ci: matches.get_one::("ci").cloned(), extra_checks: None, pos_args: vec![], }; diff --git a/src/tools/tidy/src/arg_parser/tests.rs b/src/tools/tidy/src/arg_parser/tests.rs index c5e7aed21c1a0..3aa6162f5c7d6 100644 --- a/src/tools/tidy/src/arg_parser/tests.rs +++ b/src/tools/tidy/src/arg_parser/tests.rs @@ -19,6 +19,7 @@ fn test_tidy_parser_full() { "yarn", "--verbose", "--bless", + "--ci", "--extra-checks", "if-installed:auto:js,auto:if-installed:py,if-installed:auto:cpp,if-installed:auto:spellcheck", "--", // pos_args @@ -38,6 +39,8 @@ fn test_tidy_parser_full() { assert_eq!(parsed_args.npm, PathBuf::from("yarn")); assert!(parsed_args.verbose); assert!(parsed_args.bless); + assert!(parsed_args.ci.is_some()); + assert!(parsed_args.ci.unwrap()); assert_eq!( parsed_args.extra_checks, Some(vec![ @@ -166,3 +169,46 @@ fn test_tidy_parser_missing_npm_path() { let cmd = TidyArgParser::command(); assert!(cmd.try_get_matches_from(args).is_err()); } + +// --ci has some variations +#[test] +fn test_tidy_parse_ci_flag() { + // They are requried + let base_args = vec![ + "rust-tidy", + "--root-path", + "/home/user/rust", + "--cargo-path", + "/home/user/rust/build/x86_64-unknown-linux-gnu/stage0/bin/cargo", + "--output-dir", + "/home/user/rust/build", + "--concurrency", + "16", + "--npm-path", + "yarn", + ]; + + // No --ci + let parsed_args = TidyArgParser::build(TidyArgParser::command().get_matches_from(&base_args)); + assert!(parsed_args.ci.is_none()); + + // --ci + let mut args1 = base_args.clone(); + args1.push("--ci"); + let parsed_args = TidyArgParser::build(TidyArgParser::command().get_matches_from(args1)); + assert!(parsed_args.ci.is_some()); + + // --ci=true + let mut args2 = base_args.clone(); + args2.extend_from_slice(&["--ci", "true"]); + let parsed_args = TidyArgParser::build(TidyArgParser::command().get_matches_from(args2)); + assert!(parsed_args.ci.is_some()); + assert!(parsed_args.ci.unwrap()); + + // --ci=false + let mut args2 = base_args.clone(); + args2.extend_from_slice(&["--ci", "false"]); + let parsed_args = TidyArgParser::build(TidyArgParser::command().get_matches_from(args2)); + assert!(parsed_args.ci.is_some()); + assert!(!parsed_args.ci.unwrap()); +} diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 24c610b41f3a4..2879df6670e81 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -6,7 +6,6 @@ use std::fs::{File, read_dir}; use std::io::Write; use std::path::Path; -use build_helper::ci::CiEnv; use cargo_metadata::semver::Version; use cargo_metadata::{Metadata, Package, PackageId}; @@ -637,7 +636,7 @@ pub fn check(root: &Path, cargo: &Path, tidy_ctx: TidyCtx) { check_proc_macro_dep_list(root, cargo, bless, &mut check); for &WorkspaceInfo { path, exceptions, crates_and_deps, submodules } in WORKSPACES { - if has_missing_submodule(root, submodules) { + if has_missing_submodule(root, submodules, tidy_ctx.is_running_on_ci()) { continue; } @@ -757,8 +756,8 @@ pub static CRATES: &[&str] = &[ /// Used to skip a check if a submodule is not checked out, and not in a CI environment. /// /// This helps prevent enforcing developers to fetch submodules for tidy. -pub fn has_missing_submodule(root: &Path, submodules: &[&str]) -> bool { - !CiEnv::is_ci() +pub fn has_missing_submodule(root: &Path, submodules: &[&str], is_ci: bool) -> bool { + !is_ci && submodules.iter().any(|submodule| { let path = root.join(submodule); !path.exists() diff --git a/src/tools/tidy/src/diagnostics.rs b/src/tools/tidy/src/diagnostics.rs index 4e6c316f5e18e..3f93316f63606 100644 --- a/src/tools/tidy/src/diagnostics.rs +++ b/src/tools/tidy/src/diagnostics.rs @@ -4,6 +4,9 @@ use std::io; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; +use build_helper::ci::CiEnv; +use build_helper::git::{GitConfig, get_closest_upstream_commit}; +use build_helper::stage0_parser::{Stage0Config, parse_stage0_file}; use termcolor::Color; /// CLI flags used by tidy. @@ -28,11 +31,24 @@ impl TidyFlags { pub struct TidyCtx { tidy_flags: TidyFlags, diag_ctx: Arc>, + ci_env: CiEnv, + pub base_commit: Option, } impl TidyCtx { - pub fn new(root_path: &Path, verbose: bool, tidy_flags: TidyFlags) -> Self { - Self { + pub fn new( + root_path: &Path, + verbose: bool, + ci_flag: Option, + tidy_flags: TidyFlags, + ) -> Self { + let ci_env = match ci_flag { + Some(true) => CiEnv::GitHubActions, + Some(false) => CiEnv::None, + None => CiEnv::current(), + }; + + let mut tidy_ctx = Self { diag_ctx: Arc::new(Mutex::new(DiagCtxInner { running_checks: Default::default(), finished_checks: Default::default(), @@ -40,13 +56,22 @@ impl TidyCtx { verbose, })), tidy_flags, - } + ci_env, + base_commit: None, + }; + tidy_ctx.base_commit = find_base_commit(&tidy_ctx); + + tidy_ctx } pub fn is_bless_enabled(&self) -> bool { self.tidy_flags.bless } + pub fn is_running_on_ci(&self) -> bool { + self.ci_env.is_running_in_ci() + } + pub fn start_check>(&self, id: Id) -> RunningCheck { let mut id = id.into(); @@ -75,6 +100,46 @@ impl TidyCtx { } } +fn find_base_commit(tidy_ctx: &TidyCtx) -> Option { + let mut check = tidy_ctx.start_check("CI history"); + + let stage0 = parse_stage0_file(); + let Stage0Config { nightly_branch, git_merge_commit_email, .. } = stage0.config; + + let base_commit = match get_closest_upstream_commit( + None, + &GitConfig { + nightly_branch: &nightly_branch, + git_merge_commit_email: &git_merge_commit_email, + }, + tidy_ctx.ci_env, + ) { + Ok(Some(commit)) => Some(commit), + Ok(None) => { + error_if_in_ci("no base commit found", tidy_ctx.is_running_on_ci(), &mut check); + None + } + Err(error) => { + error_if_in_ci( + &format!("failed to retrieve base commit: {error}"), + tidy_ctx.is_running_on_ci(), + &mut check, + ); + None + } + }; + + base_commit +} + +fn error_if_in_ci(msg: &str, is_ci: bool, check: &mut RunningCheck) { + if is_ci { + check.error(msg); + } else { + check.warning(format!("{msg}. Some checks will be skipped.")); + } +} + struct DiagCtxInner { running_checks: HashSet, finished_checks: HashSet, @@ -175,7 +240,7 @@ impl RunningCheck { /// Useful if you want to run some functions from tidy without configuring /// diagnostics. pub fn new_noop() -> Self { - let ctx = TidyCtx::new(Path::new(""), false, TidyFlags::default()); + let ctx = TidyCtx::new(Path::new(""), false, None, TidyFlags::default()); ctx.start_check("noop") } diff --git a/src/tools/tidy/src/error_codes.rs b/src/tools/tidy/src/error_codes.rs index 185f3187a15c8..76fbf79951a9c 100644 --- a/src/tools/tidy/src/error_codes.rs +++ b/src/tools/tidy/src/error_codes.rs @@ -36,11 +36,11 @@ const IGNORE_DOCTEST_CHECK: &[&str] = &["E0464", "E0570", "E0601", "E0602", "E07 const IGNORE_UI_TEST_CHECK: &[&str] = &["E0461", "E0465", "E0514", "E0554", "E0640", "E0717", "E0729"]; -pub fn check(root_path: &Path, search_paths: &[&Path], ci_info: &crate::CiInfo, tidy_ctx: TidyCtx) { +pub fn check(root_path: &Path, search_paths: &[&Path], tidy_ctx: TidyCtx) { let mut check = tidy_ctx.start_check("error_codes"); // Check that no error code explanation was removed. - check_removed_error_code_explanation(ci_info, &mut check); + check_removed_error_code_explanation(&tidy_ctx.base_commit, &mut check); // Stage 1: create list let error_codes = extract_error_codes(root_path, &mut check); @@ -57,8 +57,8 @@ pub fn check(root_path: &Path, search_paths: &[&Path], ci_info: &crate::CiInfo, check_error_codes_used(search_paths, &error_codes, &mut check, &no_longer_emitted); } -fn check_removed_error_code_explanation(ci_info: &crate::CiInfo, check: &mut RunningCheck) { - let Some(base_commit) = &ci_info.base_commit else { +fn check_removed_error_code_explanation(base_commit: &Option, check: &mut RunningCheck) { + let Some(base_commit) = base_commit else { check.verbose_msg("Skipping error code explanation removal check"); return; }; diff --git a/src/tools/tidy/src/extdeps.rs b/src/tools/tidy/src/extdeps.rs index 19c773d12f7fa..7999386a3c298 100644 --- a/src/tools/tidy/src/extdeps.rs +++ b/src/tools/tidy/src/extdeps.rs @@ -19,7 +19,7 @@ pub fn check(root: &Path, tidy_ctx: TidyCtx) { let mut check = tidy_ctx.start_check("extdeps"); for &WorkspaceInfo { path, submodules, .. } in crate::deps::WORKSPACES { - if crate::deps::has_missing_submodule(root, submodules) { + if crate::deps::has_missing_submodule(root, submodules, tidy_ctx.is_running_on_ci()) { continue; } diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index 28e78b396d557..124de884637ea 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -23,9 +23,6 @@ use std::process::Command; use std::str::FromStr; use std::{env, fmt, fs, io}; -use build_helper::ci::CiEnv; - -use crate::CiInfo; use crate::diagnostics::TidyCtx; mod rustdoc_js; @@ -53,7 +50,6 @@ const SPELLCHECK_VER: &str = "1.38.1"; pub fn check( root_path: &Path, outdir: &Path, - ci_info: &CiInfo, librustdoc_path: &Path, tools_path: &Path, npm: &Path, @@ -67,7 +63,6 @@ pub fn check( if let Err(e) = check_impl( root_path, outdir, - ci_info, librustdoc_path, tools_path, npm, @@ -83,7 +78,6 @@ pub fn check( fn check_impl( root_path: &Path, outdir: &Path, - ci_info: &CiInfo, librustdoc_path: &Path, tools_path: &Path, npm: &Path, @@ -121,9 +115,12 @@ fn check_impl( }; lint_args.retain(|ck| ck.is_non_if_installed_or_matches(root_path, outdir)); if lint_args.iter().any(|ck| ck.auto) { - crate::files_modified_batch_filter(ci_info, &mut lint_args, |ck, path| { - ck.is_non_auto_or_matches(path) - }); + crate::files_modified_batch_filter( + &tidy_ctx.base_commit, + tidy_ctx.is_running_on_ci(), + &mut lint_args, + |ck, path| ck.is_non_auto_or_matches(path), + ); } macro_rules! extra_check { @@ -321,7 +318,7 @@ fn check_impl( } else { eprintln!("spellchecking files"); } - let res = spellcheck_runner(root_path, &outdir, &cargo, &args); + let res = spellcheck_runner(root_path, &outdir, &cargo, &args, tidy_ctx.is_running_on_ci()); if res.is_err() { rerun_with_bless("spellcheck", "fix typos"); } @@ -629,9 +626,16 @@ fn spellcheck_runner( outdir: &Path, cargo: &Path, args: &[&str], + is_ci: bool, ) -> Result<(), Error> { - let bin_path = - ensure_version_or_cargo_install(outdir, cargo, "typos-cli", "typos", SPELLCHECK_VER)?; + let bin_path = ensure_version_or_cargo_install( + outdir, + cargo, + "typos-cli", + "typos", + SPELLCHECK_VER, + is_ci, + )?; match Command::new(bin_path).current_dir(src_root).args(args).status() { Ok(status) => { if status.success() { @@ -713,6 +717,7 @@ fn ensure_version_or_cargo_install( pkg_name: &str, bin_name: &str, version: &str, + is_ci: bool, ) -> Result { if let Ok(bin_path) = ensure_version(build_dir, bin_name, version) { return Ok(bin_path); @@ -746,7 +751,7 @@ fn ensure_version_or_cargo_install( // On CI, we set opt-level flag for quicker installation. // Since lower opt-level decreases the tool's performance, // we don't set this option on local. - if CiEnv::is_ci() { + if is_ci { cmd.env("RUSTFLAGS", "-Copt-level=0"); } diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs index ed41130f5d299..2cb8df782d28e 100644 --- a/src/tools/tidy/src/lib.rs +++ b/src/tools/tidy/src/lib.rs @@ -6,12 +6,6 @@ use std::ffi::OsStr; use std::process::Command; -use build_helper::ci::CiEnv; -use build_helper::git::{GitConfig, get_closest_upstream_commit}; -use build_helper::stage0_parser::{Stage0Config, parse_stage0_file}; - -use crate::diagnostics::{RunningCheck, TidyCtx}; - macro_rules! static_regex { ($re:literal) => {{ static RE: ::std::sync::LazyLock<::regex::Regex> = @@ -42,60 +36,6 @@ macro_rules! t { }; } -pub struct CiInfo { - pub git_merge_commit_email: String, - pub nightly_branch: String, - pub base_commit: Option, - pub ci_env: CiEnv, -} - -impl CiInfo { - pub fn new(tidy_ctx: TidyCtx) -> Self { - let mut check = tidy_ctx.start_check("CI history"); - - let stage0 = parse_stage0_file(); - let Stage0Config { nightly_branch, git_merge_commit_email, .. } = stage0.config; - - let mut info = Self { - nightly_branch, - git_merge_commit_email, - ci_env: CiEnv::current(), - base_commit: None, - }; - let base_commit = match get_closest_upstream_commit(None, &info.git_config(), info.ci_env) { - Ok(Some(commit)) => Some(commit), - Ok(None) => { - info.error_if_in_ci("no base commit found", &mut check); - None - } - Err(error) => { - info.error_if_in_ci( - &format!("failed to retrieve base commit: {error}"), - &mut check, - ); - None - } - }; - info.base_commit = base_commit; - info - } - - pub fn git_config(&self) -> GitConfig<'_> { - GitConfig { - nightly_branch: &self.nightly_branch, - git_merge_commit_email: &self.git_merge_commit_email, - } - } - - pub fn error_if_in_ci(&self, msg: &str, check: &mut RunningCheck) { - if self.ci_env.is_running_in_ci() { - check.error(msg); - } else { - check.warning(format!("{msg}. Some checks will be skipped.")); - } - } -} - pub fn git_diff>(base_commit: &str, extra_arg: S) -> Option { let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?; Some(String::from_utf8_lossy(&output.stdout).into()) @@ -107,15 +47,16 @@ pub fn git_diff>(base_commit: &str, extra_arg: S) -> Option( - ci_info: &CiInfo, + base_commit: &Option, + is_ci: bool, items: &mut Vec, pred: impl Fn(&T, &str) -> bool, ) { - if CiEnv::is_ci() { + if is_ci { // assume everything is modified on CI because we really don't want false positives there. return; } - let Some(base_commit) = &ci_info.base_commit else { + let Some(base_commit) = base_commit else { eprintln!("No base commit, assuming all files are modified"); return; }; @@ -150,9 +91,13 @@ pub fn files_modified_batch_filter( } /// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files. -pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool { +pub fn files_modified( + base_commit: &Option, + is_ci: bool, + pred: impl Fn(&str) -> bool, +) -> bool { let mut v = vec![()]; - files_modified_batch_filter(ci_info, &mut v, |_, p| pred(p)); + files_modified_batch_filter(base_commit, is_ci, &mut v, |_, p| pred(p)); !v.is_empty() } diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs index 09c08e1baf503..0e9885138d1e1 100644 --- a/src/tools/tidy/src/main.rs +++ b/src/tools/tidy/src/main.rs @@ -40,11 +40,11 @@ fn main() { let verbose = parsed_args.verbose; let bless = parsed_args.bless; + let ci = parsed_args.ci; let extra_checks = parsed_args.extra_checks; let pos_args = parsed_args.pos_args; - let tidy_ctx = TidyCtx::new(&root_path, verbose, TidyFlags::new(bless)); - let ci_info = CiInfo::new(tidy_ctx.clone()); + let tidy_ctx = TidyCtx::new(&root_path, verbose, ci, TidyFlags::new(bless)); let drain_handles = |handles: &mut VecDeque>| { // poll all threads for completion before awaiting the oldest one @@ -101,12 +101,12 @@ fn main() { check!(rustdoc_gui_tests, &tests_path); check!(rustdoc_css_themes, &librustdoc_path); check!(rustdoc_templates, &librustdoc_path); - check!(rustdoc_json, &src_path, &ci_info); + check!(rustdoc_json, &src_path); check!(known_bug, &crashes_path); check!(unknown_revision, &tests_path); // Checks that only make sense for the compiler. - check!(error_codes, &root_path, &[&compiler_path, &librustdoc_path], &ci_info); + check!(error_codes, &root_path, &[&compiler_path, &librustdoc_path]); check!(target_policy, &root_path); check!(gcc_submodule, &root_path, &compiler_path); @@ -154,7 +154,6 @@ fn main() { extra_checks, &root_path, &output_directory, - &ci_info, &librustdoc_path, &tools_path, &npm, diff --git a/src/tools/tidy/src/rustdoc_json.rs b/src/tools/tidy/src/rustdoc_json.rs index b8fb04f2d4e1f..ef7dabc8021ba 100644 --- a/src/tools/tidy/src/rustdoc_json.rs +++ b/src/tools/tidy/src/rustdoc_json.rs @@ -8,16 +8,18 @@ use crate::diagnostics::{CheckId, TidyCtx}; const RUSTDOC_JSON_TYPES: &str = "src/rustdoc-json-types"; -pub fn check(src_path: &Path, ci_info: &crate::CiInfo, tidy_ctx: TidyCtx) { +pub fn check(src_path: &Path, tidy_ctx: TidyCtx) { let mut check = tidy_ctx.start_check(CheckId::new("rustdoc_json").path(src_path)); - let Some(base_commit) = &ci_info.base_commit else { + let Some(base_commit) = &tidy_ctx.base_commit else { check.verbose_msg("No base commit, skipping rustdoc_json check"); return; }; // First we check that `src/rustdoc-json-types` was modified. - if !crate::files_modified(ci_info, |p| p.starts_with(RUSTDOC_JSON_TYPES)) { + if !crate::files_modified(&tidy_ctx.base_commit, tidy_ctx.is_running_on_ci(), |p| { + p.starts_with(RUSTDOC_JSON_TYPES) + }) { // `rustdoc-json-types` was not modified so nothing more to check here. return; } From d11748689a03a532435a4120e6f10dce49d927fb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:09:39 +1100 Subject: [PATCH 06/15] Remove `DepKindVTable::is_anon`. It's unused. --- compiler/rustc_middle/src/dep_graph/dep_node.rs | 5 ----- compiler/rustc_query_impl/src/dep_kind_vtables.rs | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 63b78955ba87b..13b693f147a3c 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -174,11 +174,6 @@ impl fmt::Debug for DepNode { /// of the `DepKind`. Overall, this allows to implement `DepContext` using this manual /// jump table instead of large matches. pub struct DepKindVTable<'tcx> { - /// Anonymous queries cannot be replayed from one compiler invocation to the next. - /// When their result is needed, it is recomputed. They are useful for fine-grained - /// dependency tracking, and caching within one compiler invocation. - pub is_anon: bool, - /// Eval-always queries do not track their dependencies, and are always recomputed, even if /// their inputs have not changed since the last compiler invocation. The result is still /// cached within one compiler invocation. diff --git a/compiler/rustc_query_impl/src/dep_kind_vtables.rs b/compiler/rustc_query_impl/src/dep_kind_vtables.rs index f892ff05214f9..be833b0c8cf34 100644 --- a/compiler/rustc_query_impl/src/dep_kind_vtables.rs +++ b/compiler/rustc_query_impl/src/dep_kind_vtables.rs @@ -13,7 +13,6 @@ mod non_query { // We use this for most things when incr. comp. is turned off. pub(crate) fn Null<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Unit, force_from_dep_node: Some(|_, dep_node, _| { @@ -26,7 +25,6 @@ mod non_query { // We use this for the forever-red node. pub(crate) fn Red<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Unit, force_from_dep_node: Some(|_, dep_node, _| { @@ -38,7 +36,6 @@ mod non_query { pub(crate) fn SideEffect<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Unit, force_from_dep_node: Some(|tcx, _, prev_index| { @@ -51,7 +48,6 @@ mod non_query { pub(crate) fn AnonZeroDeps<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: true, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Opaque, force_from_dep_node: Some(|_, _, _| bug!("cannot force an anon node")), @@ -61,7 +57,6 @@ mod non_query { pub(crate) fn TraitSelect<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: true, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Unit, force_from_dep_node: None, @@ -71,7 +66,6 @@ mod non_query { pub(crate) fn CompileCodegenUnit<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Opaque, force_from_dep_node: None, @@ -81,7 +75,6 @@ mod non_query { pub(crate) fn CompileMonoItem<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Opaque, force_from_dep_node: None, @@ -91,7 +84,6 @@ mod non_query { pub(crate) fn Metadata<'tcx>() -> DepKindVTable<'tcx> { DepKindVTable { - is_anon: false, is_eval_always: false, key_fingerprint_style: KeyFingerprintStyle::Unit, force_from_dep_node: None, @@ -117,7 +109,6 @@ where if is_anon || !key_fingerprint_style.reconstructible() { return DepKindVTable { - is_anon, is_eval_always, key_fingerprint_style, force_from_dep_node: None, @@ -126,7 +117,6 @@ where } DepKindVTable { - is_anon, is_eval_always, key_fingerprint_style, force_from_dep_node: Some(|tcx, dep_node, _| { From f1789faf17408e5717bfb59f8875198a13e3f9eb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:27:31 +1100 Subject: [PATCH 07/15] Inline and remove `handle_cycle_error`. It has a single use. --- compiler/rustc_query_impl/src/execution.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 22155e961426f..26b916fb7569c 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -4,7 +4,7 @@ use std::mem; use rustc_data_structures::hash_table::{Entry, HashTable}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::{outline, sharded, sync}; -use rustc_errors::{Diag, FatalError, StashKey}; +use rustc_errors::{FatalError, StashKey}; use rustc_middle::dep_graph::{DepGraphData, DepNodeKey, SerializedDepNodeIndex}; use rustc_middle::query::plumbing::QueryVTable; use rustc_middle::query::{ @@ -108,19 +108,10 @@ fn mk_cycle<'tcx, C: QueryCache>( cycle_error: CycleError, ) -> C::Value { let error = report_cycle(tcx.sess, &cycle_error); - handle_cycle_error(query, tcx, &cycle_error, error) -} - -fn handle_cycle_error<'tcx, C: QueryCache>( - query: &'tcx QueryVTable<'tcx, C>, - tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, - error: Diag<'_>, -) -> C::Value { match query.cycle_error_handling { CycleErrorHandling::Error => { let guar = error.emit(); - query.value_from_cycle_error(tcx, cycle_error, guar) + query.value_from_cycle_error(tcx, &cycle_error, guar) } CycleErrorHandling::Fatal => { error.emit(); @@ -129,7 +120,7 @@ fn handle_cycle_error<'tcx, C: QueryCache>( } CycleErrorHandling::DelayBug => { let guar = error.delay_as_bug(); - query.value_from_cycle_error(tcx, cycle_error, guar) + query.value_from_cycle_error(tcx, &cycle_error, guar) } CycleErrorHandling::Stash => { let guar = if let Some(root) = cycle_error.cycle.first() @@ -139,7 +130,7 @@ fn handle_cycle_error<'tcx, C: QueryCache>( } else { error.emit() }; - query.value_from_cycle_error(tcx, cycle_error, guar) + query.value_from_cycle_error(tcx, &cycle_error, guar) } } } From adf2d1ed34863061a195326006545aecbb2ba877 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:31:32 +1100 Subject: [PATCH 08/15] Make `from_cycle_error` consume the `CycleError`. This makes it clear the `CycleError` is not used after the call. --- compiler/rustc_middle/src/query/plumbing.rs | 4 +-- compiler/rustc_query_impl/src/execution.rs | 6 ++-- compiler/rustc_query_impl/src/values.rs | 37 +++++++-------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 55d760c55cd2f..5bb84e030e14a 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -148,7 +148,7 @@ pub struct QueryVTable<'tcx, C: QueryCache> { pub is_loadable_from_disk_fn: Option>, pub hash_result: HashResult, pub value_from_cycle_error: - fn(tcx: TyCtxt<'tcx>, cycle_error: &CycleError, guar: ErrorGuaranteed) -> C::Value, + fn(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> C::Value, pub format_value: fn(&C::Value) -> String, /// Formats a human-readable description of this query and its key, as @@ -213,7 +213,7 @@ impl<'tcx, C: QueryCache> QueryVTable<'tcx, C> { pub fn value_from_cycle_error( &self, tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, + cycle_error: CycleError, guar: ErrorGuaranteed, ) -> C::Value { (self.value_from_cycle_error)(tcx, cycle_error, guar) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 26b916fb7569c..9c6f5cdf9839b 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -111,7 +111,7 @@ fn mk_cycle<'tcx, C: QueryCache>( match query.cycle_error_handling { CycleErrorHandling::Error => { let guar = error.emit(); - query.value_from_cycle_error(tcx, &cycle_error, guar) + query.value_from_cycle_error(tcx, cycle_error, guar) } CycleErrorHandling::Fatal => { error.emit(); @@ -120,7 +120,7 @@ fn mk_cycle<'tcx, C: QueryCache>( } CycleErrorHandling::DelayBug => { let guar = error.delay_as_bug(); - query.value_from_cycle_error(tcx, &cycle_error, guar) + query.value_from_cycle_error(tcx, cycle_error, guar) } CycleErrorHandling::Stash => { let guar = if let Some(root) = cycle_error.cycle.first() @@ -130,7 +130,7 @@ fn mk_cycle<'tcx, C: QueryCache>( } else { error.emit() }; - query.value_from_cycle_error(tcx, &cycle_error, guar) + query.value_from_cycle_error(tcx, cycle_error, guar) } } } diff --git a/compiler/rustc_query_impl/src/values.rs b/compiler/rustc_query_impl/src/values.rs index 8f55e98df867e..6e739ee04a024 100644 --- a/compiler/rustc_query_impl/src/values.rs +++ b/compiler/rustc_query_impl/src/values.rs @@ -18,14 +18,13 @@ use rustc_span::{ErrorGuaranteed, Span}; use crate::job::report_cycle; pub(crate) trait Value<'tcx>: Sized { - fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: &CycleError, guar: ErrorGuaranteed) - -> Self; + fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self; } impl<'tcx, T> Value<'tcx> for T { default fn from_cycle_error( tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, + cycle_error: CycleError, _guar: ErrorGuaranteed, ) -> T { tcx.sess.dcx().abort_if_errors(); @@ -38,7 +37,7 @@ impl<'tcx, T> Value<'tcx> for T { } impl<'tcx> Value<'tcx> for Ty<'_> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &CycleError, guar: ErrorGuaranteed) -> Self { + fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { // SAFETY: This is never called when `Self` is not `Ty<'tcx>`. // FIXME: Represent the above fact in the trait system somehow. unsafe { std::mem::transmute::, Ty<'_>>(Ty::new_error(tcx, guar)) } @@ -46,13 +45,13 @@ impl<'tcx> Value<'tcx> for Ty<'_> { } impl<'tcx> Value<'tcx> for Result>, CyclePlaceholder> { - fn from_cycle_error(_tcx: TyCtxt<'tcx>, _: &CycleError, guar: ErrorGuaranteed) -> Self { + fn from_cycle_error(_tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { Err(CyclePlaceholder(guar)) } } impl<'tcx> Value<'tcx> for ty::SymbolName<'_> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &CycleError, _guar: ErrorGuaranteed) -> Self { + fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, _guar: ErrorGuaranteed) -> Self { // SAFETY: This is never called when `Self` is not `SymbolName<'tcx>`. // FIXME: Represent the above fact in the trait system somehow. unsafe { @@ -64,11 +63,7 @@ impl<'tcx> Value<'tcx> for ty::SymbolName<'_> { } impl<'tcx> Value<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { - fn from_cycle_error( - tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, - guar: ErrorGuaranteed, - ) -> Self { + fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { let err = Ty::new_error(tcx, guar); let arity = if let Some(info) = cycle_error.cycle.get(0) @@ -100,7 +95,7 @@ impl<'tcx> Value<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { impl<'tcx> Value<'tcx> for Representability { fn from_cycle_error( tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, + cycle_error: CycleError, _guar: ErrorGuaranteed, ) -> Self { let mut item_and_field_ids = Vec::new(); @@ -134,21 +129,13 @@ impl<'tcx> Value<'tcx> for Representability { } impl<'tcx> Value<'tcx> for ty::EarlyBinder<'_, Ty<'_>> { - fn from_cycle_error( - tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, - guar: ErrorGuaranteed, - ) -> Self { + fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { ty::EarlyBinder::bind(Ty::from_cycle_error(tcx, cycle_error, guar)) } } impl<'tcx> Value<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> { - fn from_cycle_error( - tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, - guar: ErrorGuaranteed, - ) -> Self { + fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { ty::EarlyBinder::bind(ty::Binder::from_cycle_error(tcx, cycle_error, guar)) } } @@ -156,7 +143,7 @@ impl<'tcx> Value<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> { impl<'tcx> Value<'tcx> for &[ty::Variance] { fn from_cycle_error( tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, + cycle_error: CycleError, _guar: ErrorGuaranteed, ) -> Self { search_for_cycle_permutation( @@ -204,7 +191,7 @@ fn search_for_cycle_permutation( impl<'tcx, T> Value<'tcx> for Result> { fn from_cycle_error( tcx: TyCtxt<'tcx>, - cycle_error: &CycleError, + cycle_error: CycleError, _guar: ErrorGuaranteed, ) -> Self { let diag = search_for_cycle_permutation( @@ -280,7 +267,7 @@ impl<'tcx, T> Value<'tcx> for Result> { ControlFlow::Continue(()) } }, - || report_cycle(tcx.sess, cycle_error), + || report_cycle(tcx.sess, &cycle_error), ); let guar = diag.emit(); From 86e1af9ac88842c0422389561c186deadaa7fad0 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:35:45 +1100 Subject: [PATCH 09/15] Avoid an early return in `try_execute_query`. When there are two cases of equal size and importance, I find an if/else expression easier to read than an early return. --- compiler/rustc_query_impl/src/execution.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 9c6f5cdf9839b..01a97339e7852 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -313,15 +313,15 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>( // Only call `wait_for_query` if we're using a Rayon thread pool // as it will attempt to mark the worker thread as blocked. - return wait_for_query(query, tcx, span, key, latch, current_job_id); - } - - let id = job.id; - drop(state_lock); + wait_for_query(query, tcx, span, key, latch, current_job_id) + } else { + let id = job.id; + drop(state_lock); - // If we are single-threaded we know that we have cycle error, - // so we just return the error. - cycle_error(query, tcx, id, span) + // If we are single-threaded we know that we have cycle error, + // so we just return the error. + cycle_error(query, tcx, id, span) + } } ActiveKeyStatus::Poisoned => FatalError.raise(), } From f1c39fecdcc040ab7ee99bf8d25ddc3f3adcea14 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:40:47 +1100 Subject: [PATCH 10/15] Merge two assertion blocks in `execute_job_non_incr`. It reads better that way. --- compiler/rustc_query_impl/src/execution.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 01a97339e7852..ca4b1275f7cdd 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -403,12 +403,6 @@ fn execute_job_non_incr<'tcx, C: QueryCache>( ) -> (C::Value, DepNodeIndex) { debug_assert!(!tcx.dep_graph.is_fully_enabled()); - // Fingerprint the key, just to assert that it doesn't - // have anything we don't consider hashable - if cfg!(debug_assertions) { - let _ = key.to_fingerprint(tcx); - } - let prof_timer = tcx.prof.query_provider(); // Call the query provider. let result = @@ -416,14 +410,14 @@ fn execute_job_non_incr<'tcx, C: QueryCache>( let dep_node_index = tcx.dep_graph.next_virtual_depnode_index(); prof_timer.finish_with_query_invocation_id(dep_node_index.into()); - // Similarly, fingerprint the result to assert that - // it doesn't have anything not considered hashable. - if cfg!(debug_assertions) - && let Some(hash_result) = query.hash_result - { - tcx.with_stable_hashing_context(|mut hcx| { - hash_result(&mut hcx, &result); - }); + // Sanity: Fingerprint the key and the result to assert they don't contain anything unhashable. + if cfg!(debug_assertions) { + let _ = key.to_fingerprint(tcx); + if let Some(hash_result) = query.hash_result { + tcx.with_stable_hashing_context(|mut hcx| { + hash_result(&mut hcx, &result); + }); + } } (result, dep_node_index) From e7dfd53ddd4e6a295b40326b8f03aae3cdb0027a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 10:48:53 +1100 Subject: [PATCH 11/15] Remove three single-use type synonyms. It's an unnecessary level of indirection. --- compiler/rustc_middle/src/query/plumbing.rs | 26 +++++++++------------ 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 5bb84e030e14a..13ba20ef7bd56 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -62,18 +62,6 @@ pub enum CycleErrorHandling { Stash, } -pub type WillCacheOnDiskForKeyFn<'tcx, Key> = fn(tcx: TyCtxt<'tcx>, key: &Key) -> bool; - -pub type TryLoadFromDiskFn<'tcx, Key, Value> = fn( - tcx: TyCtxt<'tcx>, - key: &Key, - prev_index: SerializedDepNodeIndex, - index: DepNodeIndex, -) -> Option; - -pub type IsLoadableFromDiskFn<'tcx, Key> = - fn(tcx: TyCtxt<'tcx>, key: &Key, index: SerializedDepNodeIndex) -> bool; - pub type HashResult = Option, &V) -> Fingerprint>; #[derive(Clone, Debug)] @@ -128,7 +116,7 @@ pub struct QueryVTable<'tcx, C: QueryCache> { pub cycle_error_handling: CycleErrorHandling, pub state: QueryState<'tcx, C::Key>, pub cache: C, - pub will_cache_on_disk_for_key_fn: Option>, + pub will_cache_on_disk_for_key_fn: Option, key: &C::Key) -> bool>, /// Function pointer that calls `tcx.$query(key)` for this query and /// discards the returned value. @@ -144,8 +132,16 @@ pub struct QueryVTable<'tcx, C: QueryCache> { /// This should be the only code that calls the provider function. pub invoke_provider_fn: fn(tcx: TyCtxt<'tcx>, key: C::Key) -> C::Value, - pub try_load_from_disk_fn: Option>, - pub is_loadable_from_disk_fn: Option>, + pub try_load_from_disk_fn: Option< + fn( + tcx: TyCtxt<'tcx>, + key: &C::Key, + prev_index: SerializedDepNodeIndex, + index: DepNodeIndex, + ) -> Option, + >, + pub is_loadable_from_disk_fn: + Option, key: &C::Key, index: SerializedDepNodeIndex) -> bool>, pub hash_result: HashResult, pub value_from_cycle_error: fn(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> C::Value, From 2322ccad94057a772223e0a3ac7343d036a86df4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 14:31:51 +1100 Subject: [PATCH 12/15] Replace two `abort_if_errors` calls. Calling `abort_if_errors` after emitting an error is guaranteed to call `raise_fatal`, so just do that directly instead. --- compiler/rustc_query_impl/src/execution.rs | 5 ++--- compiler/rustc_query_impl/src/values.rs | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index ca4b1275f7cdd..a94b198aba8e8 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -114,9 +114,8 @@ fn mk_cycle<'tcx, C: QueryCache>( query.value_from_cycle_error(tcx, cycle_error, guar) } CycleErrorHandling::Fatal => { - error.emit(); - tcx.dcx().abort_if_errors(); - unreachable!() + let guar = error.emit(); + guar.raise_fatal(); } CycleErrorHandling::DelayBug => { let guar = error.delay_as_bug(); diff --git a/compiler/rustc_query_impl/src/values.rs b/compiler/rustc_query_impl/src/values.rs index 6e739ee04a024..216f36bd6e847 100644 --- a/compiler/rustc_query_impl/src/values.rs +++ b/compiler/rustc_query_impl/src/values.rs @@ -27,12 +27,14 @@ impl<'tcx, T> Value<'tcx> for T { cycle_error: CycleError, _guar: ErrorGuaranteed, ) -> T { - tcx.sess.dcx().abort_if_errors(); - bug!( - "<{} as Value>::from_cycle_error called without errors: {:#?}", - std::any::type_name::(), - cycle_error.cycle, - ); + let Some(guar) = tcx.sess.dcx().has_errors() else { + bug!( + "<{} as Value>::from_cycle_error called without errors: {:#?}", + std::any::type_name::(), + cycle_error.cycle, + ); + }; + guar.raise_fatal(); } } From 19b3617dd418347e09aaee9229018b2a3cf6e249 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 16:39:23 +1100 Subject: [PATCH 13/15] Remove a duplicated comment. This exact comment block also appears in `compiler/rustc_middle/src/queries.rs`, which is a better place for it. --- compiler/rustc_middle/src/query/plumbing.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 13ba20ef7bd56..40a57e990a72e 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -643,18 +643,6 @@ macro_rules! define_callbacks { }; } -// Each of these queries corresponds to a function pointer field in the -// `Providers` struct for requesting a value of that type, and a method -// on `tcx: TyCtxt` (and `tcx.at(span)`) for doing that request in a way -// which memoizes and does dep-graph tracking, wrapping around the actual -// `Providers` that the driver creates (using several `rustc_*` crates). -// -// The result type of each query must implement `Clone`, and additionally -// `ty::query::values::Value`, which produces an appropriate placeholder -// (error) value if the query resulted in a query cycle. -// Queries marked with `cycle_fatal` do not need the latter implementation, -// as they will raise an fatal error on query cycles instead. - mod sealed { use rustc_hir::def_id::{LocalModDefId, ModDefId}; From 65b76a44338fe73f3436f8b61c82a531aad41b4e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 15:11:18 +1100 Subject: [PATCH 14/15] Rename trait `Value` as `FromCycleError`. `Value` is an unhelpfully generic name. Standard naming procedure for a trait with a single method is for the trait name to match the method name, which is what this commit does. Likewise, the enclosing module is renamed from `values` to `from_cycle_error`. Also add a comment about some non-obvious behaviour. --- compiler/rustc_middle/src/queries.rs | 6 ++-- .../src/{values.rs => from_cycle_error.rs} | 28 +++++++++++-------- compiler/rustc_query_impl/src/lib.rs | 4 +-- compiler/rustc_query_impl/src/plumbing.rs | 3 +- 4 files changed, 23 insertions(+), 18 deletions(-) rename compiler/rustc_query_impl/src/{values.rs => from_cycle_error.rs} (93%) diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 0ff38a0f36041..f54b291a2024f 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -150,10 +150,10 @@ use crate::{dep_graph, mir, thir}; // `Providers` that the driver creates (using several `rustc_*` crates). // // The result type of each query must implement `Clone`, and additionally -// `ty::query::values::Value`, which produces an appropriate placeholder -// (error) value if the query resulted in a query cycle. +// `ty::query::from_cycle_error::FromCycleError`, which produces an appropriate +// placeholder (error) value if the query resulted in a query cycle. // Queries marked with `cycle_fatal` do not need the latter implementation, -// as they will raise an fatal error on query cycles instead. +// as they will raise a fatal error on query cycles instead. rustc_queries! { /// Caches the expansion of a derive proc macro, e.g. `#[derive(Serialize)]`. /// The key is: diff --git a/compiler/rustc_query_impl/src/values.rs b/compiler/rustc_query_impl/src/from_cycle_error.rs similarity index 93% rename from compiler/rustc_query_impl/src/values.rs rename to compiler/rustc_query_impl/src/from_cycle_error.rs index 216f36bd6e847..690eb022ea234 100644 --- a/compiler/rustc_query_impl/src/values.rs +++ b/compiler/rustc_query_impl/src/from_cycle_error.rs @@ -17,11 +17,15 @@ use rustc_span::{ErrorGuaranteed, Span}; use crate::job::report_cycle; -pub(crate) trait Value<'tcx>: Sized { +pub(crate) trait FromCycleError<'tcx>: Sized { + /// Try to produce a `Self` value that represents an error form (e.g. `TyKind::Error`). + /// + /// Note: the default impl calls `raise_fatal`, ending compilation immediately! Only a few + /// types override this with a non-fatal impl. fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self; } -impl<'tcx, T> Value<'tcx> for T { +impl<'tcx, T> FromCycleError<'tcx> for T { default fn from_cycle_error( tcx: TyCtxt<'tcx>, cycle_error: CycleError, @@ -29,7 +33,7 @@ impl<'tcx, T> Value<'tcx> for T { ) -> T { let Some(guar) = tcx.sess.dcx().has_errors() else { bug!( - "<{} as Value>::from_cycle_error called without errors: {:#?}", + "<{} as FromCycleError>::from_cycle_error called without errors: {:#?}", std::any::type_name::(), cycle_error.cycle, ); @@ -38,7 +42,7 @@ impl<'tcx, T> Value<'tcx> for T { } } -impl<'tcx> Value<'tcx> for Ty<'_> { +impl<'tcx> FromCycleError<'tcx> for Ty<'_> { fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { // SAFETY: This is never called when `Self` is not `Ty<'tcx>`. // FIXME: Represent the above fact in the trait system somehow. @@ -46,13 +50,13 @@ impl<'tcx> Value<'tcx> for Ty<'_> { } } -impl<'tcx> Value<'tcx> for Result>, CyclePlaceholder> { +impl<'tcx> FromCycleError<'tcx> for Result>, CyclePlaceholder> { fn from_cycle_error(_tcx: TyCtxt<'tcx>, _: CycleError, guar: ErrorGuaranteed) -> Self { Err(CyclePlaceholder(guar)) } } -impl<'tcx> Value<'tcx> for ty::SymbolName<'_> { +impl<'tcx> FromCycleError<'tcx> for ty::SymbolName<'_> { fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, _guar: ErrorGuaranteed) -> Self { // SAFETY: This is never called when `Self` is not `SymbolName<'tcx>`. // FIXME: Represent the above fact in the trait system somehow. @@ -64,7 +68,7 @@ impl<'tcx> Value<'tcx> for ty::SymbolName<'_> { } } -impl<'tcx> Value<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { +impl<'tcx> FromCycleError<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { let err = Ty::new_error(tcx, guar); @@ -94,7 +98,7 @@ impl<'tcx> Value<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { } } -impl<'tcx> Value<'tcx> for Representability { +impl<'tcx> FromCycleError<'tcx> for Representability { fn from_cycle_error( tcx: TyCtxt<'tcx>, cycle_error: CycleError, @@ -130,19 +134,19 @@ impl<'tcx> Value<'tcx> for Representability { } } -impl<'tcx> Value<'tcx> for ty::EarlyBinder<'_, Ty<'_>> { +impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, Ty<'_>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { ty::EarlyBinder::bind(Ty::from_cycle_error(tcx, cycle_error, guar)) } } -impl<'tcx> Value<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> { +impl<'tcx> FromCycleError<'tcx> for ty::EarlyBinder<'_, ty::Binder<'_, ty::FnSig<'_>>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { ty::EarlyBinder::bind(ty::Binder::from_cycle_error(tcx, cycle_error, guar)) } } -impl<'tcx> Value<'tcx> for &[ty::Variance] { +impl<'tcx> FromCycleError<'tcx> for &[ty::Variance] { fn from_cycle_error( tcx: TyCtxt<'tcx>, cycle_error: CycleError, @@ -190,7 +194,7 @@ fn search_for_cycle_permutation( otherwise() } -impl<'tcx, T> Value<'tcx> for Result> { +impl<'tcx, T> FromCycleError<'tcx> for Result> { fn from_cycle_error( tcx: TyCtxt<'tcx>, cycle_error: CycleError, diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 1b93ffe945b3d..8156627e83407 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -18,12 +18,12 @@ use rustc_middle::ty::TyCtxt; use rustc_span::Span; pub use crate::dep_kind_vtables::make_dep_kind_vtables; +use crate::from_cycle_error::FromCycleError; pub use crate::job::{QueryJobMap, break_query_cycles, print_query_stack}; pub use crate::plumbing::{collect_active_jobs_from_all_queries, query_key_hash_verify_all}; use crate::plumbing::{encode_all_query_results, try_mark_green}; use crate::profiling_support::QueryKeyStringCache; pub use crate::profiling_support::alloc_self_profile_query_strings; -use crate::values::Value; #[macro_use] mod plumbing; @@ -31,9 +31,9 @@ mod plumbing; mod dep_kind_vtables; mod error; mod execution; +mod from_cycle_error; mod job; mod profiling_support; -mod values; /// Trait that knows how to look up the [`QueryVTable`] for a particular query. /// diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 8920f8dba38d1..c6668a7ab5aac 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -603,7 +603,8 @@ macro_rules! define_queries { None }), value_from_cycle_error: |tcx, cycle, guar| { - let result: queries::$name::Value<'tcx> = Value::from_cycle_error(tcx, cycle, guar); + let result: queries::$name::Value<'tcx> = + FromCycleError::from_cycle_error(tcx, cycle, guar); erase::erase_val(result) }, hash_result: hash_result!([$($modifiers)*][queries::$name::Value<'tcx>]), From fa5138bb2d82ec26ce46c81504422c046392a1aa Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 27 Feb 2026 16:15:59 +1100 Subject: [PATCH 15/15] Remove `FromCycleError` impl for `SymbolName`. It has no effect. `symbol_name` is the only query that produces a `SymbolName`. If it was marked with `cycle_delayed_bug`/`cycle_stash` then this `FromCycleError` impl would make sense, but that's not the case. Maybe it was the case in the past. --- compiler/rustc_query_impl/src/from_cycle_error.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/rustc_query_impl/src/from_cycle_error.rs b/compiler/rustc_query_impl/src/from_cycle_error.rs index 690eb022ea234..a13db9004d678 100644 --- a/compiler/rustc_query_impl/src/from_cycle_error.rs +++ b/compiler/rustc_query_impl/src/from_cycle_error.rs @@ -56,18 +56,6 @@ impl<'tcx> FromCycleError<'tcx> for Result>, CyclePla } } -impl<'tcx> FromCycleError<'tcx> for ty::SymbolName<'_> { - fn from_cycle_error(tcx: TyCtxt<'tcx>, _: CycleError, _guar: ErrorGuaranteed) -> Self { - // SAFETY: This is never called when `Self` is not `SymbolName<'tcx>`. - // FIXME: Represent the above fact in the trait system somehow. - unsafe { - std::mem::transmute::, ty::SymbolName<'_>>(ty::SymbolName::new( - tcx, "", - )) - } - } -} - impl<'tcx> FromCycleError<'tcx> for ty::Binder<'_, ty::FnSig<'_>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: CycleError, guar: ErrorGuaranteed) -> Self { let err = Ty::new_error(tcx, guar);