From 4508f5f8628c2869f153b59aba6d90411d831e33 Mon Sep 17 00:00:00 2001 From: Mu001999 Date: Sun, 2 Mar 2025 23:21:43 +0800 Subject: [PATCH 01/23] update outdated doc with new example --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index d72cf00293f0c..e81d753f3e7d4 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -218,10 +218,10 @@ fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGua /// definition itself. For example, this definition would be illegal: /// /// ```rust -/// struct Ref<'a, T> { x: &'a T } +/// struct StaticRef { x: &'static T } /// ``` /// -/// because the type did not declare that `T:'a`. +/// because the type did not declare that `T: 'static`. /// /// We do this check as a pre-pass before checking fn bodies because if these constraints are /// not included it frequently leads to confusing errors in fn bodies. So it's better to check From 795a6669f83274ce3ada1046e6c9284b8e900a98 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Tue, 18 Mar 2025 16:32:09 -0400 Subject: [PATCH 02/23] rustc_resolve: fix instability in lib.rmeta contents rust-lang/rust@23032f31c91f2 accidentally introduced some nondeterminism in the ordering of lib.rmeta files, which we caught in our bazel-based builds only recently due to being further behind than normal. In my testing, this fixes the issue. --- Cargo.lock | 1 + compiler/rustc_resolve/Cargo.toml | 1 + compiler/rustc_resolve/src/rustdoc.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 63a3f5dd03773..67ddac6032b44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4316,6 +4316,7 @@ name = "rustc_resolve" version = "0.0.0" dependencies = [ "bitflags", + "itertools", "pulldown-cmark 0.11.3", "rustc_arena", "rustc_ast", diff --git a/compiler/rustc_resolve/Cargo.toml b/compiler/rustc_resolve/Cargo.toml index f4771f1af2cfd..9ea9c58cfd17a 100644 --- a/compiler/rustc_resolve/Cargo.toml +++ b/compiler/rustc_resolve/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start bitflags = "2.4.1" +itertools = "0.12" pulldown-cmark = { version = "0.11", features = ["html"], default-features = false } rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 52aaab77ebed6..9fda1eb4dc453 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,6 +1,7 @@ use std::mem; use std::ops::Range; +use itertools::Itertools; use pulldown_cmark::{ BrokenLink, BrokenLinkCallback, CowStr, Event, LinkType, Options, Parser, Tag, }; @@ -454,7 +455,7 @@ fn parse_links<'md>(doc: &'md str) -> Vec> { } } - for (label, refdef) in event_iter.reference_definitions().iter() { + for (label, refdef) in event_iter.reference_definitions().iter().sorted_by_key(|x| x.0) { if !refids.contains(label) { links.push(preprocess_link(&refdef.dest)); } From 1f1c630a58297d49e92b79cf072559924b4f1123 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 5 Mar 2025 23:33:54 +0100 Subject: [PATCH 03/23] Greatly simplify doctest parsing and information extraction --- src/librustdoc/doctest/make.rs | 459 ++++++++++----------------------- 1 file changed, 136 insertions(+), 323 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 9935074877bc8..cb14608b35aeb 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -5,11 +5,10 @@ use std::fmt::{self, Write as _}; use std::io; use std::sync::Arc; -use rustc_ast as ast; +use rustc_ast::{self as ast, HasAttrs}; +use rustc_errors::ColorConfig; use rustc_errors::emitter::stderr_destination; -use rustc_errors::{ColorConfig, FatalError}; use rustc_parse::new_parser_from_source_str; -use rustc_parse::parser::attr::InnerAttrPolicy; use rustc_session::parse::ParseSess; use rustc_span::FileName; use rustc_span::edition::Edition; @@ -21,6 +20,19 @@ use super::GlobalTestOptions; use crate::display::Joined as _; use crate::html::markdown::LangString; +#[derive(Default)] +struct ParseSourceInfo { + has_main_fn: bool, + found_extern_crate: bool, + supports_color: bool, + has_global_allocator: bool, + has_macro_def: bool, + everything_else: String, + crates: String, + crate_attrs: String, + maybe_crate_attrs: String, +} + /// This struct contains information about the doctest itself which is then used to generate /// doctest source code appropriately. pub(crate) struct DocTestBuilder { @@ -53,8 +65,23 @@ impl DocTestBuilder { !lang_str.compile_fail && !lang_str.test_harness && !lang_str.standalone_crate }); - let Some(SourceInfo { crate_attrs, maybe_crate_attrs, crates, everything_else }) = - partition_source(source, edition) + let result = rustc_driver::catch_fatal_errors(|| { + rustc_span::create_session_if_not_set_then(edition, |_| { + parse_source(source, &crate_name) + }) + }); + + let Ok(Ok(ParseSourceInfo { + has_main_fn, + found_extern_crate, + supports_color, + has_global_allocator, + has_macro_def, + everything_else, + crates, + crate_attrs, + maybe_crate_attrs, + })) = result else { return Self::invalid( String::new(), @@ -65,35 +92,13 @@ impl DocTestBuilder { ); }; - // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern - // crate already is included. - let Ok(( - ParseSourceInfo { - has_main_fn, - found_extern_crate, - supports_color, - has_global_allocator, - has_macro_def, - .. - }, - failed_ast, - )) = check_for_main_and_extern_crate( - crate_name, - source, - &everything_else, - &crates, - edition, - can_merge_doctests, - ) - else { - // If the parser panicked due to a fatal error, pass the test code through unchanged. - // The error will be reported during compilation. - return Self::invalid(crate_attrs, maybe_crate_attrs, crates, everything_else, test_id); - }; + debug!("crate_attrs:\n{crate_attrs}{maybe_crate_attrs}"); + debug!("crates:\n{crates}"); + debug!("after:\n{everything_else}"); + // If the AST returned an error, we don't want this doctest to be merged with the // others. Same if it contains `#[feature]` or `#[no_std]`. let can_be_merged = can_merge_doctests - && !failed_ast && !has_global_allocator && crate_attrs.is_empty() // If this is a merged doctest and a defined macro uses `$crate`, then the path will @@ -146,6 +151,7 @@ impl DocTestBuilder { if self.failed_ast { // If the AST failed to compile, no need to go generate a complete doctest, the error // will be better this way. + debug!("failed AST:\n{test_code}"); return (test_code.to_string(), 0); } let mut line_offset = 0; @@ -255,13 +261,6 @@ impl DocTestBuilder { } } -#[derive(PartialEq, Eq, Debug)] -enum ParsingResult { - Failed, - AstError, - Ok, -} - fn cancel_error_count(psess: &ParseSess) { // Reset errors so that they won't be reported as compiler bugs when dropping the // dcx. Any errors in the tests will be reported when the test file is compiled, @@ -270,17 +269,20 @@ fn cancel_error_count(psess: &ParseSess) { psess.dcx().reset_err_count(); } -fn parse_source( - source: String, - info: &mut ParseSourceInfo, - crate_name: &Option<&str>, -) -> ParsingResult { +const DOCTEST_CODE_WRAPPER: &str = "fn f(){"; + +fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { use rustc_errors::DiagCtxt; use rustc_errors::emitter::{Emitter, HumanEmitter}; - use rustc_parse::parser::ForceCollect; + // use rustc_parse::parser::ForceCollect; use rustc_span::source_map::FilePathMapping; - let filename = FileName::anon_source_code(&source); + let mut info = + ParseSourceInfo { found_extern_crate: crate_name.is_none(), ..Default::default() }; + + let wrapped_source = format!("{DOCTEST_CODE_WRAPPER}{source}\n}}"); + + let filename = FileName::anon_source_code(&wrapped_source); // Any errors in parsing should also appear when the doctest is compiled for real, so just // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. @@ -299,15 +301,32 @@ fn parse_source( let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); let psess = ParseSess::with_dcx(dcx, sm); - let mut parser = match new_parser_from_source_str(&psess, filename, source) { + let mut parser = match new_parser_from_source_str(&psess, filename, wrapped_source) { Ok(p) => p, Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); cancel_error_count(&psess); - return ParsingResult::Failed; + return Err(()); } }; - let mut parsing_result = ParsingResult::Ok; + + fn push_to_s( + s: &mut String, + source: &str, + span: rustc_span::Span, + prev_span_hi: &mut Option, + ) { + let extra_len = DOCTEST_CODE_WRAPPER.len(); + // We need to shift by 1 because we added `{` at the beginning of the source.we provided + // to the parser. + let lo = prev_span_hi.unwrap_or(span.lo().0 as usize - extra_len); + let mut hi = span.hi().0 as usize - extra_len; + if hi > source.len() { + hi = source.len(); + } + s.push_str(&source[lo..hi]); + *prev_span_hi = Some(hi); + } // Recurse through functions body. It is necessary because the doctest source code is // wrapped in a function to limit the number of AST errors. If we don't recurse into @@ -325,6 +344,8 @@ fn parse_source( } match item.kind { ast::ItemKind::Fn(ref fn_item) if !info.has_main_fn => { + // We only push if it's the top item because otherwise, we would duplicate + // its content since the top-level item was already added. if item.ident.name == sym::main && is_top_level { info.has_main_fn = true; } @@ -334,7 +355,6 @@ fn parse_source( ast::StmtKind::Item(ref item) => { check_item(item, info, crate_name, false) } - ast::StmtKind::MacCall(..) => info.found_macro = true, _ => {} } } @@ -350,295 +370,88 @@ fn parse_source( }; } } - ast::ItemKind::MacCall(..) => info.found_macro = true, - ast::ItemKind::MacroDef(..) => info.has_macro_def = true, - _ => {} - } - } - - loop { - match parser.parse_item(ForceCollect::No) { - Ok(Some(item)) => { - check_item(&item, info, crate_name, true); - - if info.has_main_fn && info.found_extern_crate { - break; - } - } - Ok(None) => break, - Err(e) => { - parsing_result = ParsingResult::AstError; - e.cancel(); - break; + ast::ItemKind::MacroDef(..) => { + info.has_macro_def = true; } + _ => {} } - - // The supplied item is only used for diagnostics, - // which are swallowed here anyway. - parser.maybe_consume_incorrect_semicolon(None); - } - - cancel_error_count(&psess); - parsing_result -} - -#[derive(Default)] -struct ParseSourceInfo { - has_main_fn: bool, - found_extern_crate: bool, - found_macro: bool, - supports_color: bool, - has_global_allocator: bool, - has_macro_def: bool, -} - -fn check_for_main_and_extern_crate( - crate_name: Option<&str>, - original_source_code: &str, - everything_else: &str, - crates: &str, - edition: Edition, - can_merge_doctests: bool, -) -> Result<(ParseSourceInfo, bool), FatalError> { - let result = rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_if_not_set_then(edition, |_| { - let mut info = - ParseSourceInfo { found_extern_crate: crate_name.is_none(), ..Default::default() }; - - let mut parsing_result = - parse_source(format!("{crates}{everything_else}"), &mut info, &crate_name); - // No need to double-check this if the "merged doctests" feature isn't enabled (so - // before the 2024 edition). - if can_merge_doctests && parsing_result != ParsingResult::Ok { - // If we found an AST error, we want to ensure it's because of an expression being - // used outside of a function. - // - // To do so, we wrap in a function in order to make sure that the doctest AST is - // correct. For example, if your doctest is `foo::bar()`, if we don't wrap it in a - // block, it would emit an AST error, which would be problematic for us since we - // want to filter out such errors which aren't "real" errors. - // - // The end goal is to be able to merge as many doctests as possible as one for much - // faster doctests run time. - parsing_result = parse_source( - format!("{crates}\nfn __doctest_wrap(){{{everything_else}\n}}"), - &mut info, - &crate_name, - ); - } - - (info, parsing_result) - }) - }); - let (mut info, parsing_result) = match result { - Err(..) | Ok((_, ParsingResult::Failed)) => return Err(FatalError), - Ok((info, parsing_result)) => (info, parsing_result), - }; - - // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't - // see it. In that case, run the old text-based scan to see if they at least have a main - // function written inside a macro invocation. See - // https://github.com/rust-lang/rust/issues/56898 - if info.found_macro - && !info.has_main_fn - && original_source_code - .lines() - .map(|line| { - let comment = line.find("//"); - if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line } - }) - .any(|code| code.contains("fn main")) - { - info.has_main_fn = true; } - Ok((info, parsing_result != ParsingResult::Ok)) -} - -enum AttrKind { - CrateAttr, - Attr, -} - -/// Returns `Some` if the attribute is complete and `Some(true)` if it is an attribute that can be -/// placed at the crate root. -fn check_if_attr_is_complete(source: &str, edition: Edition) -> Option { - if source.is_empty() { - // Empty content so nothing to check in here... - return None; - } + let mut prev_span_hi = None; let not_crate_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny]; + let parsed = parser.parse_item(rustc_parse::parser::ForceCollect::No); - rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_if_not_set_then(edition, |_| { - use rustc_errors::DiagCtxt; - use rustc_errors::emitter::HumanEmitter; - use rustc_span::source_map::FilePathMapping; - - let filename = FileName::anon_source_code(source); - // Any errors in parsing should also appear when the doctest is compiled for real, so just - // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr. - let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); - let fallback_bundle = rustc_errors::fallback_fluent_bundle( - rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), - false, - ); + debug!("+++++> {parsed:#?}"); - let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle); - - let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); - let psess = ParseSess::with_dcx(dcx, sm); - let mut parser = match new_parser_from_source_str(&psess, filename, source.to_owned()) { - Ok(p) => p, - Err(errs) => { - errs.into_iter().for_each(|err| err.cancel()); - // If there is an unclosed delimiter, an error will be returned by the - // tokentrees. - return None; - } - }; - // If a parsing error happened, it's very likely that the attribute is incomplete. - let ret = match parser.parse_attribute(InnerAttrPolicy::Permitted) { - Ok(attr) => { - let attr_name = attr.name_or_empty(); - - if not_crate_attrs.contains(&attr_name) { - // There is one exception to these attributes: - // `#![allow(internal_features)]`. If this attribute is used, we need to - // consider it only as a crate-level attribute. - if attr_name == sym::allow - && let Some(list) = attr.meta_item_list() - && list.iter().any(|sub_attr| { - sub_attr.name_or_empty().as_str() == "internal_features" - }) - { - Some(AttrKind::CrateAttr) - } else { - Some(AttrKind::Attr) - } + let result = match parsed { + Ok(Some(ref item)) + if let ast::ItemKind::Fn(ref fn_item) = item.kind + && let Some(ref body) = fn_item.body => + { + for attr in &item.attrs { + let attr_name = attr.name_or_empty(); + + if attr.style == ast::AttrStyle::Outer || not_crate_attrs.contains(&attr_name) { + // There is one exception to these attributes: + // `#![allow(internal_features)]`. If this attribute is used, we need to + // consider it only as a crate-level attribute. + if attr_name == sym::allow + && let Some(list) = attr.meta_item_list() + && list.iter().any(|sub_attr| { + sub_attr.name_or_empty().as_str() == "internal_features" + }) + { + push_to_s(&mut info.crate_attrs, source, attr.span, &mut prev_span_hi); } else { - Some(AttrKind::CrateAttr) + push_to_s( + &mut info.maybe_crate_attrs, + source, + attr.span, + &mut prev_span_hi, + ); } + } else { + push_to_s(&mut info.crate_attrs, source, attr.span, &mut prev_span_hi); } - Err(e) => { - e.cancel(); - None + } + for stmt in &body.stmts { + match stmt.kind { + ast::StmtKind::Item(ref item) => check_item(&item, &mut info, crate_name, true), + ast::StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { + cancel_error_count(&psess); + return Err(()); + } + _ => {} } - }; - ret - }) - }) - .unwrap_or(None) -} -fn handle_attr(mod_attr_pending: &mut String, source_info: &mut SourceInfo, edition: Edition) { - if let Some(attr_kind) = check_if_attr_is_complete(mod_attr_pending, edition) { - let push_to = match attr_kind { - AttrKind::CrateAttr => &mut source_info.crate_attrs, - AttrKind::Attr => &mut source_info.maybe_crate_attrs, - }; - push_to.push_str(mod_attr_pending); - push_to.push('\n'); - // If it's complete, then we can clear the pending content. - mod_attr_pending.clear(); - } else { - mod_attr_pending.push('\n'); - } -} - -#[derive(Default)] -struct SourceInfo { - crate_attrs: String, - maybe_crate_attrs: String, - crates: String, - everything_else: String, -} - -fn partition_source(s: &str, edition: Edition) -> Option { - #[derive(Copy, Clone, PartialEq)] - enum PartitionState { - Attrs, - Crates, - Other, - } - let mut source_info = SourceInfo::default(); - let mut state = PartitionState::Attrs; - let mut mod_attr_pending = String::new(); - - for line in s.lines() { - let trimline = line.trim(); - - // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be - // shunted into "everything else" - match state { - PartitionState::Attrs => { - state = if trimline.starts_with("#![") { - mod_attr_pending = line.to_owned(); - handle_attr(&mut mod_attr_pending, &mut source_info, edition); - continue; - } else if trimline.chars().all(|c| c.is_whitespace()) - || (trimline.starts_with("//") && !trimline.starts_with("///")) - { - PartitionState::Attrs - } else if trimline.starts_with("extern crate") - || trimline.starts_with("#[macro_use] extern crate") - { - PartitionState::Crates - } else { - // First we check if the previous attribute was "complete"... - if !mod_attr_pending.is_empty() { - // If not, then we append the new line into the pending attribute to check - // if this time it's complete... - mod_attr_pending.push_str(line); - if !trimline.is_empty() { - handle_attr(&mut mod_attr_pending, &mut source_info, edition); - } - continue; - } else { - PartitionState::Other - } - }; - } - PartitionState::Crates => { - state = if trimline.starts_with("extern crate") - || trimline.starts_with("#[macro_use] extern crate") - || trimline.chars().all(|c| c.is_whitespace()) - || (trimline.starts_with("//") && !trimline.starts_with("///")) + // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to + // tweak the span to include the attributes as well. + let mut span = stmt.span; + if let Some(attr) = stmt.kind.attrs().first() { + span = span.with_lo(attr.span.lo()); + } + if info.everything_else.is_empty() + && (!info.maybe_crate_attrs.is_empty() || !info.crate_attrs.is_empty()) { - PartitionState::Crates - } else { - PartitionState::Other - }; + // We add potential backlines into attributes if there are some. + push_to_s( + &mut info.maybe_crate_attrs, + source, + span.shrink_to_lo(), + &mut prev_span_hi, + ); + } + push_to_s(&mut info.everything_else, source, span, &mut prev_span_hi); } - PartitionState::Other => {} + Ok(info) } - - match state { - PartitionState::Attrs => { - source_info.crate_attrs.push_str(line); - source_info.crate_attrs.push('\n'); - } - PartitionState::Crates => { - source_info.crates.push_str(line); - source_info.crates.push('\n'); - } - PartitionState::Other => { - source_info.everything_else.push_str(line); - source_info.everything_else.push('\n'); - } + Err(e) => { + e.cancel(); + Err(()) } - } - - if !mod_attr_pending.is_empty() { - debug!("invalid doctest code: {s:?}"); - return None; - } - - source_info.everything_else = source_info.everything_else.trim().to_string(); - - debug!("crate_attrs:\n{}{}", source_info.crate_attrs, source_info.maybe_crate_attrs); - debug!("crates:\n{}", source_info.crates); - debug!("after:\n{}", source_info.everything_else); + _ => Err(()), + }; - Some(source_info) + cancel_error_count(&psess); + result } From 01fb7c3ac6223ca5657987e73401f7eb64609e01 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 6 Mar 2025 16:44:59 +0100 Subject: [PATCH 04/23] Update rustdoc-ui tests --- tests/rustdoc-ui/extract-doctests.stdout | 2 +- tests/rustdoc-ui/remap-path-prefix-invalid-doctest.stdout | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/rustdoc-ui/extract-doctests.stdout b/tests/rustdoc-ui/extract-doctests.stdout index fa8604cae948a..b11531b844ee4 100644 --- a/tests/rustdoc-ui/extract-doctests.stdout +++ b/tests/rustdoc-ui/extract-doctests.stdout @@ -1 +1 @@ -{"format_version":1,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":"#![allow(unused)]\nfn main() {\nlet x = 12;\nlet y = 14;\n}","name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":"#![allow(unused)]\nfn main() {\nlet\n}","name":"$DIR/extract-doctests.rs - (line 13)"}]} \ No newline at end of file +{"format_version":1,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":"#![allow(unused)]\nfn main() {\nlet x = 12;\nlet y = 14;\n}","name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":null,"name":"$DIR/extract-doctests.rs - (line 13)"}]} \ No newline at end of file diff --git a/tests/rustdoc-ui/remap-path-prefix-invalid-doctest.stdout b/tests/rustdoc-ui/remap-path-prefix-invalid-doctest.stdout index a05b51699894b..c0d2515998f23 100644 --- a/tests/rustdoc-ui/remap-path-prefix-invalid-doctest.stdout +++ b/tests/rustdoc-ui/remap-path-prefix-invalid-doctest.stdout @@ -5,11 +5,11 @@ test remapped_path/remap-path-prefix-invalid-doctest.rs - SomeStruct (line 10) . failures: ---- remapped_path/remap-path-prefix-invalid-doctest.rs - SomeStruct (line 10) stdout ---- -error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `is` +error: expected one of `!` or `::`, found `is` --> remapped_path/remap-path-prefix-invalid-doctest.rs:11:6 | LL | this is not real code - | ^^ expected one of 8 possible tokens + | ^^ expected one of `!` or `::` error: aborting due to 1 previous error From 6f7e8d441a81ed89e14ad5ce53dcbe52ab0af64c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 7 Mar 2025 13:47:21 +0100 Subject: [PATCH 05/23] Correctly handle `fn main` in macro --- src/librustdoc/doctest/make.rs | 43 +++++++++++++++++++++++---------- src/librustdoc/doctest/tests.rs | 10 ++++---- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index cb14608b35aeb..810f53636ce41 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -5,15 +5,17 @@ use std::fmt::{self, Write as _}; use std::io; use std::sync::Arc; -use rustc_ast::{self as ast, HasAttrs}; +use rustc_ast::token::{Delimiter, TokenKind}; +use rustc_ast::tokenstream::TokenTree; +use rustc_ast::{self as ast, HasAttrs, StmtKind}; use rustc_errors::ColorConfig; use rustc_errors::emitter::stderr_destination; use rustc_parse::new_parser_from_source_str; use rustc_session::parse::ParseSess; -use rustc_span::FileName; use rustc_span::edition::Edition; use rustc_span::source_map::SourceMap; use rustc_span::symbol::sym; +use rustc_span::{FileName, kw}; use tracing::debug; use super::GlobalTestOptions; @@ -319,7 +321,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result source.len() { hi = source.len(); @@ -351,11 +353,8 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - check_item(item, info, crate_name, false) - } - _ => {} + if let StmtKind::Item(ref item) = stmt.kind { + check_item(item, info, crate_name, false) } } } @@ -381,8 +380,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result {parsed:#?}"); - let result = match parsed { Ok(Some(ref item)) if let ast::ItemKind::Fn(ref fn_item) = item.kind @@ -416,11 +413,31 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result check_item(&item, &mut info, crate_name, true), - ast::StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { + StmtKind::Item(ref item) => check_item(&item, &mut info, crate_name, true), + StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { cancel_error_count(&psess); return Err(()); } + StmtKind::MacCall(ref mac_call) if !info.has_main_fn => { + let mut iter = mac_call.mac.args.tokens.iter(); + + while let Some(token) = iter.next() { + if let TokenTree::Token(token, _) = token + && let TokenKind::Ident(name, _) = token.kind + && name == kw::Fn + && let Some(TokenTree::Token(fn_token, _)) = iter.peek() + && let TokenKind::Ident(fn_name, _) = fn_token.kind + && fn_name == sym::main + && let Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _)) = { + iter.next(); + iter.peek() + } + { + info.has_main_fn = true; + break; + } + } + } _ => {} } @@ -433,7 +450,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result Date: Fri, 7 Mar 2025 15:20:14 +0100 Subject: [PATCH 06/23] Correctly handle line comments in attributes and generate extern crates outside of wrapping function --- src/librustdoc/doctest/make.rs | 50 +++++++++++++------ src/librustdoc/doctest/tests.rs | 30 +++++++++-- tests/run-make/rustdoc-error-lines/rmake.rs | 6 +-- .../rustdoc-ui/doctest/display-output.stdout | 6 +-- tests/rustdoc-ui/doctest/extern-crate.rs | 23 +++++++++ tests/rustdoc-ui/doctest/extern-crate.stdout | 6 +++ tests/rustdoc/playground.rs | 2 +- 7 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/extern-crate.rs create mode 100644 tests/rustdoc-ui/doctest/extern-crate.stdout diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 810f53636ce41..0ed2d37f74c18 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -176,9 +176,24 @@ impl DocTestBuilder { // Now push any outer attributes from the example, assuming they // are intended to be crate attributes. - prog.push_str(&self.crate_attrs); - prog.push_str(&self.maybe_crate_attrs); - prog.push_str(&self.crates); + if !self.crate_attrs.is_empty() { + prog.push_str(&self.crate_attrs); + if !self.crate_attrs.ends_with('\n') { + prog.push('\n'); + } + } + if !self.maybe_crate_attrs.is_empty() { + prog.push_str(&self.maybe_crate_attrs); + if !self.maybe_crate_attrs.ends_with('\n') { + prog.push('\n'); + } + } + if !self.crates.is_empty() { + prog.push_str(&self.crates); + if !self.crates.ends_with('\n') { + prog.push('\n'); + } + } // Don't inject `extern crate std` because it's already injected by the // compiler. @@ -276,7 +291,6 @@ const DOCTEST_CODE_WRAPPER: &str = "fn f(){"; fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { use rustc_errors::DiagCtxt; use rustc_errors::emitter::{Emitter, HumanEmitter}; - // use rustc_parse::parser::ForceCollect; use rustc_span::source_map::FilePathMapping; let mut info = @@ -338,7 +352,8 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result, is_top_level: bool, - ) { + ) -> bool { + let mut is_crate = false; if !info.has_global_allocator && item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator) { @@ -354,12 +369,13 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { + is_crate = true; if !info.found_extern_crate && let Some(crate_name) = crate_name { @@ -374,6 +390,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result {} } + is_crate } let mut prev_span_hi = None; @@ -412,8 +429,11 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result check_item(&item, &mut info, crate_name, true), + StmtKind::Item(ref item) => { + is_crate = check_item(&item, &mut info, crate_name, true); + } StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { cancel_error_count(&psess); return Err(()); @@ -450,15 +470,15 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result $DIR/display-output.rs:11:5 + --> $DIR/display-output.rs:12:5 | LL | let x = 12; | ^ help: if this is intentional, prefix it with an underscore: `_x` @@ -19,13 +19,13 @@ LL | #![warn(unused)] = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: unused variable: `x` - --> $DIR/display-output.rs:13:8 + --> $DIR/display-output.rs:14:8 | LL | fn foo(x: &dyn std::fmt::Display) {} | ^ help: if this is intentional, prefix it with an underscore: `_x` warning: function `foo` is never used - --> $DIR/display-output.rs:13:4 + --> $DIR/display-output.rs:14:4 | LL | fn foo(x: &dyn std::fmt::Display) {} | ^^^ diff --git a/tests/rustdoc-ui/doctest/extern-crate.rs b/tests/rustdoc-ui/doctest/extern-crate.rs new file mode 100644 index 0000000000000..0415d33bb723b --- /dev/null +++ b/tests/rustdoc-ui/doctest/extern-crate.rs @@ -0,0 +1,23 @@ +//@ check-pass +//@ compile-flags:--test --test-args=--test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" + +// This test ensures that crate imports are placed outside of the `main` function +// so they work all the time (even in 2015 edition). + +/// ```rust +/// #![feature(test)] +/// +/// extern crate test; +/// use test::Bencher; +/// +/// #[bench] +/// fn bench_xor_1000_ints(b: &mut Bencher) { +/// b.iter(|| { +/// (0..1000).fold(0, |old, new| old ^ new); +/// }); +/// } +/// ``` +/// +pub fn foo() {} diff --git a/tests/rustdoc-ui/doctest/extern-crate.stdout b/tests/rustdoc-ui/doctest/extern-crate.stdout new file mode 100644 index 0000000000000..b103343afdd57 --- /dev/null +++ b/tests/rustdoc-ui/doctest/extern-crate.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/extern-crate.rs - foo (line 9) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc/playground.rs b/tests/rustdoc/playground.rs index db2d1669df604..65dad2a5195d6 100644 --- a/tests/rustdoc/playground.rs +++ b/tests/rustdoc/playground.rs @@ -24,4 +24,4 @@ //@ matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0Afn+main()+%7B%0A++++println!(%22Hello,+world!%22);%0A%7D&edition=2015"]' "" //@ matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0Afn+main()+%7B%0A++++println!(%22Hello,+world!%22);%0A%7D&edition=2015"]' "" -//@ matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0A%23!%5Bfeature(something)%5D%0A%0Afn+main()+%7B%0A++++println!(%22Hello,+world!%22);%0A%7D&version=nightly&edition=2015"]' "" +//@ matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0A%23!%5Bfeature(something)%5D%0A%0A%0Afn+main()+%7B%0A++++println!(%22Hello,+world!%22);%0A%7D&version=nightly&edition=2015"]' "" From be13105d737545a4a5f0aa0709342b4db8f2f1e3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 12:55:30 +0100 Subject: [PATCH 07/23] Improve comment and test for generated doctest with code comments --- src/librustdoc/doctest/make.rs | 5 +++-- src/librustdoc/doctest/tests.rs | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 0ed2d37f74c18..512343693bc28 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -85,6 +85,8 @@ impl DocTestBuilder { maybe_crate_attrs, })) = result else { + // If the AST returned an error, we don't want this doctest to be merged with the + // others. return Self::invalid( String::new(), String::new(), @@ -98,8 +100,7 @@ impl DocTestBuilder { debug!("crates:\n{crates}"); debug!("after:\n{everything_else}"); - // If the AST returned an error, we don't want this doctest to be merged with the - // others. Same if it contains `#[feature]` or `#[no_std]`. + // If it contains `#[feature]` or `#[no_std]`, we don't want it to be merged either. let can_be_merged = can_merge_doctests && !has_global_allocator && crate_attrs.is_empty() diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs index 59cc33558ed50..4833ea0405154 100644 --- a/src/librustdoc/doctest/tests.rs +++ b/src/librustdoc/doctest/tests.rs @@ -405,7 +405,9 @@ fn check_split_args() { #[test] fn comment_in_attrs() { - // if input already has a fn main, it should insert a space before it + // If there is an inline code comment after attributes, we need to ensure that + // a backline will be added to prevent generating code "inside" it (and thus generating) + // invalid code. let opts = default_global_opts(""); let input = "\ #![feature(rustdoc_internals)] @@ -424,4 +426,24 @@ fn main() { .to_string(); let (output, len) = make_test(input, None, false, &opts, None); assert_eq!((output, len), (expected, 2)); + + // And same, if there is a `main` function provided by the user, we ensure that it's + // correctly separated. + let input = "\ +#![feature(rustdoc_internals)] +#![allow(internal_features)] +#![doc(rust_logo)] +//! This crate has the Rust(tm) branding on it. +fn main() {}"; + let expected = "\ +#![allow(unused)] +#![feature(rustdoc_internals)] +#![allow(internal_features)] +#![doc(rust_logo)] +//! This crate has the Rust(tm) branding on it. + +fn main() {}" + .to_string(); + let (output, len) = make_test(input, None, false, &opts, None); + assert_eq!((output, len), (expected, 1)); } From f333ad8656af7a43559805a9a321da8cf618d0c1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 13:00:49 +0100 Subject: [PATCH 08/23] Improve code comment --- src/librustdoc/doctest/make.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 512343693bc28..46226a8bc7174 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -334,8 +334,8 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result, ) { let extra_len = DOCTEST_CODE_WRAPPER.len(); - // We need to shift by 1 because we added `{` at the beginning of the source.we provided - // to the parser. + // We need to shift by the length of `DOCTEST_CODE_WRAPPER` because we + // added it at the beginning of the source we provided to the parser. let lo = prev_span_hi.unwrap_or(0); let mut hi = span.hi().0 as usize - extra_len; if hi > source.len() { From 8327bb6ad3c6a6975ed7a0a408614b8d90c8d0ad Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 13:01:25 +0100 Subject: [PATCH 09/23] Add `expect` to the list of non-crate attributes for doctest generation --- src/librustdoc/doctest/make.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 46226a8bc7174..abb626025a8c8 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -395,7 +395,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result Date: Thu, 27 Mar 2025 14:32:29 +0100 Subject: [PATCH 10/23] Improve code --- src/librustdoc/doctest/make.rs | 63 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index abb626025a8c8..93be352000607 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -25,7 +25,7 @@ use crate::html::markdown::LangString; #[derive(Default)] struct ParseSourceInfo { has_main_fn: bool, - found_extern_crate: bool, + already_has_extern_crate: bool, supports_color: bool, has_global_allocator: bool, has_macro_def: bool, @@ -48,7 +48,7 @@ pub(crate) struct DocTestBuilder { pub(crate) crates: String, pub(crate) everything_else: String, pub(crate) test_id: Option, - pub(crate) failed_ast: bool, + pub(crate) invalid_ast: bool, pub(crate) can_be_merged: bool, } @@ -75,7 +75,7 @@ impl DocTestBuilder { let Ok(Ok(ParseSourceInfo { has_main_fn, - found_extern_crate, + already_has_extern_crate, supports_color, has_global_allocator, has_macro_def, @@ -114,9 +114,9 @@ impl DocTestBuilder { maybe_crate_attrs, crates, everything_else, - already_has_extern_crate: found_extern_crate, + already_has_extern_crate, test_id, - failed_ast: false, + invalid_ast: false, can_be_merged, } } @@ -137,7 +137,7 @@ impl DocTestBuilder { everything_else, already_has_extern_crate: false, test_id, - failed_ast: true, + invalid_ast: true, can_be_merged: false, } } @@ -151,10 +151,10 @@ impl DocTestBuilder { opts: &GlobalTestOptions, crate_name: Option<&str>, ) -> (String, usize) { - if self.failed_ast { + if self.invalid_ast { // If the AST failed to compile, no need to go generate a complete doctest, the error // will be better this way. - debug!("failed AST:\n{test_code}"); + debug!("invalid AST:\n{test_code}"); return (test_code.to_string(), 0); } let mut line_offset = 0; @@ -279,7 +279,7 @@ impl DocTestBuilder { } } -fn cancel_error_count(psess: &ParseSess) { +fn reset_error_count(psess: &ParseSess) { // Reset errors so that they won't be reported as compiler bugs when dropping the // dcx. Any errors in the tests will be reported when the test file is compiled, // Note that we still need to cancel the errors above otherwise `Diag` will panic on @@ -295,7 +295,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result) -> Result p, Err(errs) => { errs.into_iter().for_each(|err| err.cancel()); - cancel_error_count(&psess); + reset_error_count(&psess); return Err(()); } }; - fn push_to_s( - s: &mut String, - source: &str, - span: rustc_span::Span, - prev_span_hi: &mut Option, - ) { + fn push_to_s(s: &mut String, source: &str, span: rustc_span::Span, prev_span_hi: &mut usize) { let extra_len = DOCTEST_CODE_WRAPPER.len(); // We need to shift by the length of `DOCTEST_CODE_WRAPPER` because we // added it at the beginning of the source we provided to the parser. - let lo = prev_span_hi.unwrap_or(0); let mut hi = span.hi().0 as usize - extra_len; if hi > source.len() { hi = source.len(); } - s.push_str(&source[lo..hi]); - *prev_span_hi = Some(hi); + s.push_str(&source[*prev_span_hi..hi]); + *prev_span_hi = hi; } // Recurse through functions body. It is necessary because the doctest source code is @@ -354,7 +348,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result, is_top_level: bool, ) -> bool { - let mut is_crate = false; + let mut is_extern_crate = false; if !info.has_global_allocator && item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator) { @@ -370,17 +364,17 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - is_crate = true; - if !info.found_extern_crate + is_extern_crate = true; + if !info.already_has_extern_crate && let Some(crate_name) = crate_name { - info.found_extern_crate = match original { + info.already_has_extern_crate = match original { Some(name) => name.as_str() == *crate_name, None => item.ident.as_str() == *crate_name, }; @@ -391,10 +385,10 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result {} } - is_crate + is_extern_crate } - let mut prev_span_hi = None; + let mut prev_span_hi = 0; let not_crate_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect]; let parsed = parser.parse_item(rustc_parse::parser::ForceCollect::No); @@ -430,13 +424,13 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - is_crate = check_item(&item, &mut info, crate_name, true); + is_extern_crate = check_item(&item, &mut info, crate_name, true); } StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { - cancel_error_count(&psess); + reset_error_count(&psess); return Err(()); } StmtKind::MacCall(ref mac_call) if !info.has_main_fn => { @@ -471,11 +465,12 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result) -> Result Err(()), }; - cancel_error_count(&psess); + reset_error_count(&psess); result } From 1494da4ffbfab8d0b7a62dd37d9f4a77f1038840 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 16:23:41 +0100 Subject: [PATCH 11/23] Add new regression test for doctest --- src/librustdoc/doctest/tests.rs | 131 ++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs index 4833ea0405154..949ed837db3af 100644 --- a/src/librustdoc/doctest/tests.rs +++ b/src/librustdoc/doctest/tests.rs @@ -447,3 +447,134 @@ fn main() {}" let (output, len) = make_test(input, None, false, &opts, None); assert_eq!((output, len), (expected, 1)); } + +#[test] +fn comments() { + let opts = default_global_opts(""); + let input = r##" +//! A doc comment that applies to the implicit anonymous module of this crate + +pub mod outer_module { + + //! - Inner line doc + //!! - Still an inner line doc (but with a bang at the beginning) + + /*! - Inner block doc */ + /*!! - Still an inner block doc (but with a bang at the beginning) */ + + // - Only a comment + /// - Outer line doc (exactly 3 slashes) + //// - Only a comment + + /* - Only a comment */ + /** - Outer block doc (exactly) 2 asterisks */ + /*** - Only a comment */ + + pub mod inner_module {} + + pub mod nested_comments { + /* In Rust /* we can /* nest comments */ */ */ + + // All three types of block comments can contain or be nested inside + // any other type: + + /* /* */ /** */ /*! */ */ + /*! /* */ /** */ /*! */ */ + /** /* */ /** */ /*! */ */ + pub mod dummy_item {} + } + + pub mod degenerate_cases { + // empty inner line doc + //! + + // empty inner block doc + /*!*/ + + // empty line comment + // + + // empty outer line doc + /// + + // empty block comment + /**/ + + pub mod dummy_item {} + + // empty 2-asterisk block isn't a doc block, it is a block comment + /***/ + + } + + /* The next one isn't allowed because outer doc comments + require an item that will receive the doc */ + + /// Where is my item? +} +"##; + let expected = " +//! A doc comment that applies to the implicit anonymous module of this crate + +pub mod outer_module { + + //! - Inner line doc + //!! - Still an inner line doc (but with a bang at the beginning) + + /*! - Inner block doc */ + /*!! - Still an inner block doc (but with a bang at the beginning) */ + + // - Only a comment + /// - Outer line doc (exactly 3 slashes) + //// - Only a comment + + /* - Only a comment */ + /** - Outer block doc (exactly) 2 asterisks */ + /*** - Only a comment */ + + pub mod inner_module {} + + pub mod nested_comments { + /* In Rust /* we can /* nest comments */ */ */ + + // All three types of block comments can contain or be nested inside + // any other type: + + /* /* */ /** */ /*! */ */ + /*! /* */ /** */ /*! */ */ + /** /* */ /** */ /*! */ */ + pub mod dummy_item {} + } + + pub mod degenerate_cases { + // empty inner line doc + //! + + // empty inner block doc + /*!*/ + + // empty line comment + // + + // empty outer line doc + /// + + // empty block comment + /**/ + + pub mod dummy_item {} + + // empty 2-asterisk block isn't a doc block, it is a block comment + /***/ + + } + + /* The next one isn't allowed because outer doc comments + require an item that will receive the doc */ + + /// Where is my item? +} +".to_string(); + let (output, len) = make_test(input, None, false, &opts, None); + assert_eq!((output, len), (expected, 0)); +} \ No newline at end of file From 49f1e9cd8d31b65416408f4975feb5eded4d6158 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 14:43:11 +0100 Subject: [PATCH 12/23] Remove recursion in `check_item` --- src/librustdoc/doctest/make.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 93be352000607..55b57292b33a2 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -342,12 +342,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result, - is_top_level: bool, - ) -> bool { + fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) -> bool { let mut is_extern_crate = false; if !info.has_global_allocator && item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator) @@ -355,19 +350,12 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { + ast::ItemKind::Fn(_) if !info.has_main_fn => { // We only push if it's the top item because otherwise, we would duplicate // its content since the top-level item was already added. - if item.ident.name == sym::main && is_top_level { + if item.ident.name == sym::main { info.has_main_fn = true; } - if let Some(ref body) = fn_item.body { - for stmt in &body.stmts { - if let StmtKind::Item(ref item) = stmt.kind { - is_extern_crate |= check_item(item, info, crate_name, false); - } - } - } } ast::ItemKind::ExternCrate(original) => { is_extern_crate = true; @@ -427,7 +415,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - is_extern_crate = check_item(&item, &mut info, crate_name, true); + is_extern_crate = check_item(&item, &mut info, crate_name); } StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => { reset_error_count(&psess); From 77eb97d9a983f213cff1df7e02a4a96273653482 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Thu, 27 Mar 2025 11:59:04 -0400 Subject: [PATCH 13/23] librustdoc: also stabilize iteration order here --- src/librustdoc/html/markdown.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index b067dbf750e7d..7606128e9e888 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1726,6 +1726,7 @@ pub(crate) fn markdown_links<'md, R>( md: &'md str, preprocess_link: impl Fn(MarkdownLink) -> Option, ) -> Vec { + use itertools::Itertools; if md.is_empty() { return vec![]; } @@ -1884,7 +1885,7 @@ pub(crate) fn markdown_links<'md, R>( let mut links = Vec::new(); let mut refdefs = FxIndexMap::default(); - for (label, refdef) in event_iter.reference_definitions().iter() { + for (label, refdef) in event_iter.reference_definitions().iter().sorted_by_key(|x| x.0) { refdefs.insert(label.to_string(), (false, refdef.dest.to_string(), refdef.span.clone())); } From eaa0613e8d767ca7b6527a79e9bdf90e84bb17fa Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 26 Mar 2025 18:12:21 +0530 Subject: [PATCH 14/23] feat(config): Add ChangeId enum for suppressing warnings Introduces the `ChangeId` enum to allow suppressing `change_id` warnings. Now, `ChangeId` supports both numeric values and the string literal `"ignore"`. Numeric values behave as expected, while `"ignore"` is used to suppress warning messages. --- src/bootstrap/src/bin/main.rs | 80 +++++++++++++------------ src/bootstrap/src/core/config/config.rs | 31 ++++++++-- src/bootstrap/src/core/config/tests.rs | 5 +- src/bootstrap/src/lib.rs | 2 +- 4 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 443131dc7f3c4..5df9902a9778c 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -11,8 +11,8 @@ use std::str::FromStr; use std::{env, process}; use bootstrap::{ - Build, CONFIG_CHANGE_HISTORY, Config, Flags, Subcommand, debug, find_recent_config_change_ids, - human_readable_changes, t, + Build, CONFIG_CHANGE_HISTORY, ChangeId, Config, Flags, Subcommand, debug, + find_recent_config_change_ids, human_readable_changes, t, }; #[cfg(feature = "tracing")] use tracing::instrument; @@ -155,50 +155,52 @@ fn check_version(config: &Config) -> Option { let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id; let warned_id_path = config.out.join("bootstrap").join(".last-warned-change-id"); - if let Some(mut id) = config.change_id { - if id == latest_change_id { - return None; + let mut id = match config.change_id { + Some(ChangeId::Id(id)) if id == latest_change_id => return None, + Some(ChangeId::Ignore) => return None, + Some(ChangeId::Id(id)) => id, + None => { + msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n"); + msg.push_str("NOTE: to silence this warning, "); + msg.push_str(&format!( + "add `change-id = {latest_change_id}` at the top of `bootstrap.toml`" + )); + return Some(msg); } + }; - // Always try to use `change-id` from .last-warned-change-id first. If it doesn't exist, - // then use the one from the bootstrap.toml. This way we never show the same warnings - // more than once. - if let Ok(t) = fs::read_to_string(&warned_id_path) { - let last_warned_id = usize::from_str(&t) - .unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display())); - - // We only use the last_warned_id if it exists in `CONFIG_CHANGE_HISTORY`. - // Otherwise, we may retrieve all the changes if it's not the highest value. - // For better understanding, refer to `change_tracker::find_recent_config_change_ids`. - if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) { - id = last_warned_id; - } - }; + // Always try to use `change-id` from .last-warned-change-id first. If it doesn't exist, + // then use the one from the bootstrap.toml. This way we never show the same warnings + // more than once. + if let Ok(t) = fs::read_to_string(&warned_id_path) { + let last_warned_id = usize::from_str(&t) + .unwrap_or_else(|_| panic!("{} is corrupted.", warned_id_path.display())); + + // We only use the last_warned_id if it exists in `CONFIG_CHANGE_HISTORY`. + // Otherwise, we may retrieve all the changes if it's not the highest value. + // For better understanding, refer to `change_tracker::find_recent_config_change_ids`. + if CONFIG_CHANGE_HISTORY.iter().any(|config| config.change_id == last_warned_id) { + id = last_warned_id; + } + }; - let changes = find_recent_config_change_ids(id); + let changes = find_recent_config_change_ids(id); - if changes.is_empty() { - return None; - } + if changes.is_empty() { + return None; + } - msg.push_str("There have been changes to x.py since you last updated:\n"); - msg.push_str(&human_readable_changes(&changes)); + msg.push_str("There have been changes to x.py since you last updated:\n"); + msg.push_str(&human_readable_changes(&changes)); - msg.push_str("NOTE: to silence this warning, "); - msg.push_str(&format!( - "update `bootstrap.toml` to use `change-id = {latest_change_id}` instead" - )); + msg.push_str("NOTE: to silence this warning, "); + msg.push_str(&format!( + "update `bootstrap.toml` to use `change-id = {latest_change_id}` instead" + )); - if io::stdout().is_terminal() { - t!(fs::write(warned_id_path, latest_change_id.to_string())); - } - } else { - msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n"); - msg.push_str("NOTE: to silence this warning, "); - msg.push_str(&format!( - "add `change-id = {latest_change_id}` at the top of `bootstrap.toml`" - )); - }; + if io::stdout().is_terminal() { + t!(fs::write(warned_id_path, latest_change_id.to_string())); + } Some(msg) } diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index cf4ef4ee310d6..96316d4d0fdb6 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -193,7 +193,7 @@ pub enum GccCiMode { /// `bootstrap.example.toml`. #[derive(Default, Clone)] pub struct Config { - pub change_id: Option, + pub change_id: Option, pub bypass_bootstrap_lock: bool, pub ccache: Option, /// Call Build::ninja() instead of this. @@ -700,14 +700,32 @@ pub(crate) struct TomlConfig { profile: Option, } +/// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`. +#[derive(Clone, Debug, PartialEq)] +pub enum ChangeId { + Ignore, + Id(usize), +} + /// Since we use `#[serde(deny_unknown_fields)]` on `TomlConfig`, we need a wrapper type /// for the "change-id" field to parse it even if other fields are invalid. This ensures /// that if deserialization fails due to other fields, we can still provide the changelogs /// to allow developers to potentially find the reason for the failure in the logs.. #[derive(Deserialize, Default)] pub(crate) struct ChangeIdWrapper { - #[serde(alias = "change-id")] - pub(crate) inner: Option, + #[serde(alias = "change-id", default, deserialize_with = "deserialize_change_id")] + pub(crate) inner: Option, +} + +fn deserialize_change_id<'de, D: Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + let value = toml::Value::deserialize(deserializer)?; + Ok(match value { + toml::Value::String(s) if s == "ignore" => Some(ChangeId::Ignore), + toml::Value::Integer(i) => Some(ChangeId::Id(i as usize)), + _ => return Err(serde::de::Error::custom("expected \"ignore\" or an integer")), + }) } /// Describes how to handle conflicts in merging two [`TomlConfig`] @@ -1351,10 +1369,11 @@ impl Config { toml::from_str(&contents) .and_then(|table: toml::Value| TomlConfig::deserialize(table)) .inspect_err(|_| { - if let Ok(Some(changes)) = toml::from_str(&contents) - .and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table)) - .map(|change_id| change_id.inner.map(crate::find_recent_config_change_ids)) + if let Ok(ChangeIdWrapper { inner: Some(ChangeId::Id(id)) }) = + toml::from_str::(&contents) + .and_then(|table: toml::Value| ChangeIdWrapper::deserialize(table)) { + let changes = crate::find_recent_config_change_ids(id); if !changes.is_empty() { println!( "WARNING: There have been changes to x.py since you last updated:\n{}", diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index 068e237c2cdcd..ac36910c4e05d 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -10,6 +10,7 @@ use serde::Deserialize; use super::flags::Flags; use super::{ChangeIdWrapper, Config, RUSTC_IF_UNCHANGED_ALLOWED_PATHS}; +use crate::ChangeId; use crate::core::build_steps::clippy::{LintConfig, get_clippy_rules_in_order}; use crate::core::build_steps::llvm; use crate::core::build_steps::llvm::LLVM_INVALIDATION_PATHS; @@ -171,7 +172,7 @@ runner = "x86_64-runner" ) }, ); - assert_eq!(config.change_id, Some(1), "setting top-level value"); + assert_eq!(config.change_id, Some(ChangeId::Id(1)), "setting top-level value"); assert_eq!( config.rust_lto, crate::core::config::RustcLto::Fat, @@ -311,7 +312,7 @@ fn parse_change_id_with_unknown_field() { "#; let change_id_wrapper: ChangeIdWrapper = toml::from_str(config).unwrap(); - assert_eq!(change_id_wrapper.inner, Some(3461)); + assert_eq!(change_id_wrapper.inner, Some(ChangeId::Id(3461))); } #[test] diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index c47cd8b452f94..843d474f92de8 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -45,8 +45,8 @@ mod core; mod utils; pub use core::builder::PathSet; -pub use core::config::Config; pub use core::config::flags::{Flags, Subcommand}; +pub use core::config::{ChangeId, Config}; #[cfg(feature = "tracing")] use tracing::{instrument, span}; From 02444322c0ee0362ae01c35da5495c75232e72da Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 26 Mar 2025 18:25:47 +0530 Subject: [PATCH 15/23] add changeInfo to change tracker --- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 1a101b02f7095..78887a42a0009 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -390,4 +390,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Warning, summary: "The default configuration filename has changed from `config.toml` to `bootstrap.toml`. `config.toml` is deprecated but remains supported for backward compatibility.", }, + ChangeInfo { + change_id: 138986, + severity: ChangeSeverity::Info, + summary: "You can now use `change_id = \"ignore\"` to suppress `change_id` warnings in the console.", + }, ]; From 1437dec79942f1367b4cc7756e2f080261c34f3a Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Thu, 27 Mar 2025 12:39:48 -0400 Subject: [PATCH 16/23] rustc_resolve: prevent iteration of refids for completeness This came up in review, and it should help some future author not introduce non-deterministic output here. --- compiler/rustc_resolve/src/rustdoc.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 9fda1eb4dc453..a32fe6990160c 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -8,7 +8,8 @@ use pulldown_cmark::{ use rustc_ast as ast; use rustc_ast::attr::AttributeExt; use rustc_ast::util::comments::beautify_doc_string; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::unord::UnordSet; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, kw, sym}; @@ -423,7 +424,7 @@ fn parse_links<'md>(doc: &'md str) -> Vec> { ); let mut links = Vec::new(); - let mut refids = FxHashSet::default(); + let mut refids = UnordSet::default(); while let Some(event) = event_iter.next() { match event { From 5d274408d4b8b845f490912dcdadcc717f18fcca Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 27 Mar 2025 17:43:29 +0100 Subject: [PATCH 17/23] Only take outer attributes into account when generating content between first non-crate items and the crate items --- src/librustdoc/doctest/make.rs | 8 +- src/librustdoc/doctest/tests.rs | 131 ++++---------------------------- 2 files changed, 19 insertions(+), 120 deletions(-) diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 55b57292b33a2..56b1e76ae8cfd 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use rustc_ast::token::{Delimiter, TokenKind}; use rustc_ast::tokenstream::TokenTree; -use rustc_ast::{self as ast, HasAttrs, StmtKind}; +use rustc_ast::{self as ast, AttrStyle, HasAttrs, StmtKind}; use rustc_errors::ColorConfig; use rustc_errors::emitter::stderr_destination; use rustc_parse::new_parser_from_source_str; @@ -388,7 +388,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result) -> Result Date: Fri, 28 Mar 2025 01:54:35 +0000 Subject: [PATCH 18/23] Update target maintainers for thumb targets to reflect new REWG Arm team name --- src/doc/rustc/src/platform-support/thumbv6m-none-eabi.md | 3 +-- src/doc/rustc/src/platform-support/thumbv7em-none-eabi.md | 3 +-- src/doc/rustc/src/platform-support/thumbv7m-none-eabi.md | 3 +-- src/doc/rustc/src/platform-support/thumbv8m.base-none-eabi.md | 3 +-- src/doc/rustc/src/platform-support/thumbv8m.main-none-eabi.md | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/doc/rustc/src/platform-support/thumbv6m-none-eabi.md b/src/doc/rustc/src/platform-support/thumbv6m-none-eabi.md index f4ed6201bbdc9..746b84435479f 100644 --- a/src/doc/rustc/src/platform-support/thumbv6m-none-eabi.md +++ b/src/doc/rustc/src/platform-support/thumbv6m-none-eabi.md @@ -26,8 +26,7 @@ only option because there is no FPU support in [Armv6-M]. ## Target maintainers -* [Rust Embedded Devices Working Group Cortex-M - Team](https://github.com/rust-embedded), `cortex-m@teams.rust-embedded.org` +* [Rust Embedded Devices Working Group Arm Team](https://github.com/rust-embedded/wg?tab=readme-ov-file#the-arm-team) ## Target CPU and Target Feature options diff --git a/src/doc/rustc/src/platform-support/thumbv7em-none-eabi.md b/src/doc/rustc/src/platform-support/thumbv7em-none-eabi.md index 11c9486cb76ea..12e28265678c5 100644 --- a/src/doc/rustc/src/platform-support/thumbv7em-none-eabi.md +++ b/src/doc/rustc/src/platform-support/thumbv7em-none-eabi.md @@ -21,8 +21,7 @@ See [`arm-none-eabi`](arm-none-eabi.md) for information applicable to all ## Target maintainers -* [Rust Embedded Devices Working Group Cortex-M - Team](https://github.com/rust-embedded), `cortex-m@teams.rust-embedded.org` +* [Rust Embedded Devices Working Group Arm Team](https://github.com/rust-embedded/wg?tab=readme-ov-file#the-arm-team) ## Target CPU and Target Feature options diff --git a/src/doc/rustc/src/platform-support/thumbv7m-none-eabi.md b/src/doc/rustc/src/platform-support/thumbv7m-none-eabi.md index b258033bb0fa7..03324b341d07f 100644 --- a/src/doc/rustc/src/platform-support/thumbv7m-none-eabi.md +++ b/src/doc/rustc/src/platform-support/thumbv7m-none-eabi.md @@ -22,8 +22,7 @@ only option because there is no FPU support in [Armv7-M]. ## Target maintainers -* [Rust Embedded Devices Working Group Cortex-M - Team](https://github.com/rust-embedded), `cortex-m@teams.rust-embedded.org` +* [Rust Embedded Devices Working Group Arm Team](https://github.com/rust-embedded/wg?tab=readme-ov-file#the-arm-team) ## Target CPU and Target Feature options diff --git a/src/doc/rustc/src/platform-support/thumbv8m.base-none-eabi.md b/src/doc/rustc/src/platform-support/thumbv8m.base-none-eabi.md index 0ae4e3e94bd8c..4a92e856466c0 100644 --- a/src/doc/rustc/src/platform-support/thumbv8m.base-none-eabi.md +++ b/src/doc/rustc/src/platform-support/thumbv8m.base-none-eabi.md @@ -22,8 +22,7 @@ only option because there is no FPU support in [Armv8-M] Baseline. ## Target maintainers -* [Rust Embedded Devices Working Group Cortex-M - Team](https://github.com/rust-embedded), `cortex-m@teams.rust-embedded.org` +* [Rust Embedded Devices Working Group Arm Team](https://github.com/rust-embedded/wg?tab=readme-ov-file#the-arm-team) ## Target CPU and Target Feature options diff --git a/src/doc/rustc/src/platform-support/thumbv8m.main-none-eabi.md b/src/doc/rustc/src/platform-support/thumbv8m.main-none-eabi.md index 82fdc5b21cf2d..9f85d08fa0acd 100644 --- a/src/doc/rustc/src/platform-support/thumbv8m.main-none-eabi.md +++ b/src/doc/rustc/src/platform-support/thumbv8m.main-none-eabi.md @@ -25,8 +25,7 @@ See [`arm-none-eabi`](arm-none-eabi.md) for information applicable to all ## Target maintainers -* [Rust Embedded Devices Working Group Cortex-M - Team](https://github.com/rust-embedded), `cortex-m@teams.rust-embedded.org` +* [Rust Embedded Devices Working Group Arm Team](https://github.com/rust-embedded/wg?tab=readme-ov-file#the-arm-team) ## Target CPU and Target Feature options From b24083bccc15a55d8488fee8ee629689c5039ded Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 27 Mar 2025 23:10:30 +0530 Subject: [PATCH 19/23] Add new change-id option in bootstrap.example.toml and update the change-id description references --- bootstrap.example.toml | 5 +++-- src/bootstrap/src/bin/main.rs | 4 ++-- src/bootstrap/src/core/config/config.rs | 6 +++++- src/bootstrap/src/utils/change_tracker.rs | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index caffe1a93712f..2a98821f22529 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -28,8 +28,9 @@ # - A new option # - A change in the default values # -# If `change-id` does not match the version that is currently running, -# `x.py` will inform you about the changes made on bootstrap. +# If the change-id does not match the version currently in use, x.py will +# display the changes made to the bootstrap. +# To suppress these warnings, you can set change-id = "ignore". #change-id = # ============================================================================= diff --git a/src/bootstrap/src/bin/main.rs b/src/bootstrap/src/bin/main.rs index 5df9902a9778c..7ec3140c03862 100644 --- a/src/bootstrap/src/bin/main.rs +++ b/src/bootstrap/src/bin/main.rs @@ -163,7 +163,7 @@ fn check_version(config: &Config) -> Option { msg.push_str("WARNING: The `change-id` is missing in the `bootstrap.toml`. This means that you will not be able to track the major changes made to the bootstrap configurations.\n"); msg.push_str("NOTE: to silence this warning, "); msg.push_str(&format!( - "add `change-id = {latest_change_id}` at the top of `bootstrap.toml`" + "add `change-id = {latest_change_id}` or change-id = \"ignore\" at the top of `bootstrap.toml`" )); return Some(msg); } @@ -195,7 +195,7 @@ fn check_version(config: &Config) -> Option { msg.push_str("NOTE: to silence this warning, "); msg.push_str(&format!( - "update `bootstrap.toml` to use `change-id = {latest_change_id}` instead" + "update `bootstrap.toml` to use `change-id = {latest_change_id}` or change-id = \"ignore\" instead" )); if io::stdout().is_terminal() { diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 96316d4d0fdb6..bbb0fbfbb9351 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -724,7 +724,11 @@ fn deserialize_change_id<'de, D: Deserializer<'de>>( Ok(match value { toml::Value::String(s) if s == "ignore" => Some(ChangeId::Ignore), toml::Value::Integer(i) => Some(ChangeId::Id(i as usize)), - _ => return Err(serde::de::Error::custom("expected \"ignore\" or an integer")), + _ => { + return Err(serde::de::Error::custom( + "expected \"ignore\" or an integer for change-id", + )); + } }) } diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 78887a42a0009..5314141dd1b02 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -393,6 +393,6 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ ChangeInfo { change_id: 138986, severity: ChangeSeverity::Info, - summary: "You can now use `change_id = \"ignore\"` to suppress `change_id` warnings in the console.", + summary: "You can now use `change-id = \"ignore\"` to suppress `change-id ` warnings in the console.", }, ]; From d5f7e71d5c861c513fda67bd35382ac03049e177 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Fri, 28 Mar 2025 10:11:45 +0300 Subject: [PATCH 20/23] bootstrap: update `test_find` test `cc::Build::get_archiver` is noisy on the `arm-linux-androideabi` target and constantly printing `llvm-ar --version` output during bootstrap tests on all platforms. Signed-off-by: onur-ozkan --- src/bootstrap/src/utils/cc_detect/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index c97529cbe9eb4..b4a1b52dd2300 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -264,7 +264,7 @@ fn test_find_target_without_config() { fn test_find() { let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) }); let target1 = TargetSelection::from_user("x86_64-unknown-linux-gnu"); - let target2 = TargetSelection::from_user("arm-linux-androideabi"); + let target2 = TargetSelection::from_user("x86_64-unknown-openbsd"); build.targets.push(target1.clone()); build.hosts.push(target2.clone()); find(&build); From 3a9a5770ef44401315e8c99b13b81b4c595fc82e Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 28 Mar 2025 08:15:18 +0100 Subject: [PATCH 21/23] Remove ScopeDepth entirely. The scope depth was tracked, but never actually used for anything. --- .../rustc_hir_analysis/src/check/region.rs | 38 +++++++------------ compiler/rustc_middle/src/middle/region.rs | 8 ++-- compiler/rustc_middle/src/ty/rvalue_scopes.rs | 2 +- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index ba8124b11fc13..9d99a3ea67395 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -23,18 +23,11 @@ use tracing::debug; #[derive(Debug, Copy, Clone)] struct Context { - /// The scope that contains any new variables declared, plus its depth in - /// the scope tree. + /// The scope that contains any new variables declared. var_parent: Option, - /// Region parent of expressions, etc., plus its depth in the scope tree. - parent: Option<(Scope, ScopeDepth)>, -} - -impl Context { - fn set_var_parent(&mut self) { - self.var_parent = self.parent.map(|(p, _)| p); - } + /// Region parent of expressions, etc. + parent: Option, } struct ScopeResolutionVisitor<'tcx> { @@ -119,7 +112,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi // itself has returned. visitor.enter_node_scope_with_dtor(blk.hir_id.local_id); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; { // This block should be kept approximately in sync with @@ -138,7 +131,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi local_id: blk.hir_id.local_id, data: ScopeData::Remainder(FirstStatementIndex::new(i)), }); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; visitor.visit_stmt(statement); // We need to back out temporarily to the last enclosing scope // for the `else` block, so that even the temporaries receiving @@ -163,7 +156,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi local_id: blk.hir_id.local_id, data: ScopeData::Remainder(FirstStatementIndex::new(i)), }); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; visitor.visit_stmt(statement) } hir::StmtKind::Item(..) => { @@ -213,7 +206,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir: visitor.terminating_scopes.insert(arm.hir_id.local_id); visitor.enter_node_scope_with_dtor(arm.hir_id.local_id); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; if let Some(expr) = arm.guard && !has_let_expr(expr) @@ -490,7 +483,7 @@ fn resolve_expr<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hi ScopeData::IfThen }; visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; visitor.visit_expr(cond); visitor.visit_expr(then); visitor.cx = expr_cx; @@ -505,7 +498,7 @@ fn resolve_expr<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hi ScopeData::IfThen }; visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data }); - visitor.cx.set_var_parent(); + visitor.cx.var_parent = visitor.cx.parent; visitor.visit_expr(cond); visitor.visit_expr(then); visitor.cx = expr_cx; @@ -545,7 +538,7 @@ fn resolve_expr<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hi // Keep traversing up while we can. match visitor.scope_tree.parent_map.get(&scope) { // Don't cross from closure bodies to their parent. - Some(&(superscope, _)) => match superscope.data { + Some(&superscope) => match superscope.data { ScopeData::CallSite => break, _ => scope = superscope, }, @@ -782,19 +775,16 @@ fn resolve_local<'tcx>( impl<'tcx> ScopeResolutionVisitor<'tcx> { /// Records the current parent (if any) as the parent of `child_scope`. /// Returns the depth of `child_scope`. - fn record_child_scope(&mut self, child_scope: Scope) -> ScopeDepth { + fn record_child_scope(&mut self, child_scope: Scope) { let parent = self.cx.parent; self.scope_tree.record_scope_parent(child_scope, parent); - // If `child_scope` has no parent, it must be the root node, and so has - // a depth of 1. Otherwise, its depth is one more than its parent's. - parent.map_or(1, |(_p, d)| d + 1) } /// Records the current parent (if any) as the parent of `child_scope`, /// and sets `child_scope` as the new current parent. fn enter_scope(&mut self, child_scope: Scope) { - let child_depth = self.record_child_scope(child_scope); - self.cx.parent = Some((child_scope, child_depth)); + self.record_child_scope(child_scope); + self.cx.parent = Some(child_scope); } fn enter_node_scope_with_dtor(&mut self, id: hir::ItemLocalId) { @@ -855,7 +845,7 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> { self.enter_body(body.value.hir_id, |this| { if this.tcx.hir_body_owner_kind(owner_id).is_fn_or_closure() { // The arguments and `self` are parented to the fn. - this.cx.set_var_parent(); + this.cx.var_parent = this.cx.parent; for param in body.params { this.visit_pat(param.pat); } diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 66ece8f0e52fd..ba31f775b651e 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -199,8 +199,6 @@ impl Scope { } } -pub type ScopeDepth = u32; - /// The region scope tree encodes information about region relationships. #[derive(Default, Debug, HashStable)] pub struct ScopeTree { @@ -213,7 +211,7 @@ pub struct ScopeTree { /// conditional expression or repeating block. (Note that the /// enclosing scope ID for the block associated with a closure is /// the closure itself.) - pub parent_map: FxIndexMap, + pub parent_map: FxIndexMap, /// Maps from a variable or binding ID to the block in which that /// variable is declared. @@ -328,7 +326,7 @@ pub struct YieldData { } impl ScopeTree { - pub fn record_scope_parent(&mut self, child: Scope, parent: Option<(Scope, ScopeDepth)>) { + pub fn record_scope_parent(&mut self, child: Scope, parent: Option) { debug!("{:?}.parent = {:?}", child, parent); if let Some(p) = parent { @@ -353,7 +351,7 @@ impl ScopeTree { /// Returns the narrowest scope that encloses `id`, if any. pub fn opt_encl_scope(&self, id: Scope) -> Option { - self.parent_map.get(&id).cloned().map(|(p, _)| p) + self.parent_map.get(&id).cloned() } /// Returns the lifetime of the local variable `var_id`, if any. diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs index b00c8169a36a7..9bf6e3a759004 100644 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ b/compiler/rustc_middle/src/ty/rvalue_scopes.rs @@ -38,7 +38,7 @@ impl RvalueScopes { let mut id = Scope { local_id: expr_id, data: ScopeData::Node }; let mut backwards_incompatible = None; - while let Some(&(p, _)) = region_scope_tree.parent_map.get(&id) { + while let Some(&p) = region_scope_tree.parent_map.get(&id) { match p.data { ScopeData::Destruction => { debug!("temporary_scope({expr_id:?}) = {id:?} [enclosing]"); From deeac1c588d3487202490280ee64434ad3c45f0c Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 28 Mar 2025 08:36:16 +0100 Subject: [PATCH 22/23] Remove outdated comment. --- compiler/rustc_hir_analysis/src/check/region.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 9d99a3ea67395..2528adb937b31 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -774,7 +774,6 @@ fn resolve_local<'tcx>( impl<'tcx> ScopeResolutionVisitor<'tcx> { /// Records the current parent (if any) as the parent of `child_scope`. - /// Returns the depth of `child_scope`. fn record_child_scope(&mut self, child_scope: Scope) { let parent = self.cx.parent; self.scope_tree.record_scope_parent(child_scope, parent); From 87d524bef68b6a63f965cec81e0ce28e00843907 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 28 Mar 2025 10:35:53 +0100 Subject: [PATCH 23/23] Update `coverage-run-rustdoc` output --- tests/coverage-run-rustdoc/doctest.coverage | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/coverage-run-rustdoc/doctest.coverage b/tests/coverage-run-rustdoc/doctest.coverage index f007eb0cadea6..0fa94361c472e 100644 --- a/tests/coverage-run-rustdoc/doctest.coverage +++ b/tests/coverage-run-rustdoc/doctest.coverage @@ -58,21 +58,21 @@ $DIR/doctest.rs: LL| |//! LL| |//! doctest with custom main: LL| |//! ``` - LL| 1|//! fn some_func() { - LL| 1|//! println!("called some_func()"); - LL| 1|//! } - LL| |//! - LL| |//! #[derive(Debug)] - LL| |//! struct SomeError; + LL| |//! fn some_func() { + LL| |//! println!("called some_func()"); + LL| |//! } + LL| 1|//! + LL| 1|//! #[derive(Debug)] + LL| 1|//! struct SomeError; LL| |//! LL| |//! extern crate doctest_crate; LL| |//! - LL| 1|//! fn doctest_main() -> Result<(), SomeError> { + LL| |//! fn doctest_main() -> Result<(), SomeError> { LL| 1|//! some_func(); LL| 1|//! doctest_crate::fn_run_in_doctests(2); LL| 1|//! Ok(()) LL| 1|//! } - LL| |//! + LL| 1|//! LL| |//! // this `main` is not shown as covered, as it clashes with all the other LL| |//! // `main` functions that were automatically generated for doctests LL| |//! fn main() -> Result<(), SomeError> {