From a22e9e0e026bd1d2da24041b40a4227a0b7d6616 Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:56:01 -0400 Subject: [PATCH 1/5] docs: autogenerate compiler flag stubs by parsing options! macro --- src/tools/unstable-book-gen/src/main.rs | 611 +++++++++++++++++++++++- 1 file changed, 609 insertions(+), 2 deletions(-) diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs index 16550f83003dc..4238d7afa341c 100644 --- a/src/tools/unstable-book-gen/src/main.rs +++ b/src/tools/unstable-book-gen/src/main.rs @@ -6,10 +6,12 @@ use std::fs::{self, write}; use std::path::Path; use tidy::diagnostics::RunningCheck; -use tidy::features::{Features, collect_env_vars, collect_lang_features, collect_lib_features}; +use tidy::features::{ + Feature, Features, Status, collect_env_vars, collect_lang_features, collect_lib_features, +}; use tidy::t; use tidy::unstable_book::{ - ENV_VARS_DIR, LANG_FEATURES_DIR, LIB_FEATURES_DIR, PATH_STR, + COMPILER_FLAGS_DIR, ENV_VARS_DIR, LANG_FEATURES_DIR, LIB_FEATURES_DIR, PATH_STR, collect_unstable_book_section_file_names, collect_unstable_feature_names, }; @@ -113,6 +115,510 @@ fn copy_recursive(from: &Path, to: &Path) { } } +fn collect_compiler_flags(compiler_path: &Path) -> Features { + let options_path = compiler_path.join("rustc_session/src/options.rs"); + let options_rs = t!(fs::read_to_string(&options_path), options_path); + parse_compiler_flags(&options_rs, &options_path) +} + +const DESCRIPTION_FIELD: usize = 3; +const REQUIRED_FIELDS: usize = 4; +const OPTIONAL_FIELDS: usize = 5; + +struct SourceBlock<'a> { + content: &'a str, + offset: usize, +} + +struct ParsedOptionEntry<'a> { + name: &'a str, + name_start: usize, + description: Option, + next_idx: usize, +} + +fn parse_compiler_flags(options_rs: &str, options_path: &Path) -> Features { + let options_block = find_options_block(options_rs, "UnstableOptions"); + let alphabetical_section = find_tidy_alphabetical_section(options_block); + let section_line_offset = line_number(options_rs, alphabetical_section.offset) - 1; + + let mut features = Features::new(); + let mut idx = 0; + + while idx < alphabetical_section.content.len() { + skip_ws_comments_and_attrs(alphabetical_section.content, &mut idx); + if idx >= alphabetical_section.content.len() { + break; + } + + let entry = parse_one_entry(alphabetical_section.content, idx); + idx = entry.next_idx; + + if entry.name == "help" { + continue; + } + + features.insert( + entry.name.to_owned(), + Feature { + level: Status::Unstable, + since: None, + has_gate_test: false, + tracking_issue: None, + file: options_path.to_path_buf(), + line: section_line_offset + + line_number(alphabetical_section.content, entry.name_start), + description: entry.description, + }, + ); + } + + features +} + +fn parse_one_entry(source: &str, start_idx: usize) -> ParsedOptionEntry<'_> { + let name_start = start_idx; + let name_end = + parse_ident_end(source, name_start).expect("expected an option name in UnstableOptions"); + let name = &source[name_start..name_end]; + let mut idx = name_end; + + skip_ws_comments(source, &mut idx); + expect_byte(source, idx, b':', &format!("expected `:` after option name `{name}`")); + idx += 1; + + idx = + find_char_outside_nested(source, idx, b'=').expect("expected `=` in UnstableOptions entry"); + idx += 1; + + skip_ws_comments(source, &mut idx); + expect_byte(source, idx, b'(', &format!("expected tuple payload for option `{name}`")); + + let tuple_start = idx; + let tuple_end = find_matching_delimiter(source, tuple_start, b'(', b')') + .expect("UnstableOptions tuple should be balanced"); + let next_idx = skip_past_entry_delimiter(source, tuple_end + 1, name); + + let description = if name == "help" { + None + } else { + let fields = split_top_level_fields(&source[tuple_start + 1..tuple_end]); + validate_option_fields(&fields, name); + // The `options!` macro layout is `(init, parse, [dep_tracking...], desc, ...)`. + Some(parse_string_literal( + fields.get(DESCRIPTION_FIELD).expect("option description should be present"), + )) + }; + + ParsedOptionEntry { name, name_start, description, next_idx } +} + +fn find_options_block<'a>(source: &'a str, struct_name: &str) -> SourceBlock<'a> { + let mut search_from = 0; + + while let Some(relative_start) = source[search_from..].find("options!") { + let macro_start = search_from + relative_start; + let open_brace = source[macro_start..] + .find('{') + .map(|relative| macro_start + relative) + .expect("options! invocation should contain `{`"); + let close_brace = find_matching_delimiter(source, open_brace, b'{', b'}') + .expect("options! invocation should have a matching `}`"); + let block = &source[open_brace + 1..close_brace]; + + if block.trim_start().starts_with(struct_name) { + return SourceBlock { content: block, offset: open_brace + 1 }; + } + + search_from = close_brace + 1; + } + + panic!("could not find `{struct_name}` options! block"); +} + +fn find_tidy_alphabetical_section(block: SourceBlock<'_>) -> SourceBlock<'_> { + let start_marker = "// tidy-alphabetical-start"; + let end_marker = "// tidy-alphabetical-end"; + + let section_start = block + .content + .find(start_marker) + .map(|start| start + start_marker.len()) + .expect("options! block should contain `// tidy-alphabetical-start`"); + let section_end = block.content[section_start..] + .find(end_marker) + .map(|end| section_start + end) + .expect("options! block should contain `// tidy-alphabetical-end`"); + + SourceBlock { + content: &block.content[section_start..section_end], + offset: block.offset + section_start, + } +} + +fn line_number(source: &str, offset: usize) -> usize { + source[..offset].bytes().filter(|&byte| byte == b'\n').count() + 1 +} + +fn expect_byte(source: &str, idx: usize, expected: u8, context: &str) { + assert_eq!(source.as_bytes().get(idx).copied(), Some(expected), "{context}"); +} + +fn skip_ws_comments_and_attrs(source: &str, idx: &mut usize) { + loop { + skip_ws_comments(source, idx); + + if source[*idx..].starts_with("#[") { + let attr_start = *idx + 1; + let attr_end = find_matching_delimiter(source, attr_start, b'[', b']') + .expect("attribute should have matching `]`"); + *idx = attr_end + 1; + continue; + } + + break; + } +} + +fn skip_ws_comments(source: &str, idx: &mut usize) { + loop { + while let Some(byte) = source.as_bytes().get(*idx) { + if byte.is_ascii_whitespace() { + *idx += 1; + } else { + break; + } + } + + if source[*idx..].starts_with("//") { + *idx = source[*idx..].find('\n').map_or(source.len(), |end| *idx + end + 1); + continue; + } + + if source[*idx..].starts_with("/*") { + *idx = skip_block_comment(source, *idx); + continue; + } + + break; + } +} + +fn skip_block_comment(source: &str, mut idx: usize) -> usize { + let mut depth = 1; + idx += 2; + + while idx < source.len() { + match source.as_bytes().get(idx..idx + 2) { + Some(b"/*") => { + depth += 1; + idx += 2; + } + Some(b"*/") => { + depth -= 1; + idx += 2; + if depth == 0 { + return idx; + } + } + _ => idx += 1, + } + } + + panic!("unterminated block comment"); +} + +fn parse_ident_end(source: &str, start: usize) -> Option { + let bytes = source.as_bytes(); + let first = *bytes.get(start)?; + if !(first == b'_' || first.is_ascii_alphabetic()) { + return None; + } + + let mut idx = start + 1; + while let Some(byte) = bytes.get(idx) { + if *byte == b'_' || byte.is_ascii_alphanumeric() { + idx += 1; + } else { + break; + } + } + + Some(idx) +} + +fn find_char_outside_nested(source: &str, start: usize, needle: u8) -> Option { + let mut idx = start; + let mut paren_depth = 0; + let mut bracket_depth = 0; + let mut brace_depth = 0; + + while idx < source.len() { + match source.as_bytes()[idx] { + b'/' if source[idx..].starts_with("//") => { + idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); + } + b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), + b'"' => idx = skip_string_literal(source, idx), + b'(' => { + paren_depth += 1; + idx += 1; + } + b')' => { + paren_depth -= 1; + idx += 1; + } + b'[' => { + bracket_depth += 1; + idx += 1; + } + b']' => { + bracket_depth -= 1; + idx += 1; + } + b'{' => { + brace_depth += 1; + idx += 1; + } + b'}' => { + brace_depth -= 1; + idx += 1; + } + byte if byte == needle + && paren_depth == 0 + && bracket_depth == 0 + && brace_depth == 0 => + { + return Some(idx); + } + _ => idx += 1, + } + } + + None +} + +fn find_matching_delimiter(source: &str, start: usize, open: u8, close: u8) -> Option { + let mut idx = start; + let mut depth = 0; + + while idx < source.len() { + match source.as_bytes()[idx] { + b'/' if source[idx..].starts_with("//") => { + idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); + } + b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), + b'"' => idx = skip_string_literal(source, idx), + byte if byte == open => { + depth += 1; + idx += 1; + } + byte if byte == close => { + depth -= 1; + if depth == 0 { + return Some(idx); + } + idx += 1; + } + _ => idx += 1, + } + } + + None +} + +fn split_top_level_fields(source: &str) -> Vec<&str> { + let mut fields = Vec::new(); + let mut field_start = 0; + let mut idx = 0; + let mut paren_depth = 0; + let mut bracket_depth = 0; + let mut brace_depth = 0; + + while idx < source.len() { + match source.as_bytes()[idx] { + b'/' if source[idx..].starts_with("//") => { + idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); + } + b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), + b'"' => idx = skip_string_literal(source, idx), + b'(' => { + paren_depth += 1; + idx += 1; + } + b')' => { + paren_depth -= 1; + idx += 1; + } + b'[' => { + bracket_depth += 1; + idx += 1; + } + b']' => { + bracket_depth -= 1; + idx += 1; + } + b'{' => { + brace_depth += 1; + idx += 1; + } + b'}' => { + brace_depth -= 1; + idx += 1; + } + b',' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => { + fields.push(source[field_start..idx].trim()); + idx += 1; + field_start = idx; + } + _ => idx += 1, + } + } + + fields.push(source[field_start..].trim()); + fields +} + +fn validate_option_fields(fields: &[&str], name: &str) { + assert!( + matches!(fields.len(), REQUIRED_FIELDS | OPTIONAL_FIELDS), + "unexpected field count for option `{name}`: expected 4 or 5 fields, found {}", + fields.len() + ); + assert!( + fields[2].starts_with('[') && fields[2].ends_with(']'), + "expected dep-tracking field in option `{name}`, found `{}`", + fields[2] + ); + assert!( + looks_like_string_literal(fields[DESCRIPTION_FIELD]), + "expected description string literal in option `{name}`, found `{}`", + fields[DESCRIPTION_FIELD] + ); + + if let Some(extra_field) = fields.get(REQUIRED_FIELDS) { + assert!( + extra_field.trim_start().starts_with("is_deprecated_and_do_nothing:"), + "unexpected trailing field in option `{name}`: `{extra_field}`", + ); + } +} + +fn looks_like_string_literal(field: &str) -> bool { + let field = field.trim(); + (field.starts_with('"') && field.ends_with('"')) || parse_raw_string_literal(field).is_some() +} + +fn skip_past_entry_delimiter(source: &str, start: usize, name: &str) -> usize { + let mut idx = start; + skip_ws_comments(source, &mut idx); + + match source.as_bytes().get(idx).copied() { + Some(b',') => idx + 1, + None => idx, + Some(byte) => { + panic!("expected `,` after option entry `{name}`, found {:?}", char::from(byte)) + } + } +} + +fn skip_string_literal(source: &str, mut idx: usize) -> usize { + idx += 1; + + while idx < source.len() { + match source.as_bytes()[idx] { + b'\\' => { + idx += 1; + if idx < source.len() { + idx += 1; + } + } + b'"' => return idx + 1, + _ => idx += 1, + } + } + + panic!("unterminated string literal"); +} + +fn parse_string_literal(literal: &str) -> String { + let literal = literal.trim(); + + if let Some(raw_literal) = parse_raw_string_literal(literal) { + return raw_literal; + } + + let inner = literal + .strip_prefix('"') + .and_then(|value| value.strip_suffix('"')) + .expect("expected a string literal"); + let mut output = String::new(); + let mut chars = inner.chars().peekable(); + + while let Some(ch) = chars.next() { + if ch != '\\' { + output.push(ch); + continue; + } + + let escaped = chars.next().expect("unterminated string escape"); + match escaped { + '\n' => while chars.next_if(|ch| ch.is_whitespace()).is_some() {}, + '\r' => { + let _ = chars.next_if_eq(&'\n'); + while chars.next_if(|ch| ch.is_whitespace()).is_some() {} + } + '"' => output.push('"'), + '\'' => output.push('\''), + '\\' => output.push('\\'), + 'n' => output.push('\n'), + 'r' => output.push('\r'), + 't' => output.push('\t'), + '0' => output.push('\0'), + 'x' => { + let hi = chars.next().expect("missing first hex digit in escape"); + let lo = chars.next().expect("missing second hex digit in escape"); + let byte = u8::from_str_radix(&format!("{hi}{lo}"), 16) + .expect("invalid hex escape in string literal"); + output.push(char::from(byte)); + } + 'u' => { + assert_eq!(chars.next(), Some('{'), "expected `{{` after `\\u`"); + let mut digits = String::new(); + for ch in chars.by_ref() { + if ch == '}' { + break; + } + digits.push(ch); + } + let scalar = + u32::from_str_radix(&digits, 16).expect("invalid unicode escape in string"); + output.push(char::from_u32(scalar).expect("unicode escape should be valid")); + } + _ => panic!("unsupported escape in string literal"), + } + } + + output +} + +fn parse_raw_string_literal(literal: &str) -> Option { + let rest = literal.strip_prefix('r')?; + let hashes = rest.bytes().take_while(|&byte| byte == b'#').count(); + let quote_idx = 1 + hashes; + + if literal.as_bytes().get(quote_idx) != Some(&b'"') { + return None; + } + + let suffix = format!("\"{}", "#".repeat(hashes)); + let content = literal[quote_idx + 1..] + .strip_suffix(&suffix) + .expect("raw string literal should have a matching terminator"); + + Some(content.to_owned()) +} + fn main() { let library_path_str = env::args_os().nth(1).expect("library/ path required"); let compiler_path_str = env::args_os().nth(2).expect("compiler/ path required"); @@ -129,6 +635,7 @@ fn main() { .filter(|&(ref name, _)| !lang_features.contains_key(name)) .collect(); let env_vars = collect_env_vars(compiler_path); + let compiler_flags = collect_compiler_flags(compiler_path); let doc_src_path = src_path.join(PATH_STR); @@ -144,9 +651,109 @@ fn main() { &dest_path.join(LIB_FEATURES_DIR), &lib_features, ); + generate_feature_files( + &doc_src_path.join(COMPILER_FLAGS_DIR), + &dest_path.join(COMPILER_FLAGS_DIR), + &compiler_flags, + ); generate_env_files(&doc_src_path.join(ENV_VARS_DIR), &dest_path.join(ENV_VARS_DIR), &env_vars); copy_recursive(&doc_src_path, &dest_path); generate_summary(&dest_path, &lang_features, &lib_features); } + +#[cfg(test)] +mod tests { + use std::path::{Path, PathBuf}; + + use super::{parse_compiler_flags, parse_one_entry, skip_ws_comments_and_attrs}; + + #[test] + fn parses_unstable_options_entries() { + let options_rs = r#" +options! { + UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", + + // tidy-alphabetical-start + #[rustc_lint_opt_deny_field_access("test attr")] + allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], + "only allow the listed language features to be enabled in code (comma separated)"), + dump_mir: Option = (None, parse_opt_string, [UNTRACKED], + "dump MIR state to file. + `val` is used to select which passes and functions to dump."), + join_lines: bool = (false, parse_bool, [TRACKED], + "join \ + continued lines"), + help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), + // tidy-alphabetical-end +} +"#; + + let features = parse_compiler_flags(options_rs, Path::new("options.rs")); + + assert!(features.contains_key("allow_features")); + assert!(features.contains_key("dump_mir")); + assert!(features.contains_key("join_lines")); + assert!(!features.contains_key("help")); + + assert_eq!( + features["dump_mir"].description.as_deref(), + Some( + "dump MIR state to file.\n `val` is used to select which passes and functions to dump." + ), + ); + assert_eq!(features["join_lines"].description.as_deref(), Some("join continued lines"),); + assert_eq!(features["allow_features"].file, PathBuf::from("options.rs")); + assert_eq!(features["allow_features"].line, 7); + } + + #[test] + fn parse_one_entry_skips_help_description_and_advances() { + let section = r#" +help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), +join_lines: bool = (false, parse_bool, [TRACKED], "join \ + continued lines"), +"#; + let section = section.trim_start(); + + let help_entry = parse_one_entry(section, 0); + assert_eq!(help_entry.name, "help"); + assert!(help_entry.description.is_none()); + + let mut next_idx = help_entry.next_idx; + skip_ws_comments_and_attrs(section, &mut next_idx); + let next_entry = parse_one_entry(section, next_idx); + + assert_eq!(next_entry.name, "join_lines"); + assert_eq!(next_entry.description.as_deref(), Some("join continued lines"),); + } + + #[test] + fn parse_one_entry_accepts_optional_trailing_metadata() { + let entry = r#" +deprecated_flag: bool = (false, parse_no_value, [UNTRACKED], "deprecated flag", + is_deprecated_and_do_nothing: true), +"#; + let entry = entry.trim_start(); + + let parsed = parse_one_entry(entry, 0); + assert_eq!(parsed.name, "deprecated_flag"); + assert_eq!(parsed.description.as_deref(), Some("deprecated flag")); + } + + #[test] + fn parses_real_unstable_options_file() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let options_path = manifest_dir.join("../../../compiler/rustc_session/src/options.rs"); + let options_rs = std::fs::read_to_string(&options_path).unwrap(); + let features = parse_compiler_flags(&options_rs, &options_path); + + assert!(features.contains_key("allow_features")); + assert!(features.contains_key("dump_mir")); + assert!(features.contains_key("unstable_options")); + assert!(!features.contains_key("help")); + assert!(features["dump_mir"].line > 0); + assert!(features["dump_mir"].description.as_deref().unwrap().starts_with("dump MIR state")); + } +} From 83a21a5e7129b62e4c1944affb906c0751a6cfaf Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Wed, 18 Mar 2026 21:59:48 -0400 Subject: [PATCH 2/5] fix: move tests to separate file and avoid tidy markers in strings --- src/tools/unstable-book-gen/src/main.rs | 94 +----------------------- src/tools/unstable-book-gen/src/tests.rs | 90 +++++++++++++++++++++++ 2 files changed, 91 insertions(+), 93 deletions(-) create mode 100644 src/tools/unstable-book-gen/src/tests.rs diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs index 4238d7afa341c..12b35a11c0d66 100644 --- a/src/tools/unstable-book-gen/src/main.rs +++ b/src/tools/unstable-book-gen/src/main.rs @@ -664,96 +664,4 @@ fn main() { } #[cfg(test)] -mod tests { - use std::path::{Path, PathBuf}; - - use super::{parse_compiler_flags, parse_one_entry, skip_ws_comments_and_attrs}; - - #[test] - fn parses_unstable_options_entries() { - let options_rs = r#" -options! { - UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", - - // tidy-alphabetical-start - #[rustc_lint_opt_deny_field_access("test attr")] - allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], - "only allow the listed language features to be enabled in code (comma separated)"), - dump_mir: Option = (None, parse_opt_string, [UNTRACKED], - "dump MIR state to file. - `val` is used to select which passes and functions to dump."), - join_lines: bool = (false, parse_bool, [TRACKED], - "join \ - continued lines"), - help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), - // tidy-alphabetical-end -} -"#; - - let features = parse_compiler_flags(options_rs, Path::new("options.rs")); - - assert!(features.contains_key("allow_features")); - assert!(features.contains_key("dump_mir")); - assert!(features.contains_key("join_lines")); - assert!(!features.contains_key("help")); - - assert_eq!( - features["dump_mir"].description.as_deref(), - Some( - "dump MIR state to file.\n `val` is used to select which passes and functions to dump." - ), - ); - assert_eq!(features["join_lines"].description.as_deref(), Some("join continued lines"),); - assert_eq!(features["allow_features"].file, PathBuf::from("options.rs")); - assert_eq!(features["allow_features"].line, 7); - } - - #[test] - fn parse_one_entry_skips_help_description_and_advances() { - let section = r#" -help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), -join_lines: bool = (false, parse_bool, [TRACKED], "join \ - continued lines"), -"#; - let section = section.trim_start(); - - let help_entry = parse_one_entry(section, 0); - assert_eq!(help_entry.name, "help"); - assert!(help_entry.description.is_none()); - - let mut next_idx = help_entry.next_idx; - skip_ws_comments_and_attrs(section, &mut next_idx); - let next_entry = parse_one_entry(section, next_idx); - - assert_eq!(next_entry.name, "join_lines"); - assert_eq!(next_entry.description.as_deref(), Some("join continued lines"),); - } - - #[test] - fn parse_one_entry_accepts_optional_trailing_metadata() { - let entry = r#" -deprecated_flag: bool = (false, parse_no_value, [UNTRACKED], "deprecated flag", - is_deprecated_and_do_nothing: true), -"#; - let entry = entry.trim_start(); - - let parsed = parse_one_entry(entry, 0); - assert_eq!(parsed.name, "deprecated_flag"); - assert_eq!(parsed.description.as_deref(), Some("deprecated flag")); - } - - #[test] - fn parses_real_unstable_options_file() { - let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let options_path = manifest_dir.join("../../../compiler/rustc_session/src/options.rs"); - let options_rs = std::fs::read_to_string(&options_path).unwrap(); - let features = parse_compiler_flags(&options_rs, &options_path); - - assert!(features.contains_key("allow_features")); - assert!(features.contains_key("dump_mir")); - assert!(features.contains_key("unstable_options")); - assert!(!features.contains_key("help")); - assert!(features["dump_mir"].line > 0); - assert!(features["dump_mir"].description.as_deref().unwrap().starts_with("dump MIR state")); - } -} +mod tests; diff --git a/src/tools/unstable-book-gen/src/tests.rs b/src/tools/unstable-book-gen/src/tests.rs new file mode 100644 index 0000000000000..98008a45be629 --- /dev/null +++ b/src/tools/unstable-book-gen/src/tests.rs @@ -0,0 +1,90 @@ +use std::path::{Path, PathBuf}; + +use super::{parse_compiler_flags, parse_one_entry, skip_ws_comments_and_attrs}; + +#[test] +fn parses_unstable_options_entries() { + let tidy_start = "// tidy-alphabetical-start"; + let tidy_end = "// tidy-alphabetical-end"; + let options_rs = format!( + "\n\ + options! {{\n\ + \x20 UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, \"Z\", \"unstable\",\n\ + \n\ + \x20 {tidy_start}\n\ + \x20 #[rustc_lint_opt_deny_field_access(\"test attr\")]\n\ + \x20 allow_features: Option> = (None, parse_opt_comma_list, [TRACKED],\n\ + \x20 \"only allow the listed language features to be enabled in code (comma separated)\"),\n\ + \x20 dump_mir: Option = (None, parse_opt_string, [UNTRACKED],\n\ + \x20 \"dump MIR state to file.\n\ + \x20 `val` is used to select which passes and functions to dump.\"),\n\ + \x20 join_lines: bool = (false, parse_bool, [TRACKED],\n\ + \x20 \"join \\\n\ + \x20 continued lines\"),\n\ + \x20 help: bool = (false, parse_no_value, [UNTRACKED], \"Print unstable compiler options\"),\n\ + \x20 {tidy_end}\n\ + }}\n" + ); + + let features = parse_compiler_flags(&options_rs, Path::new("options.rs")); + + assert!(features.contains_key("allow_features")); + assert!(features.contains_key("dump_mir")); + assert!(features.contains_key("join_lines")); + assert!(!features.contains_key("help")); + + assert_eq!( + features["dump_mir"].description.as_deref(), + Some( + "dump MIR state to file.\n `val` is used to select which passes and functions to dump." + ), + ); + assert_eq!(features["join_lines"].description.as_deref(), Some("join continued lines"),); + assert_eq!(features["allow_features"].file, PathBuf::from("options.rs")); + assert_eq!(features["allow_features"].line, 7); +} + +#[test] +fn parse_one_entry_skips_help_description_and_advances() { + let section = "\ +help: bool = (false, parse_no_value, [UNTRACKED], \"Print unstable compiler options\"),\n\ +join_lines: bool = (false, parse_bool, [TRACKED], \"join \\\n\ + continued lines\"),\n"; + + let help_entry = parse_one_entry(section, 0); + assert_eq!(help_entry.name, "help"); + assert!(help_entry.description.is_none()); + + let mut next_idx = help_entry.next_idx; + skip_ws_comments_and_attrs(section, &mut next_idx); + let next_entry = parse_one_entry(section, next_idx); + + assert_eq!(next_entry.name, "join_lines"); + assert_eq!(next_entry.description.as_deref(), Some("join continued lines"),); +} + +#[test] +fn parse_one_entry_accepts_optional_trailing_metadata() { + let entry = "\ +deprecated_flag: bool = (false, parse_no_value, [UNTRACKED], \"deprecated flag\",\n\ + is_deprecated_and_do_nothing: true),\n"; + + let parsed = parse_one_entry(entry, 0); + assert_eq!(parsed.name, "deprecated_flag"); + assert_eq!(parsed.description.as_deref(), Some("deprecated flag")); +} + +#[test] +fn parses_real_unstable_options_file() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let options_path = manifest_dir.join("../../../compiler/rustc_session/src/options.rs"); + let options_rs = std::fs::read_to_string(&options_path).unwrap(); + let features = parse_compiler_flags(&options_rs, &options_path); + + assert!(features.contains_key("allow_features")); + assert!(features.contains_key("dump_mir")); + assert!(features.contains_key("unstable_options")); + assert!(!features.contains_key("help")); + assert!(features["dump_mir"].line > 0); + assert!(features["dump_mir"].description.as_deref().unwrap().starts_with("dump MIR state")); +} From b4f2c8a08e8752f016978b96d09d473bc7bc1a7b Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:31:41 -0400 Subject: [PATCH 3/5] docs: parse unstable options with syn --- Cargo.lock | 2 + compiler/rustc_session/src/options.rs | 586 +----------------- .../rustc_session/src/options/unstable.rs | 585 +++++++++++++++++ src/tools/unstable-book-gen/Cargo.toml | 2 + src/tools/unstable-book-gen/src/main.rs | 571 ++++------------- src/tools/unstable-book-gen/src/tests.rs | 98 ++- 6 files changed, 758 insertions(+), 1086 deletions(-) create mode 100644 compiler/rustc_session/src/options/unstable.rs diff --git a/Cargo.lock b/Cargo.lock index 539f66ffb3bb3..1be93aac6e086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6088,6 +6088,8 @@ name = "unstable-book-gen" version = "0.1.0" dependencies = [ "num-traits", + "proc-macro2", + "syn 2.0.110", "tidy", ] diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index fb1b3c8679481..836f7d1cb456f 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2213,588 +2213,4 @@ options! { // - src/doc/rustc/src/codegen-options/index.md } -options! { - UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", - - // If you add a new option, please update: - // - compiler/rustc_interface/src/tests.rs - // - src/doc/unstable-book/src/compiler-flags - - // tidy-alphabetical-start - allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], - "only allow the listed language features to be enabled in code (comma separated)"), - always_encode_mir: bool = (false, parse_bool, [TRACKED], - "encode MIR of all functions into the crate metadata (default: no)"), - annotate_moves: AnnotateMoves = (AnnotateMoves::Disabled, parse_annotate_moves, [TRACKED], - "emit debug info for compiler-generated move and copy operations \ - to make them visible in profilers. Can be a boolean or a size limit in bytes (default: disabled)"), - assert_incr_state: Option = (None, parse_opt_string, [UNTRACKED], - "assert that the incremental cache is in given state: \ - either `loaded` or `not-loaded`."), - assume_incomplete_release: bool = (false, parse_bool, [TRACKED], - "make cfg(version) treat the current version as incomplete (default: no)"), - autodiff: Vec = (Vec::new(), parse_autodiff, [TRACKED], - "a list of autodiff flags to enable - Mandatory setting: - `=Enable` - Optional extra settings: - `=PrintTA` - `=PrintAA` - `=PrintPerf` - `=PrintSteps` - `=PrintModBefore` - `=PrintModAfter` - `=PrintModFinal` - `=PrintPasses`, - `=NoPostopt` - `=LooseTypes` - `=Inline` - Multiple options can be combined with commas."), - #[rustc_lint_opt_deny_field_access("use `Session::binary_dep_depinfo` instead of this field")] - binary_dep_depinfo: bool = (false, parse_bool, [TRACKED], - "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \ - (default: no)"), - box_noalias: bool = (true, parse_bool, [TRACKED], - "emit noalias metadata for box (default: yes)"), - branch_protection: Option = (None, parse_branch_protection, [TRACKED TARGET_MODIFIER], - "set options for branch target identification and pointer authentication on AArch64"), - build_sdylib_interface: bool = (false, parse_bool, [UNTRACKED], - "whether the stable interface is being built"), - cache_proc_macros: bool = (false, parse_bool, [TRACKED], - "cache the results of derive proc macro invocations (potentially unsound!) (default: no"), - cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], - "instrument control-flow architecture protection"), - check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED], - "show all expected values in check-cfg diagnostics (default: no)"), - checksum_hash_algorithm: Option = (None, parse_cargo_src_file_hash, [TRACKED], - "hash algorithm of source files used to check freshness in cargo (`blake3` or `sha256`)"), - codegen_backend: Option = (None, parse_opt_string, [TRACKED], - "the backend to use"), - codegen_source_order: bool = (false, parse_bool, [UNTRACKED], - "emit mono items in the order of spans in source files (default: no)"), - contract_checks: Option = (None, parse_opt_bool, [TRACKED], - "emit runtime checks for contract pre- and post-conditions (default: no)"), - coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED], - "control details of coverage instrumentation"), - crate_attr: Vec = (Vec::new(), parse_string_push, [TRACKED], - "inject the given attribute in the crate"), - cross_crate_inline_threshold: InliningThreshold = (InliningThreshold::Sometimes(100), parse_inlining_threshold, [TRACKED], - "threshold to allow cross crate inlining of functions"), - debug_info_for_profiling: bool = (false, parse_bool, [TRACKED], - "emit discriminators and other data necessary for AutoFDO"), - debug_info_type_line_numbers: bool = (false, parse_bool, [TRACKED], - "emit type and line information for additional data types (default: no)"), - debuginfo_compression: DebugInfoCompression = (DebugInfoCompression::None, parse_debuginfo_compression, [TRACKED], - "compress debug info sections (none, zlib, zstd, default: none)"), - deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED], - "deduplicate identical diagnostics (default: yes)"), - default_visibility: Option = (None, parse_opt_symbol_visibility, [TRACKED], - "overrides the `default_visibility` setting of the target"), - dep_info_omit_d_target: bool = (false, parse_bool, [TRACKED], - "in dep-info output, omit targets for tracking dependencies of the dep-info files \ - themselves (default: no)"), - direct_access_external_data: Option = (None, parse_opt_bool, [TRACKED], - "Direct or use GOT indirect to reference external data symbols"), - dual_proc_macros: bool = (false, parse_bool, [TRACKED], - "load proc macros for both target and host, but only link to the target (default: no)"), - dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], - "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ - (default: no)"), - dump_mir: Option = (None, parse_opt_string, [UNTRACKED], - "dump MIR state to file. - `val` is used to select which passes and functions to dump. For example: - `all` matches all passes and functions, - `foo` matches all passes for functions whose name contains 'foo', - `foo & ConstProp` only the 'ConstProp' pass for function names containing 'foo', - `foo | bar` all passes for function names containing 'foo' or 'bar'."), - dump_mir_dataflow: bool = (false, parse_bool, [UNTRACKED], - "in addition to `.mir` files, create graphviz `.dot` files with dataflow results \ - (default: no)"), - dump_mir_dir: String = ("mir_dump".to_string(), parse_string, [UNTRACKED], - "the directory the MIR is dumped into (default: `mir_dump`)"), - dump_mir_exclude_alloc_bytes: bool = (false, parse_bool, [UNTRACKED], - "exclude the raw bytes of allocations when dumping MIR (used in tests) (default: no)"), - dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], - "exclude the pass number when dumping MIR (used in tests) (default: no)"), - dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], - "in addition to `.mir` files, create graphviz `.dot` files (default: no)"), - dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled, - parse_switch_with_opt_path, [UNTRACKED], - "output statistics about monomorphization collection"), - dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED], - "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"), - #[rustc_lint_opt_deny_field_access("use `Session::dwarf_version` instead of this field")] - dwarf_version: Option = (None, parse_opt_number, [TRACKED], - "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), - dylib_lto: bool = (false, parse_bool, [UNTRACKED], - "enables LTO for dylib crate type"), - eagerly_emit_delayed_bugs: bool = (false, parse_bool, [UNTRACKED], - "emit delayed bugs eagerly as errors instead of stashing them and emitting \ - them only if an error has not been emitted"), - ehcont_guard: bool = (false, parse_bool, [TRACKED], - "generate Windows EHCont Guard tables"), - embed_metadata: bool = (true, parse_bool, [TRACKED], - "embed metadata in rlibs and dylibs (default: yes)"), - embed_source: bool = (false, parse_bool, [TRACKED], - "embed source text in DWARF debug sections (default: no)"), - emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], - "emit a section containing stack size metadata (default: no)"), - emscripten_wasm_eh: bool = (true, parse_bool, [TRACKED], - "Use WebAssembly error handling for wasm32-unknown-emscripten"), - enforce_type_length_limit: bool = (false, parse_bool, [TRACKED], - "enforce the type length limit when monomorphizing instances in codegen"), - experimental_default_bounds: bool = (false, parse_bool, [TRACKED], - "enable default bounds for experimental group of auto traits"), - export_executable_symbols: bool = (false, parse_bool, [TRACKED], - "export symbols from executables, as if they were dynamic libraries"), - external_clangrt: bool = (false, parse_bool, [UNTRACKED], - "rely on user specified linker commands to find clangrt"), - extra_const_ub_checks: bool = (false, parse_bool, [TRACKED], - "turns on more checks to detect const UB, which can be slow (default: no)"), - #[rustc_lint_opt_deny_field_access("use `Session::fewer_names` instead of this field")] - fewer_names: Option = (None, parse_opt_bool, [TRACKED], - "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ - (default: no)"), - fixed_x18: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], - "make the x18 register reserved on AArch64 (default: no)"), - flatten_format_args: bool = (true, parse_bool, [TRACKED], - "flatten nested format_args!() and literals into a simplified format_args!() call \ - (default: yes)"), - fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED], - "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \ - `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"), - force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], - "force all crates to be `rustc_private` unstable (default: no)"), - function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], - "replace returns with jumps to `__x86_return_thunk` (default: `keep`)"), - function_sections: Option = (None, parse_opt_bool, [TRACKED], - "whether each function should go in its own section"), - future_incompat_test: bool = (false, parse_bool, [UNTRACKED], - "forces all lints to be future incompatible, used for internal testing (default: no)"), - graphviz_dark_mode: bool = (false, parse_bool, [UNTRACKED], - "use dark-themed colors in graphviz output (default: no)"), - graphviz_font: String = ("Courier, monospace".to_string(), parse_string, [UNTRACKED], - "use the given `fontname` in graphviz output; can be overridden by setting \ - environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"), - has_thread_local: Option = (None, parse_opt_bool, [TRACKED], - "explicitly enable the `cfg(target_thread_local)` directive"), - help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), - higher_ranked_assumptions: bool = (false, parse_bool, [TRACKED], - "allow deducing higher-ranked outlives assumptions from coroutines when proving auto traits"), - hint_mostly_unused: bool = (false, parse_bool, [TRACKED], - "hint that most of this crate will go unused, to minimize work for uncalled functions"), - human_readable_cgu_names: bool = (false, parse_bool, [TRACKED], - "generate human-readable, predictable names for codegen units (default: no)"), - identify_regions: bool = (false, parse_bool, [UNTRACKED], - "display unnamed regions as `'`, using a non-ident unique id (default: no)"), - ignore_directory_in_diagnostics_source_blocks: Vec = (Vec::new(), parse_string_push, [UNTRACKED], - "do not display the source code block in diagnostics for files in the directory"), - incremental_ignore_spans: bool = (false, parse_bool, [TRACKED], - "ignore spans during ICH computation -- used for testing (default: no)"), - incremental_info: bool = (false, parse_bool, [UNTRACKED], - "print high-level information about incremental reuse (or the lack thereof) \ - (default: no)"), - incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED], - "verify extended properties for incr. comp. (default: no): - - hashes of green query instances - - hash collisions of query keys - - hash collisions when creating dep-nodes"), - indirect_branch_cs_prefix: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], - "add `cs` prefix to `call` and `jmp` to indirect thunks (default: no)"), - inline_llvm: bool = (true, parse_bool, [TRACKED], - "enable LLVM inlining (default: yes)"), - inline_mir: Option = (None, parse_opt_bool, [TRACKED], - "enable MIR inlining (default: no)"), - inline_mir_forwarder_threshold: Option = (None, parse_opt_number, [TRACKED], - "inlining threshold when the caller is a simple forwarding function (default: 30)"), - inline_mir_hint_threshold: Option = (None, parse_opt_number, [TRACKED], - "inlining threshold for functions with inline hint (default: 100)"), - inline_mir_preserve_debug: Option = (None, parse_opt_bool, [TRACKED], - "when MIR inlining, whether to preserve debug info for callee variables \ - (default: preserve for debuginfo != None, otherwise remove)"), - inline_mir_threshold: Option = (None, parse_opt_number, [TRACKED], - "a default MIR inlining threshold (default: 50)"), - input_stats: bool = (false, parse_bool, [UNTRACKED], - "print some statistics about AST and HIR (default: no)"), - instrument_mcount: bool = (false, parse_bool, [TRACKED], - "insert function instrument code for mcount-based tracing (default: no)"), - instrument_xray: Option = (None, parse_instrument_xray, [TRACKED], - "insert function instrument code for XRay-based tracing (default: no) - Optional extra settings: - `=always` - `=never` - `=ignore-loops` - `=instruction-threshold=N` - `=skip-entry` - `=skip-exit` - Multiple options can be combined with commas."), - large_data_threshold: Option = (None, parse_opt_number, [TRACKED], - "set the threshold for objects to be stored in a \"large data\" section \ - (only effective with -Ccode-model=medium, default: 65536)"), - layout_seed: Option = (None, parse_opt_number, [TRACKED], - "seed layout randomization"), - link_directives: bool = (true, parse_bool, [TRACKED], - "honor #[link] directives in the compiled crate (default: yes)"), - link_native_libraries: bool = (true, parse_bool, [UNTRACKED], - "link native libraries in the linker invocation (default: yes)"), - link_only: bool = (false, parse_bool, [TRACKED], - "link the `.rlink` file generated by `-Z no-link` (default: no)"), - lint_llvm_ir: bool = (false, parse_bool, [TRACKED], - "lint LLVM IR (default: no)"), - lint_mir: bool = (false, parse_bool, [UNTRACKED], - "lint MIR before and after each transformation"), - llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED], - "a list of module flags to pass to LLVM (space separated)"), - llvm_plugins: Vec = (Vec::new(), parse_list, [TRACKED], - "a list LLVM plugins to enable (space separated)"), - llvm_time_trace: bool = (false, parse_bool, [UNTRACKED], - "generate JSON tracing data file from LLVM data (default: no)"), - location_detail: LocationDetail = (LocationDetail::all(), parse_location_detail, [TRACKED], - "what location details should be tracked when using caller_location, either \ - `none`, or a comma separated list of location details, for which \ - valid options are `file`, `line`, and `column` (default: `file,line,column`)"), - ls: Vec = (Vec::new(), parse_list, [UNTRACKED], - "decode and print various parts of the crate metadata for a library crate \ - (space separated)"), - macro_backtrace: bool = (false, parse_bool, [UNTRACKED], - "show macro backtraces (default: no)"), - macro_stats: bool = (false, parse_bool, [UNTRACKED], - "print some statistics about macro expansions (default: no)"), - maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED], - "save as much information as possible about the correspondence between MIR and HIR \ - as source scopes (default: no)"), - merge_functions: Option = (None, parse_merge_functions, [TRACKED], - "control the operation of the MergeFunctions LLVM pass, taking \ - the same values as the target option of the same name"), - meta_stats: bool = (false, parse_bool, [UNTRACKED], - "gather metadata statistics (default: no)"), - metrics_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], - "the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"), - min_function_alignment: Option = (None, parse_align, [TRACKED], - "align all functions to at least this many bytes. Must be a power of 2"), - min_recursion_limit: Option = (None, parse_opt_number, [TRACKED], - "set a minimum recursion limit (final limit = max(this, recursion_limit_from_crate))"), - mir_emit_retag: bool = (false, parse_bool, [TRACKED], - "emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ - (default: no)"), - mir_enable_passes: Vec<(String, bool)> = (Vec::new(), parse_list_with_polarity, [TRACKED], - "use like `-Zmir-enable-passes=+DestinationPropagation,-InstSimplify`. Forces the \ - specified passes to be enabled, overriding all other checks. In particular, this will \ - enable unsound (known-buggy and hence usually disabled) passes without further warning! \ - Passes that are not specified are enabled or disabled by other flags as usual."), - mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED], - "include extra comments in mir pretty printing, like line numbers and statement indices, \ - details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"), - mir_opt_bisect_limit: Option = (None, parse_opt_number, [TRACKED], - "limit the number of MIR optimization pass executions (global across all bodies). \ - Pass executions after this limit are skipped and reported. (default: no limit)"), - #[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")] - mir_opt_level: Option = (None, parse_opt_number, [TRACKED], - "MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"), - mir_preserve_ub: bool = (false, parse_bool, [TRACKED], - "keep place mention statements and reads in trivial SwitchInt terminators, which are interpreted \ - e.g., by miri; implies -Zmir-opt-level=0 (default: no)"), - mir_strip_debuginfo: MirStripDebugInfo = (MirStripDebugInfo::None, parse_mir_strip_debuginfo, [TRACKED], - "Whether to remove some of the MIR debug info from methods. Default: None"), - move_size_limit: Option = (None, parse_opt_number, [TRACKED], - "the size at which the `large_assignments` lint starts to be emitted"), - mutable_noalias: bool = (true, parse_bool, [TRACKED], - "emit noalias metadata for mutable references (default: yes)"), - namespaced_crates: bool = (false, parse_bool, [TRACKED], - "allow crates to be namespaced by other crates (default: no)"), - next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED], - "enable and configure the next generation trait solver used by rustc"), - nll_facts: bool = (false, parse_bool, [UNTRACKED], - "dump facts from NLL analysis into side files (default: no)"), - nll_facts_dir: String = ("nll-facts".to_string(), parse_string, [UNTRACKED], - "the directory the NLL facts are dumped into (default: `nll-facts`)"), - no_analysis: bool = (false, parse_no_value, [UNTRACKED], - "parse and expand the source, but run no analysis"), - no_codegen: bool = (false, parse_no_value, [TRACKED_NO_CRATE_HASH], - "run all passes except codegen; no output"), - no_generate_arange_section: bool = (false, parse_no_value, [TRACKED], - "omit DWARF address ranges that give faster lookups"), - no_implied_bounds_compat: bool = (false, parse_bool, [TRACKED], - "disable the compatibility version of the `implied_bounds_ty` query"), - no_leak_check: bool = (false, parse_no_value, [UNTRACKED], - "disable the 'leak check' for subtyping; unsound, but useful for tests"), - no_link: bool = (false, parse_no_value, [TRACKED], - "compile without linking"), - no_parallel_backend: bool = (false, parse_no_value, [UNTRACKED], - "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"), - no_profiler_runtime: bool = (false, parse_no_value, [TRACKED], - "prevent automatic injection of the profiler_builtins crate"), - no_steal_thir: bool = (false, parse_bool, [UNTRACKED], - "don't steal the THIR when we're done with it; useful for rustc drivers (default: no)"), - no_trait_vptr: bool = (false, parse_no_value, [TRACKED], - "disable generation of trait vptr in vtable for upcasting"), - no_unique_section_names: bool = (false, parse_bool, [TRACKED], - "do not use unique names for text and data sections when -Z function-sections is used"), - normalize_docs: bool = (false, parse_bool, [TRACKED], - "normalize associated items in rustdoc when generating documentation"), - offload: Vec = (Vec::new(), parse_offload, [TRACKED], - "a list of offload flags to enable - Mandatory setting: - `=Enable` - Currently the only option available"), - on_broken_pipe: OnBrokenPipe = (OnBrokenPipe::Default, parse_on_broken_pipe, [TRACKED], - "behavior of std::io::ErrorKind::BrokenPipe (SIGPIPE)"), - osx_rpath_install_name: bool = (false, parse_bool, [TRACKED], - "pass `-install_name @rpath/...` to the macOS linker (default: no)"), - packed_bundled_libs: bool = (false, parse_bool, [TRACKED], - "change rlib format to store native libraries as archives"), - panic_abort_tests: bool = (false, parse_bool, [TRACKED], - "support compiling tests with panic=abort (default: no)"), - panic_in_drop: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy, [TRACKED], - "panic strategy for panics in drops"), - parse_crate_root_only: bool = (false, parse_bool, [UNTRACKED], - "parse the crate root file only; do not parse other files, compile, assemble, or link \ - (default: no)"), - patchable_function_entry: PatchableFunctionEntry = (PatchableFunctionEntry::default(), parse_patchable_function_entry, [TRACKED], - "nop padding at function entry"), - plt: Option = (None, parse_opt_bool, [TRACKED], - "whether to use the PLT when calling into shared libraries; - only has effect for PIC code on systems with ELF binaries - (default: PLT is disabled if full relro is enabled on x86_64)"), - polonius: Polonius = (Polonius::default(), parse_polonius, [TRACKED], - "enable polonius-based borrow-checker (default: no)"), - pre_link_arg: (/* redirected to pre_link_args */) = ((), parse_string_push, [UNTRACKED], - "a single extra argument to prepend the linker invocation (can be used several times)"), - pre_link_args: Vec = (Vec::new(), parse_list, [UNTRACKED], - "extra arguments to prepend to the linker invocation (space separated)"), - precise_enum_drop_elaboration: bool = (true, parse_bool, [TRACKED], - "use a more precise version of drop elaboration for matches on enums (default: yes). \ - This results in better codegen, but has caused miscompilations on some tier 2 platforms. \ - See #77382 and #74551."), - #[rustc_lint_opt_deny_field_access("use `Session::print_codegen_stats` instead of this field")] - print_codegen_stats: bool = (false, parse_bool, [UNTRACKED], - "print codegen statistics (default: no)"), - print_llvm_passes: bool = (false, parse_bool, [UNTRACKED], - "print the LLVM optimization passes being run (default: no)"), - print_mono_items: bool = (false, parse_bool, [UNTRACKED], - "print the result of the monomorphization collection pass (default: no)"), - print_type_sizes: bool = (false, parse_bool, [UNTRACKED], - "print layout information for each type encountered (default: no)"), - proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], - "show backtraces for panics during proc-macro execution (default: no)"), - proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, - parse_proc_macro_execution_strategy, [UNTRACKED], - "how to run proc-macro code (default: same-thread)"), - profile_closures: bool = (false, parse_no_value, [UNTRACKED], - "profile size of closures"), - profile_sample_use: Option = (None, parse_opt_pathbuf, [TRACKED], - "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), - profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], - "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), - query_dep_graph: bool = (false, parse_bool, [UNTRACKED], - "enable queries of the dependency graph for regression testing (default: no)"), - randomize_layout: bool = (false, parse_bool, [TRACKED], - "randomize the layout of types (default: no)"), - reg_struct_return: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], - "On x86-32 targets, it overrides the default ABI to return small structs in registers. - It is UNSOUND to link together crates that use different values for this flag!"), - regparm: Option = (None, parse_opt_number, [TRACKED TARGET_MODIFIER], - "On x86-32 targets, setting this to N causes the compiler to pass N arguments \ - in registers EAX, EDX, and ECX instead of on the stack for\ - \"C\", \"cdecl\", and \"stdcall\" fn.\ - It is UNSOUND to link together crates that use different values for this flag!"), - relax_elf_relocations: Option = (None, parse_opt_bool, [TRACKED], - "whether ELF relocations can be relaxed"), - remap_cwd_prefix: Option = (None, parse_opt_pathbuf, [TRACKED], - "remap paths under the current working directory to this path prefix"), - remark_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], - "directory into which to write optimization remarks (if not specified, they will be \ -written to standard error output)"), - retpoline: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], - "enables retpoline-indirect-branches and retpoline-indirect-calls target features (default: no)"), - retpoline_external_thunk: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], - "enables retpoline-external-thunk, retpoline-indirect-branches and retpoline-indirect-calls \ - target features (default: no)"), - #[rustc_lint_opt_deny_field_access("use `Session::sanitizers()` instead of this field")] - sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED TARGET_MODIFIER], - "use a sanitizer"), - sanitizer_cfi_canonical_jump_tables: Option = (Some(true), parse_opt_bool, [TRACKED], - "enable canonical jump tables (default: yes)"), - sanitizer_cfi_generalize_pointers: Option = (None, parse_opt_bool, [TRACKED], - "enable generalizing pointer types (default: no)"), - sanitizer_cfi_normalize_integers: Option = (None, parse_opt_bool, [TRACKED TARGET_MODIFIER], - "enable normalizing integer types (default: no)"), - sanitizer_dataflow_abilist: Vec = (Vec::new(), parse_comma_list, [TRACKED], - "additional ABI list files that control how shadow parameters are passed (comma separated)"), - sanitizer_kcfi_arity: Option = (None, parse_opt_bool, [TRACKED], - "enable KCFI arity indicator (default: no)"), - sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED], - "enable origins tracking in MemorySanitizer"), - sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], - "enable recovery for selected sanitizers"), - saturating_float_casts: Option = (None, parse_opt_bool, [TRACKED], - "make float->int casts UB-free: numbers outside the integer type's range are clipped to \ - the max/min integer respectively, and NaN is mapped to 0 (default: yes)"), - self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled, - parse_switch_with_opt_path, [UNTRACKED], - "run the self profiler and output the raw event data"), - self_profile_counter: String = ("wall-time".to_string(), parse_string, [UNTRACKED], - "counter used by the self profiler (default: `wall-time`), one of: - `wall-time` (monotonic clock, i.e. `std::time::Instant`) - `instructions:u` (retired instructions, userspace-only) - `instructions-minus-irqs:u` (subtracting hardware interrupt counts for extra accuracy)" - ), - /// keep this in sync with the event filter names in librustc_data_structures/profiling.rs - self_profile_events: Option> = (None, parse_opt_comma_list, [UNTRACKED], - "specify the events recorded by the self profiler; - for example: `-Z self-profile-events=default,query-keys` - all options: none, all, default, generic-activity, query-provider, query-cache-hit - query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), - share_generics: Option = (None, parse_opt_bool, [TRACKED], - "make the current crate share its generic instantiations"), - shell_argfiles: bool = (false, parse_bool, [UNTRACKED], - "allow argument files to be specified with POSIX \"shell-style\" argument quoting"), - simulate_remapped_rust_src_base: Option = (None, parse_opt_pathbuf, [TRACKED], - "simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \ - to rust's source base directory. only meant for testing purposes"), - small_data_threshold: Option = (None, parse_opt_number, [TRACKED], - "Set the threshold for objects to be stored in a \"small data\" section"), - span_debug: bool = (false, parse_bool, [UNTRACKED], - "forward proc_macro::Span's `Debug` impl to `Span`"), - /// o/w tests have closure@path - span_free_formats: bool = (false, parse_bool, [UNTRACKED], - "exclude spans when debug-printing compiler state (default: no)"), - split_dwarf_inlining: bool = (false, parse_bool, [TRACKED], - "provide minimal debug info in the object/executable to facilitate online \ - symbolication/stack traces in the absence of .dwo/.dwp files when using Split DWARF"), - split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED], - "split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform) - (default: `split`) - - `split`: sections which do not require relocation are written into a DWARF object (`.dwo`) - file which is ignored by the linker - `single`: sections which do not require relocation are written into object file but ignored - by the linker"), - split_dwarf_out_dir : Option = (None, parse_opt_pathbuf, [TRACKED], - "location for writing split DWARF objects (`.dwo`) if enabled"), - split_lto_unit: Option = (None, parse_opt_bool, [TRACKED], - "enable LTO unit splitting (default: no)"), - src_hash_algorithm: Option = (None, parse_src_file_hash, [TRACKED], - "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), - #[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")] - stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED], - "control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"), - staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED], - "allow staticlibs to have rust dylib dependencies"), - staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED], - "prefer dynamic linking to static linking for staticlibs (default: no)"), - strict_init_checks: bool = (false, parse_bool, [TRACKED], - "control if mem::uninitialized and mem::zeroed panic on more UB"), - #[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")] - teach: bool = (false, parse_bool, [TRACKED], - "show extended diagnostic help (default: no)"), - temps_dir: Option = (None, parse_opt_string, [UNTRACKED], - "the directory the intermediate files are written to"), - terminal_urls: TerminalUrl = (TerminalUrl::No, parse_terminal_url, [UNTRACKED], - "use the OSC 8 hyperlink terminal specification to print hyperlinks in the compiler output"), - #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] - thinlto: Option = (None, parse_opt_bool, [TRACKED], - "enable ThinLTO when possible"), - /// We default to 1 here since we want to behave like - /// a sequential compiler for now. This'll likely be adjusted - /// in the future. Note that -Zthreads=0 is the way to get - /// the num_cpus behavior. - #[rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field")] - threads: usize = (1, parse_threads, [UNTRACKED], - "use a thread pool with N threads"), - time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], - "measure time of each LLVM pass (default: no)"), - time_passes: bool = (false, parse_bool, [UNTRACKED], - "measure time of each rustc pass (default: no)"), - time_passes_format: TimePassesFormat = (TimePassesFormat::Text, parse_time_passes_format, [UNTRACKED], - "the format to use for -Z time-passes (`text` (default) or `json`)"), - tiny_const_eval_limit: bool = (false, parse_bool, [TRACKED], - "sets a tiny, non-configurable limit for const eval; useful for compiler tests"), - #[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")] - tls_model: Option = (None, parse_tls_model, [TRACKED], - "choose the TLS model to use (`rustc --print tls-models` for details)"), - trace_macros: bool = (false, parse_bool, [UNTRACKED], - "for every macro invocation, print its name and arguments (default: no)"), - track_diagnostics: bool = (false, parse_bool, [UNTRACKED], - "tracks where in rustc a diagnostic was emitted"), - translate_remapped_path_to_local_path: bool = (true, parse_bool, [TRACKED], - "translate remapped paths into local paths when possible (default: yes)"), - trap_unreachable: Option = (None, parse_opt_bool, [TRACKED], - "generate trap instructions for unreachable intrinsics (default: use target setting, usually yes)"), - treat_err_as_bug: Option> = (None, parse_treat_err_as_bug, [TRACKED], - "treat the `val`th error that occurs as bug (default if not specified: 0 - don't treat errors as bugs. \ - default if specified without a value: 1 - treat the first error as bug)"), - trim_diagnostic_paths: bool = (true, parse_bool, [UNTRACKED], - "in diagnostics, use heuristics to shorten paths referring to items"), - tune_cpu: Option = (None, parse_opt_string, [TRACKED], - "select processor to schedule for (`rustc --print target-cpus` for details)"), - #[rustc_lint_opt_deny_field_access("use `TyCtxt::use_typing_mode_borrowck` instead of this field")] - typing_mode_borrowck: bool = (false, parse_bool, [TRACKED], - "enable `TypingMode::Borrowck`, changing the way opaque types are handled during MIR borrowck"), - #[rustc_lint_opt_deny_field_access("use `Session::ub_checks` instead of this field")] - ub_checks: Option = (None, parse_opt_bool, [TRACKED], - "emit runtime checks for Undefined Behavior (default: -Cdebug-assertions)"), - ui_testing: bool = (false, parse_bool, [UNTRACKED], - "emit compiler diagnostics in a form suitable for UI testing (default: no)"), - uninit_const_chunk_threshold: usize = (16, parse_number, [TRACKED], - "allow generating const initializers with mixed init/uninit chunks, \ - and set the maximum number of chunks for which this is allowed (default: 16)"), - unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED], - "take the brakes off const evaluation. NOTE: this is unsound (default: no)"), - unpretty: Option = (None, parse_unpretty, [UNTRACKED], - "present the input source, unstable (and less-pretty) variants; - `normal`, `identified`, - `expanded`, `expanded,identified`, - `expanded,hygiene` (with internal representations), - `ast-tree` (raw AST before expansion), - `ast-tree,expanded` (raw AST after expansion), - `hir` (the HIR), `hir,identified`, - `hir,typed` (HIR with types for each node), - `hir-tree` (dump the raw HIR), - `thir-tree`, `thir-flat`, - `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"), - unsound_mir_opts: bool = (false, parse_bool, [TRACKED], - "enable unsound and buggy MIR optimizations (default: no)"), - /// This name is kind of confusing: Most unstable options enable something themselves, while - /// this just allows "normal" options to be feature-gated. - /// - /// The main check for `-Zunstable-options` takes place separately from the - /// usual parsing of `-Z` options (see [`crate::config::nightly_options`]), - /// so this boolean value is mostly used for enabling unstable _values_ of - /// stable options. That separate check doesn't handle boolean values, so - /// to avoid an inconsistent state we also forbid them here. - #[rustc_lint_opt_deny_field_access("use `Session::unstable_options` instead of this field")] - unstable_options: bool = (false, parse_no_value, [UNTRACKED], - "adds unstable command line options to rustc interface (default: no)"), - use_ctors_section: Option = (None, parse_opt_bool, [TRACKED], - "use legacy .ctors section for initializers rather than .init_array"), - use_sync_unwind: Option = (None, parse_opt_bool, [TRACKED], - "Generate sync unwind tables instead of async unwind tables (default: no)"), - validate_mir: bool = (false, parse_bool, [UNTRACKED], - "validate MIR after each transformation"), - verbose_asm: bool = (false, parse_bool, [TRACKED], - "add descriptive comments from LLVM to the assembly (may change behavior) (default: no)"), - #[rustc_lint_opt_deny_field_access("use `Session::verbose_internals` instead of this field")] - verbose_internals: bool = (false, parse_bool, [TRACKED_NO_CRATE_HASH], - "in general, enable more debug printouts (default: no)"), - #[rustc_lint_opt_deny_field_access("use `Session::verify_llvm_ir` instead of this field")] - verify_llvm_ir: bool = (false, parse_bool, [TRACKED], - "verify LLVM IR (default: no)"), - virtual_function_elimination: bool = (false, parse_bool, [TRACKED], - "enables dead virtual function elimination optimization. \ - Requires `-Clto[=[fat,yes]]`"), - wasi_exec_model: Option = (None, parse_wasi_exec_model, [TRACKED], - "whether to build a wasi command or reactor"), - // This option only still exists to provide a more gradual transition path for people who need - // the spec-complaint C ABI to be used. - // FIXME remove this after a couple releases - wasm_c_abi: () = ((), parse_wasm_c_abi, [TRACKED], - "use spec-compliant C ABI for `wasm32-unknown-unknown` (deprecated, always enabled)"), - write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED], - "whether long type names should be written to files instead of being printed in errors"), - // tidy-alphabetical-end - - // If you add a new option, please update: - // - compiler/rustc_interface/src/tests.rs - // - src/doc/unstable-book/src/compiler-flags -} +include!("options/unstable.rs"); diff --git a/compiler/rustc_session/src/options/unstable.rs b/compiler/rustc_session/src/options/unstable.rs new file mode 100644 index 0000000000000..dc00bd8a223af --- /dev/null +++ b/compiler/rustc_session/src/options/unstable.rs @@ -0,0 +1,585 @@ +options! { + UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags + + // tidy-alphabetical-start + allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], + "only allow the listed language features to be enabled in code (comma separated)"), + always_encode_mir: bool = (false, parse_bool, [TRACKED], + "encode MIR of all functions into the crate metadata (default: no)"), + annotate_moves: AnnotateMoves = (AnnotateMoves::Disabled, parse_annotate_moves, [TRACKED], + "emit debug info for compiler-generated move and copy operations \ + to make them visible in profilers. Can be a boolean or a size limit in bytes (default: disabled)"), + assert_incr_state: Option = (None, parse_opt_string, [UNTRACKED], + "assert that the incremental cache is in given state: \ + either `loaded` or `not-loaded`."), + assume_incomplete_release: bool = (false, parse_bool, [TRACKED], + "make cfg(version) treat the current version as incomplete (default: no)"), + autodiff: Vec = (Vec::new(), parse_autodiff, [TRACKED], + "a list of autodiff flags to enable + Mandatory setting: + `=Enable` + Optional extra settings: + `=PrintTA` + `=PrintAA` + `=PrintPerf` + `=PrintSteps` + `=PrintModBefore` + `=PrintModAfter` + `=PrintModFinal` + `=PrintPasses`, + `=NoPostopt` + `=LooseTypes` + `=Inline` + Multiple options can be combined with commas."), + #[rustc_lint_opt_deny_field_access("use `Session::binary_dep_depinfo` instead of this field")] + binary_dep_depinfo: bool = (false, parse_bool, [TRACKED], + "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \ + (default: no)"), + box_noalias: bool = (true, parse_bool, [TRACKED], + "emit noalias metadata for box (default: yes)"), + branch_protection: Option = (None, parse_branch_protection, [TRACKED TARGET_MODIFIER], + "set options for branch target identification and pointer authentication on AArch64"), + build_sdylib_interface: bool = (false, parse_bool, [UNTRACKED], + "whether the stable interface is being built"), + cache_proc_macros: bool = (false, parse_bool, [TRACKED], + "cache the results of derive proc macro invocations (potentially unsound!) (default: no"), + cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], + "instrument control-flow architecture protection"), + check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED], + "show all expected values in check-cfg diagnostics (default: no)"), + checksum_hash_algorithm: Option = (None, parse_cargo_src_file_hash, [TRACKED], + "hash algorithm of source files used to check freshness in cargo (`blake3` or `sha256`)"), + codegen_backend: Option = (None, parse_opt_string, [TRACKED], + "the backend to use"), + codegen_source_order: bool = (false, parse_bool, [UNTRACKED], + "emit mono items in the order of spans in source files (default: no)"), + contract_checks: Option = (None, parse_opt_bool, [TRACKED], + "emit runtime checks for contract pre- and post-conditions (default: no)"), + coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED], + "control details of coverage instrumentation"), + crate_attr: Vec = (Vec::new(), parse_string_push, [TRACKED], + "inject the given attribute in the crate"), + cross_crate_inline_threshold: InliningThreshold = (InliningThreshold::Sometimes(100), parse_inlining_threshold, [TRACKED], + "threshold to allow cross crate inlining of functions"), + debug_info_for_profiling: bool = (false, parse_bool, [TRACKED], + "emit discriminators and other data necessary for AutoFDO"), + debug_info_type_line_numbers: bool = (false, parse_bool, [TRACKED], + "emit type and line information for additional data types (default: no)"), + debuginfo_compression: DebugInfoCompression = (DebugInfoCompression::None, parse_debuginfo_compression, [TRACKED], + "compress debug info sections (none, zlib, zstd, default: none)"), + deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED], + "deduplicate identical diagnostics (default: yes)"), + default_visibility: Option = (None, parse_opt_symbol_visibility, [TRACKED], + "overrides the `default_visibility` setting of the target"), + dep_info_omit_d_target: bool = (false, parse_bool, [TRACKED], + "in dep-info output, omit targets for tracking dependencies of the dep-info files \ + themselves (default: no)"), + direct_access_external_data: Option = (None, parse_opt_bool, [TRACKED], + "Direct or use GOT indirect to reference external data symbols"), + dual_proc_macros: bool = (false, parse_bool, [TRACKED], + "load proc macros for both target and host, but only link to the target (default: no)"), + dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ + (default: no)"), + dump_mir: Option = (None, parse_opt_string, [UNTRACKED], + "dump MIR state to file. + `val` is used to select which passes and functions to dump. For example: + `all` matches all passes and functions, + `foo` matches all passes for functions whose name contains 'foo', + `foo & ConstProp` only the 'ConstProp' pass for function names containing 'foo', + `foo | bar` all passes for function names containing 'foo' or 'bar'."), + dump_mir_dataflow: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files with dataflow results \ + (default: no)"), + dump_mir_dir: String = ("mir_dump".to_string(), parse_string, [UNTRACKED], + "the directory the MIR is dumped into (default: `mir_dump`)"), + dump_mir_exclude_alloc_bytes: bool = (false, parse_bool, [UNTRACKED], + "exclude the raw bytes of allocations when dumping MIR (used in tests) (default: no)"), + dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], + "exclude the pass number when dumping MIR (used in tests) (default: no)"), + dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files (default: no)"), + dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [UNTRACKED], + "output statistics about monomorphization collection"), + dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED], + "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"), + #[rustc_lint_opt_deny_field_access("use `Session::dwarf_version` instead of this field")] + dwarf_version: Option = (None, parse_opt_number, [TRACKED], + "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), + dylib_lto: bool = (false, parse_bool, [UNTRACKED], + "enables LTO for dylib crate type"), + eagerly_emit_delayed_bugs: bool = (false, parse_bool, [UNTRACKED], + "emit delayed bugs eagerly as errors instead of stashing them and emitting \ + them only if an error has not been emitted"), + ehcont_guard: bool = (false, parse_bool, [TRACKED], + "generate Windows EHCont Guard tables"), + embed_metadata: bool = (true, parse_bool, [TRACKED], + "embed metadata in rlibs and dylibs (default: yes)"), + embed_source: bool = (false, parse_bool, [TRACKED], + "embed source text in DWARF debug sections (default: no)"), + emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], + "emit a section containing stack size metadata (default: no)"), + emscripten_wasm_eh: bool = (true, parse_bool, [TRACKED], + "Use WebAssembly error handling for wasm32-unknown-emscripten"), + enforce_type_length_limit: bool = (false, parse_bool, [TRACKED], + "enforce the type length limit when monomorphizing instances in codegen"), + experimental_default_bounds: bool = (false, parse_bool, [TRACKED], + "enable default bounds for experimental group of auto traits"), + export_executable_symbols: bool = (false, parse_bool, [TRACKED], + "export symbols from executables, as if they were dynamic libraries"), + external_clangrt: bool = (false, parse_bool, [UNTRACKED], + "rely on user specified linker commands to find clangrt"), + extra_const_ub_checks: bool = (false, parse_bool, [TRACKED], + "turns on more checks to detect const UB, which can be slow (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::fewer_names` instead of this field")] + fewer_names: Option = (None, parse_opt_bool, [TRACKED], + "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ + (default: no)"), + fixed_x18: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], + "make the x18 register reserved on AArch64 (default: no)"), + flatten_format_args: bool = (true, parse_bool, [TRACKED], + "flatten nested format_args!() and literals into a simplified format_args!() call \ + (default: yes)"), + fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED], + "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \ + `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"), + force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], + "force all crates to be `rustc_private` unstable (default: no)"), + function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], + "replace returns with jumps to `__x86_return_thunk` (default: `keep`)"), + function_sections: Option = (None, parse_opt_bool, [TRACKED], + "whether each function should go in its own section"), + future_incompat_test: bool = (false, parse_bool, [UNTRACKED], + "forces all lints to be future incompatible, used for internal testing (default: no)"), + graphviz_dark_mode: bool = (false, parse_bool, [UNTRACKED], + "use dark-themed colors in graphviz output (default: no)"), + graphviz_font: String = ("Courier, monospace".to_string(), parse_string, [UNTRACKED], + "use the given `fontname` in graphviz output; can be overridden by setting \ + environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"), + has_thread_local: Option = (None, parse_opt_bool, [TRACKED], + "explicitly enable the `cfg(target_thread_local)` directive"), + help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), + higher_ranked_assumptions: bool = (false, parse_bool, [TRACKED], + "allow deducing higher-ranked outlives assumptions from coroutines when proving auto traits"), + hint_mostly_unused: bool = (false, parse_bool, [TRACKED], + "hint that most of this crate will go unused, to minimize work for uncalled functions"), + human_readable_cgu_names: bool = (false, parse_bool, [TRACKED], + "generate human-readable, predictable names for codegen units (default: no)"), + identify_regions: bool = (false, parse_bool, [UNTRACKED], + "display unnamed regions as `'`, using a non-ident unique id (default: no)"), + ignore_directory_in_diagnostics_source_blocks: Vec = (Vec::new(), parse_string_push, [UNTRACKED], + "do not display the source code block in diagnostics for files in the directory"), + incremental_ignore_spans: bool = (false, parse_bool, [TRACKED], + "ignore spans during ICH computation -- used for testing (default: no)"), + incremental_info: bool = (false, parse_bool, [UNTRACKED], + "print high-level information about incremental reuse (or the lack thereof) \ + (default: no)"), + incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED], + "verify extended properties for incr. comp. (default: no): + - hashes of green query instances + - hash collisions of query keys + - hash collisions when creating dep-nodes"), + indirect_branch_cs_prefix: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], + "add `cs` prefix to `call` and `jmp` to indirect thunks (default: no)"), + inline_llvm: bool = (true, parse_bool, [TRACKED], + "enable LLVM inlining (default: yes)"), + inline_mir: Option = (None, parse_opt_bool, [TRACKED], + "enable MIR inlining (default: no)"), + inline_mir_forwarder_threshold: Option = (None, parse_opt_number, [TRACKED], + "inlining threshold when the caller is a simple forwarding function (default: 30)"), + inline_mir_hint_threshold: Option = (None, parse_opt_number, [TRACKED], + "inlining threshold for functions with inline hint (default: 100)"), + inline_mir_preserve_debug: Option = (None, parse_opt_bool, [TRACKED], + "when MIR inlining, whether to preserve debug info for callee variables \ + (default: preserve for debuginfo != None, otherwise remove)"), + inline_mir_threshold: Option = (None, parse_opt_number, [TRACKED], + "a default MIR inlining threshold (default: 50)"), + input_stats: bool = (false, parse_bool, [UNTRACKED], + "print some statistics about AST and HIR (default: no)"), + instrument_mcount: bool = (false, parse_bool, [TRACKED], + "insert function instrument code for mcount-based tracing (default: no)"), + instrument_xray: Option = (None, parse_instrument_xray, [TRACKED], + "insert function instrument code for XRay-based tracing (default: no) + Optional extra settings: + `=always` + `=never` + `=ignore-loops` + `=instruction-threshold=N` + `=skip-entry` + `=skip-exit` + Multiple options can be combined with commas."), + large_data_threshold: Option = (None, parse_opt_number, [TRACKED], + "set the threshold for objects to be stored in a \"large data\" section \ + (only effective with -Ccode-model=medium, default: 65536)"), + layout_seed: Option = (None, parse_opt_number, [TRACKED], + "seed layout randomization"), + link_directives: bool = (true, parse_bool, [TRACKED], + "honor #[link] directives in the compiled crate (default: yes)"), + link_native_libraries: bool = (true, parse_bool, [UNTRACKED], + "link native libraries in the linker invocation (default: yes)"), + link_only: bool = (false, parse_bool, [TRACKED], + "link the `.rlink` file generated by `-Z no-link` (default: no)"), + lint_llvm_ir: bool = (false, parse_bool, [TRACKED], + "lint LLVM IR (default: no)"), + lint_mir: bool = (false, parse_bool, [UNTRACKED], + "lint MIR before and after each transformation"), + llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED], + "a list of module flags to pass to LLVM (space separated)"), + llvm_plugins: Vec = (Vec::new(), parse_list, [TRACKED], + "a list LLVM plugins to enable (space separated)"), + llvm_time_trace: bool = (false, parse_bool, [UNTRACKED], + "generate JSON tracing data file from LLVM data (default: no)"), + location_detail: LocationDetail = (LocationDetail::all(), parse_location_detail, [TRACKED], + "what location details should be tracked when using caller_location, either \ + `none`, or a comma separated list of location details, for which \ + valid options are `file`, `line`, and `column` (default: `file,line,column`)"), + ls: Vec = (Vec::new(), parse_list, [UNTRACKED], + "decode and print various parts of the crate metadata for a library crate \ + (space separated)"), + macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show macro backtraces (default: no)"), + macro_stats: bool = (false, parse_bool, [UNTRACKED], + "print some statistics about macro expansions (default: no)"), + maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED], + "save as much information as possible about the correspondence between MIR and HIR \ + as source scopes (default: no)"), + merge_functions: Option = (None, parse_merge_functions, [TRACKED], + "control the operation of the MergeFunctions LLVM pass, taking \ + the same values as the target option of the same name"), + meta_stats: bool = (false, parse_bool, [UNTRACKED], + "gather metadata statistics (default: no)"), + metrics_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], + "the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"), + min_function_alignment: Option = (None, parse_align, [TRACKED], + "align all functions to at least this many bytes. Must be a power of 2"), + min_recursion_limit: Option = (None, parse_opt_number, [TRACKED], + "set a minimum recursion limit (final limit = max(this, recursion_limit_from_crate))"), + mir_emit_retag: bool = (false, parse_bool, [TRACKED], + "emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ + (default: no)"), + mir_enable_passes: Vec<(String, bool)> = (Vec::new(), parse_list_with_polarity, [TRACKED], + "use like `-Zmir-enable-passes=+DestinationPropagation,-InstSimplify`. Forces the \ + specified passes to be enabled, overriding all other checks. In particular, this will \ + enable unsound (known-buggy and hence usually disabled) passes without further warning! \ + Passes that are not specified are enabled or disabled by other flags as usual."), + mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED], + "include extra comments in mir pretty printing, like line numbers and statement indices, \ + details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"), + mir_opt_bisect_limit: Option = (None, parse_opt_number, [TRACKED], + "limit the number of MIR optimization pass executions (global across all bodies). \ + Pass executions after this limit are skipped and reported. (default: no limit)"), + #[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")] + mir_opt_level: Option = (None, parse_opt_number, [TRACKED], + "MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"), + mir_preserve_ub: bool = (false, parse_bool, [TRACKED], + "keep place mention statements and reads in trivial SwitchInt terminators, which are interpreted \ + e.g., by miri; implies -Zmir-opt-level=0 (default: no)"), + mir_strip_debuginfo: MirStripDebugInfo = (MirStripDebugInfo::None, parse_mir_strip_debuginfo, [TRACKED], + "Whether to remove some of the MIR debug info from methods. Default: None"), + move_size_limit: Option = (None, parse_opt_number, [TRACKED], + "the size at which the `large_assignments` lint starts to be emitted"), + mutable_noalias: bool = (true, parse_bool, [TRACKED], + "emit noalias metadata for mutable references (default: yes)"), + namespaced_crates: bool = (false, parse_bool, [TRACKED], + "allow crates to be namespaced by other crates (default: no)"), + next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED], + "enable and configure the next generation trait solver used by rustc"), + nll_facts: bool = (false, parse_bool, [UNTRACKED], + "dump facts from NLL analysis into side files (default: no)"), + nll_facts_dir: String = ("nll-facts".to_string(), parse_string, [UNTRACKED], + "the directory the NLL facts are dumped into (default: `nll-facts`)"), + no_analysis: bool = (false, parse_no_value, [UNTRACKED], + "parse and expand the source, but run no analysis"), + no_codegen: bool = (false, parse_no_value, [TRACKED_NO_CRATE_HASH], + "run all passes except codegen; no output"), + no_generate_arange_section: bool = (false, parse_no_value, [TRACKED], + "omit DWARF address ranges that give faster lookups"), + no_implied_bounds_compat: bool = (false, parse_bool, [TRACKED], + "disable the compatibility version of the `implied_bounds_ty` query"), + no_leak_check: bool = (false, parse_no_value, [UNTRACKED], + "disable the 'leak check' for subtyping; unsound, but useful for tests"), + no_link: bool = (false, parse_no_value, [TRACKED], + "compile without linking"), + no_parallel_backend: bool = (false, parse_no_value, [UNTRACKED], + "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"), + no_profiler_runtime: bool = (false, parse_no_value, [TRACKED], + "prevent automatic injection of the profiler_builtins crate"), + no_steal_thir: bool = (false, parse_bool, [UNTRACKED], + "don't steal the THIR when we're done with it; useful for rustc drivers (default: no)"), + no_trait_vptr: bool = (false, parse_no_value, [TRACKED], + "disable generation of trait vptr in vtable for upcasting"), + no_unique_section_names: bool = (false, parse_bool, [TRACKED], + "do not use unique names for text and data sections when -Z function-sections is used"), + normalize_docs: bool = (false, parse_bool, [TRACKED], + "normalize associated items in rustdoc when generating documentation"), + offload: Vec = (Vec::new(), parse_offload, [TRACKED], + "a list of offload flags to enable + Mandatory setting: + `=Enable` + Currently the only option available"), + on_broken_pipe: OnBrokenPipe = (OnBrokenPipe::Default, parse_on_broken_pipe, [TRACKED], + "behavior of std::io::ErrorKind::BrokenPipe (SIGPIPE)"), + osx_rpath_install_name: bool = (false, parse_bool, [TRACKED], + "pass `-install_name @rpath/...` to the macOS linker (default: no)"), + packed_bundled_libs: bool = (false, parse_bool, [TRACKED], + "change rlib format to store native libraries as archives"), + panic_abort_tests: bool = (false, parse_bool, [TRACKED], + "support compiling tests with panic=abort (default: no)"), + panic_in_drop: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy, [TRACKED], + "panic strategy for panics in drops"), + parse_crate_root_only: bool = (false, parse_bool, [UNTRACKED], + "parse the crate root file only; do not parse other files, compile, assemble, or link \ + (default: no)"), + patchable_function_entry: PatchableFunctionEntry = (PatchableFunctionEntry::default(), parse_patchable_function_entry, [TRACKED], + "nop padding at function entry"), + plt: Option = (None, parse_opt_bool, [TRACKED], + "whether to use the PLT when calling into shared libraries; + only has effect for PIC code on systems with ELF binaries + (default: PLT is disabled if full relro is enabled on x86_64)"), + polonius: Polonius = (Polonius::default(), parse_polonius, [TRACKED], + "enable polonius-based borrow-checker (default: no)"), + pre_link_arg: (/* redirected to pre_link_args */) = ((), parse_string_push, [UNTRACKED], + "a single extra argument to prepend the linker invocation (can be used several times)"), + pre_link_args: Vec = (Vec::new(), parse_list, [UNTRACKED], + "extra arguments to prepend to the linker invocation (space separated)"), + precise_enum_drop_elaboration: bool = (true, parse_bool, [TRACKED], + "use a more precise version of drop elaboration for matches on enums (default: yes). \ + This results in better codegen, but has caused miscompilations on some tier 2 platforms. \ + See #77382 and #74551."), + #[rustc_lint_opt_deny_field_access("use `Session::print_codegen_stats` instead of this field")] + print_codegen_stats: bool = (false, parse_bool, [UNTRACKED], + "print codegen statistics (default: no)"), + print_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "print the LLVM optimization passes being run (default: no)"), + print_mono_items: bool = (false, parse_bool, [UNTRACKED], + "print the result of the monomorphization collection pass (default: no)"), + print_type_sizes: bool = (false, parse_bool, [UNTRACKED], + "print layout information for each type encountered (default: no)"), + proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show backtraces for panics during proc-macro execution (default: no)"), + proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, + parse_proc_macro_execution_strategy, [UNTRACKED], + "how to run proc-macro code (default: same-thread)"), + profile_closures: bool = (false, parse_no_value, [UNTRACKED], + "profile size of closures"), + profile_sample_use: Option = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), + profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], + "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), + query_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "enable queries of the dependency graph for regression testing (default: no)"), + randomize_layout: bool = (false, parse_bool, [TRACKED], + "randomize the layout of types (default: no)"), + reg_struct_return: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], + "On x86-32 targets, it overrides the default ABI to return small structs in registers. + It is UNSOUND to link together crates that use different values for this flag!"), + regparm: Option = (None, parse_opt_number, [TRACKED TARGET_MODIFIER], + "On x86-32 targets, setting this to N causes the compiler to pass N arguments \ + in registers EAX, EDX, and ECX instead of on the stack for\ + \"C\", \"cdecl\", and \"stdcall\" fn.\ + It is UNSOUND to link together crates that use different values for this flag!"), + relax_elf_relocations: Option = (None, parse_opt_bool, [TRACKED], + "whether ELF relocations can be relaxed"), + remap_cwd_prefix: Option = (None, parse_opt_pathbuf, [TRACKED], + "remap paths under the current working directory to this path prefix"), + remark_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], + "directory into which to write optimization remarks (if not specified, they will be \ +written to standard error output)"), + retpoline: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], + "enables retpoline-indirect-branches and retpoline-indirect-calls target features (default: no)"), + retpoline_external_thunk: bool = (false, parse_bool, [TRACKED TARGET_MODIFIER], + "enables retpoline-external-thunk, retpoline-indirect-branches and retpoline-indirect-calls \ + target features (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::sanitizers()` instead of this field")] + sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED TARGET_MODIFIER], + "use a sanitizer"), + sanitizer_cfi_canonical_jump_tables: Option = (Some(true), parse_opt_bool, [TRACKED], + "enable canonical jump tables (default: yes)"), + sanitizer_cfi_generalize_pointers: Option = (None, parse_opt_bool, [TRACKED], + "enable generalizing pointer types (default: no)"), + sanitizer_cfi_normalize_integers: Option = (None, parse_opt_bool, [TRACKED TARGET_MODIFIER], + "enable normalizing integer types (default: no)"), + sanitizer_dataflow_abilist: Vec = (Vec::new(), parse_comma_list, [TRACKED], + "additional ABI list files that control how shadow parameters are passed (comma separated)"), + sanitizer_kcfi_arity: Option = (None, parse_opt_bool, [TRACKED], + "enable KCFI arity indicator (default: no)"), + sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED], + "enable origins tracking in MemorySanitizer"), + sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], + "enable recovery for selected sanitizers"), + saturating_float_casts: Option = (None, parse_opt_bool, [TRACKED], + "make float->int casts UB-free: numbers outside the integer type's range are clipped to \ + the max/min integer respectively, and NaN is mapped to 0 (default: yes)"), + self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [UNTRACKED], + "run the self profiler and output the raw event data"), + self_profile_counter: String = ("wall-time".to_string(), parse_string, [UNTRACKED], + "counter used by the self profiler (default: `wall-time`), one of: + `wall-time` (monotonic clock, i.e. `std::time::Instant`) + `instructions:u` (retired instructions, userspace-only) + `instructions-minus-irqs:u` (subtracting hardware interrupt counts for extra accuracy)" + ), + /// keep this in sync with the event filter names in librustc_data_structures/profiling.rs + self_profile_events: Option> = (None, parse_opt_comma_list, [UNTRACKED], + "specify the events recorded by the self profiler; + for example: `-Z self-profile-events=default,query-keys` + all options: none, all, default, generic-activity, query-provider, query-cache-hit + query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), + share_generics: Option = (None, parse_opt_bool, [TRACKED], + "make the current crate share its generic instantiations"), + shell_argfiles: bool = (false, parse_bool, [UNTRACKED], + "allow argument files to be specified with POSIX \"shell-style\" argument quoting"), + simulate_remapped_rust_src_base: Option = (None, parse_opt_pathbuf, [TRACKED], + "simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \ + to rust's source base directory. only meant for testing purposes"), + small_data_threshold: Option = (None, parse_opt_number, [TRACKED], + "Set the threshold for objects to be stored in a \"small data\" section"), + span_debug: bool = (false, parse_bool, [UNTRACKED], + "forward proc_macro::Span's `Debug` impl to `Span`"), + /// o/w tests have closure@path + span_free_formats: bool = (false, parse_bool, [UNTRACKED], + "exclude spans when debug-printing compiler state (default: no)"), + split_dwarf_inlining: bool = (false, parse_bool, [TRACKED], + "provide minimal debug info in the object/executable to facilitate online \ + symbolication/stack traces in the absence of .dwo/.dwp files when using Split DWARF"), + split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED], + "split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform) + (default: `split`) + + `split`: sections which do not require relocation are written into a DWARF object (`.dwo`) + file which is ignored by the linker + `single`: sections which do not require relocation are written into object file but ignored + by the linker"), + split_dwarf_out_dir : Option = (None, parse_opt_pathbuf, [TRACKED], + "location for writing split DWARF objects (`.dwo`) if enabled"), + split_lto_unit: Option = (None, parse_opt_bool, [TRACKED], + "enable LTO unit splitting (default: no)"), + src_hash_algorithm: Option = (None, parse_src_file_hash, [TRACKED], + "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), + #[rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field")] + stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED], + "control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"), + staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED], + "allow staticlibs to have rust dylib dependencies"), + staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED], + "prefer dynamic linking to static linking for staticlibs (default: no)"), + strict_init_checks: bool = (false, parse_bool, [TRACKED], + "control if mem::uninitialized and mem::zeroed panic on more UB"), + #[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")] + teach: bool = (false, parse_bool, [TRACKED], + "show extended diagnostic help (default: no)"), + temps_dir: Option = (None, parse_opt_string, [UNTRACKED], + "the directory the intermediate files are written to"), + terminal_urls: TerminalUrl = (TerminalUrl::No, parse_terminal_url, [UNTRACKED], + "use the OSC 8 hyperlink terminal specification to print hyperlinks in the compiler output"), + #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] + thinlto: Option = (None, parse_opt_bool, [TRACKED], + "enable ThinLTO when possible"), + /// We default to 1 here since we want to behave like + /// a sequential compiler for now. This'll likely be adjusted + /// in the future. Note that -Zthreads=0 is the way to get + /// the num_cpus behavior. + #[rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field")] + threads: usize = (1, parse_threads, [UNTRACKED], + "use a thread pool with N threads"), + time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each LLVM pass (default: no)"), + time_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each rustc pass (default: no)"), + time_passes_format: TimePassesFormat = (TimePassesFormat::Text, parse_time_passes_format, [UNTRACKED], + "the format to use for -Z time-passes (`text` (default) or `json`)"), + tiny_const_eval_limit: bool = (false, parse_bool, [TRACKED], + "sets a tiny, non-configurable limit for const eval; useful for compiler tests"), + #[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")] + tls_model: Option = (None, parse_tls_model, [TRACKED], + "choose the TLS model to use (`rustc --print tls-models` for details)"), + trace_macros: bool = (false, parse_bool, [UNTRACKED], + "for every macro invocation, print its name and arguments (default: no)"), + track_diagnostics: bool = (false, parse_bool, [UNTRACKED], + "tracks where in rustc a diagnostic was emitted"), + translate_remapped_path_to_local_path: bool = (true, parse_bool, [TRACKED], + "translate remapped paths into local paths when possible (default: yes)"), + trap_unreachable: Option = (None, parse_opt_bool, [TRACKED], + "generate trap instructions for unreachable intrinsics (default: use target setting, usually yes)"), + treat_err_as_bug: Option> = (None, parse_treat_err_as_bug, [TRACKED], + "treat the `val`th error that occurs as bug (default if not specified: 0 - don't treat errors as bugs. \ + default if specified without a value: 1 - treat the first error as bug)"), + trim_diagnostic_paths: bool = (true, parse_bool, [UNTRACKED], + "in diagnostics, use heuristics to shorten paths referring to items"), + tune_cpu: Option = (None, parse_opt_string, [TRACKED], + "select processor to schedule for (`rustc --print target-cpus` for details)"), + #[rustc_lint_opt_deny_field_access("use `TyCtxt::use_typing_mode_borrowck` instead of this field")] + typing_mode_borrowck: bool = (false, parse_bool, [TRACKED], + "enable `TypingMode::Borrowck`, changing the way opaque types are handled during MIR borrowck"), + #[rustc_lint_opt_deny_field_access("use `Session::ub_checks` instead of this field")] + ub_checks: Option = (None, parse_opt_bool, [TRACKED], + "emit runtime checks for Undefined Behavior (default: -Cdebug-assertions)"), + ui_testing: bool = (false, parse_bool, [UNTRACKED], + "emit compiler diagnostics in a form suitable for UI testing (default: no)"), + uninit_const_chunk_threshold: usize = (16, parse_number, [TRACKED], + "allow generating const initializers with mixed init/uninit chunks, \ + and set the maximum number of chunks for which this is allowed (default: 16)"), + unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED], + "take the brakes off const evaluation. NOTE: this is unsound (default: no)"), + unpretty: Option = (None, parse_unpretty, [UNTRACKED], + "present the input source, unstable (and less-pretty) variants; + `normal`, `identified`, + `expanded`, `expanded,identified`, + `expanded,hygiene` (with internal representations), + `ast-tree` (raw AST before expansion), + `ast-tree,expanded` (raw AST after expansion), + `hir` (the HIR), `hir,identified`, + `hir,typed` (HIR with types for each node), + `hir-tree` (dump the raw HIR), + `thir-tree`, `thir-flat`, + `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"), + unsound_mir_opts: bool = (false, parse_bool, [TRACKED], + "enable unsound and buggy MIR optimizations (default: no)"), + /// This name is kind of confusing: Most unstable options enable something themselves, while + /// this just allows "normal" options to be feature-gated. + /// + /// The main check for `-Zunstable-options` takes place separately from the + /// usual parsing of `-Z` options (see [`crate::config::nightly_options`]), + /// so this boolean value is mostly used for enabling unstable _values_ of + /// stable options. That separate check doesn't handle boolean values, so + /// to avoid an inconsistent state we also forbid them here. + #[rustc_lint_opt_deny_field_access("use `Session::unstable_options` instead of this field")] + unstable_options: bool = (false, parse_no_value, [UNTRACKED], + "adds unstable command line options to rustc interface (default: no)"), + use_ctors_section: Option = (None, parse_opt_bool, [TRACKED], + "use legacy .ctors section for initializers rather than .init_array"), + use_sync_unwind: Option = (None, parse_opt_bool, [TRACKED], + "Generate sync unwind tables instead of async unwind tables (default: no)"), + validate_mir: bool = (false, parse_bool, [UNTRACKED], + "validate MIR after each transformation"), + verbose_asm: bool = (false, parse_bool, [TRACKED], + "add descriptive comments from LLVM to the assembly (may change behavior) (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::verbose_internals` instead of this field")] + verbose_internals: bool = (false, parse_bool, [TRACKED_NO_CRATE_HASH], + "in general, enable more debug printouts (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::verify_llvm_ir` instead of this field")] + verify_llvm_ir: bool = (false, parse_bool, [TRACKED], + "verify LLVM IR (default: no)"), + virtual_function_elimination: bool = (false, parse_bool, [TRACKED], + "enables dead virtual function elimination optimization. \ + Requires `-Clto[=[fat,yes]]`"), + wasi_exec_model: Option = (None, parse_wasi_exec_model, [TRACKED], + "whether to build a wasi command or reactor"), + // This option only still exists to provide a more gradual transition path for people who need + // the spec-complaint C ABI to be used. + // FIXME remove this after a couple releases + wasm_c_abi: () = ((), parse_wasm_c_abi, [TRACKED], + "use spec-compliant C ABI for `wasm32-unknown-unknown` (deprecated, always enabled)"), + write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED], + "whether long type names should be written to files instead of being printed in errors"), + // tidy-alphabetical-end + + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags +} diff --git a/src/tools/unstable-book-gen/Cargo.toml b/src/tools/unstable-book-gen/Cargo.toml index 73e5a91bec70a..12dd2eda3d9e2 100644 --- a/src/tools/unstable-book-gen/Cargo.toml +++ b/src/tools/unstable-book-gen/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [dependencies] tidy = { path = "../tidy" } +proc-macro2 = { version = "1.0", features = ["span-locations"] } +syn = { version = "2.0", features = ["full", "parsing"] } # not actually needed but required for now to unify the feature selection of # `num-traits` between this and `rustbook` diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs index 12b35a11c0d66..8985072cbd27b 100644 --- a/src/tools/unstable-book-gen/src/main.rs +++ b/src/tools/unstable-book-gen/src/main.rs @@ -5,6 +5,9 @@ use std::env; use std::fs::{self, write}; use std::path::Path; +use proc_macro2::{Span, TokenStream, TokenTree}; +use syn::parse::{Parse, ParseStream}; +use syn::{Attribute, Ident, Item, LitStr, Token, parenthesized}; use tidy::diagnostics::RunningCheck; use tidy::features::{ Feature, Features, Status, collect_env_vars, collect_lang_features, collect_lib_features, @@ -116,7 +119,7 @@ fn copy_recursive(from: &Path, to: &Path) { } fn collect_compiler_flags(compiler_path: &Path) -> Features { - let options_path = compiler_path.join("rustc_session/src/options.rs"); + let options_path = compiler_path.join("rustc_session/src/options/unstable.rs"); let options_rs = t!(fs::read_to_string(&options_path), options_path); parse_compiler_flags(&options_rs, &options_path) } @@ -125,50 +128,103 @@ const DESCRIPTION_FIELD: usize = 3; const REQUIRED_FIELDS: usize = 4; const OPTIONAL_FIELDS: usize = 5; -struct SourceBlock<'a> { - content: &'a str, - offset: usize, +struct ParsedOptionEntry { + name: String, + line: usize, + description: String, } -struct ParsedOptionEntry<'a> { - name: &'a str, - name_start: usize, - description: Option, - next_idx: usize, +struct UnstableOptionsInput { + struct_name: Ident, + entries: Vec, } -fn parse_compiler_flags(options_rs: &str, options_path: &Path) -> Features { - let options_block = find_options_block(options_rs, "UnstableOptions"); - let alphabetical_section = find_tidy_alphabetical_section(options_block); - let section_line_offset = line_number(options_rs, alphabetical_section.offset) - 1; - - let mut features = Features::new(); - let mut idx = 0; +impl Parse for ParsedOptionEntry { + fn parse(input: ParseStream<'_>) -> syn::Result { + let _attrs = input.call(Attribute::parse_outer)?; + + let name: Ident = input.parse()?; + let line = name.span().start().line; + input.parse::()?; + let _ty: syn::Type = input.parse()?; + input.parse::()?; + + let tuple_content; + parenthesized!(tuple_content in input); + let tuple_tokens: TokenStream = tuple_content.parse()?; + let tuple_fields = split_tuple_fields(tuple_tokens); + + if !matches!(tuple_fields.len(), REQUIRED_FIELDS | OPTIONAL_FIELDS) { + return Err(syn::Error::new( + name.span(), + format!( + "unexpected field count for option `{name}`: expected 4 or 5, found {}", + tuple_fields.len() + ), + )); + } - while idx < alphabetical_section.content.len() { - skip_ws_comments_and_attrs(alphabetical_section.content, &mut idx); - if idx >= alphabetical_section.content.len() { - break; + if tuple_fields.len() == OPTIONAL_FIELDS + && !is_deprecated_marker_field(&tuple_fields[REQUIRED_FIELDS]) + { + return Err(syn::Error::new( + name.span(), + format!( + "unexpected trailing field in option `{name}`: expected `is_deprecated_and_do_nothing: ...`" + ), + )); } - let entry = parse_one_entry(alphabetical_section.content, idx); - idx = entry.next_idx; + let description = parse_description_field(&tuple_fields[DESCRIPTION_FIELD], &name)?; + Ok(Self { name: name.to_string(), line, description }) + } +} + +impl Parse for UnstableOptionsInput { + fn parse(input: ParseStream<'_>) -> syn::Result { + let struct_name: Ident = input.parse()?; + input.parse::()?; + let _tmod_enum_name: Ident = input.parse()?; + input.parse::()?; + let _stat_name: Ident = input.parse()?; + input.parse::()?; + let _opt_module_name: Ident = input.parse()?; + input.parse::()?; + let _prefix: LitStr = input.parse()?; + input.parse::()?; + let _output_name: LitStr = input.parse()?; + input.parse::()?; + + let entries = + syn::punctuated::Punctuated::::parse_terminated(input)? + .into_iter() + .collect(); + + Ok(Self { struct_name, entries }) + } +} + +fn parse_compiler_flags(options_rs: &str, options_path: &Path) -> Features { + let options_input = parse_unstable_options_macro(options_rs).unwrap_or_else(|error| { + panic!("failed to parse unstable options from `{}`: {error}", options_path.display()) + }); + let mut features = Features::new(); + for entry in options_input.entries { if entry.name == "help" { continue; } features.insert( - entry.name.to_owned(), + entry.name, Feature { level: Status::Unstable, since: None, has_gate_test: false, tracking_issue: None, file: options_path.to_path_buf(), - line: section_line_offset - + line_number(alphabetical_section.content, entry.name_start), - description: entry.description, + line: entry.line, + description: Some(entry.description), }, ); } @@ -176,447 +232,72 @@ fn parse_compiler_flags(options_rs: &str, options_path: &Path) -> Features { features } -fn parse_one_entry(source: &str, start_idx: usize) -> ParsedOptionEntry<'_> { - let name_start = start_idx; - let name_end = - parse_ident_end(source, name_start).expect("expected an option name in UnstableOptions"); - let name = &source[name_start..name_end]; - let mut idx = name_end; - - skip_ws_comments(source, &mut idx); - expect_byte(source, idx, b':', &format!("expected `:` after option name `{name}`")); - idx += 1; - - idx = - find_char_outside_nested(source, idx, b'=').expect("expected `=` in UnstableOptions entry"); - idx += 1; - - skip_ws_comments(source, &mut idx); - expect_byte(source, idx, b'(', &format!("expected tuple payload for option `{name}`")); - - let tuple_start = idx; - let tuple_end = find_matching_delimiter(source, tuple_start, b'(', b')') - .expect("UnstableOptions tuple should be balanced"); - let next_idx = skip_past_entry_delimiter(source, tuple_end + 1, name); - - let description = if name == "help" { - None - } else { - let fields = split_top_level_fields(&source[tuple_start + 1..tuple_end]); - validate_option_fields(&fields, name); - // The `options!` macro layout is `(init, parse, [dep_tracking...], desc, ...)`. - Some(parse_string_literal( - fields.get(DESCRIPTION_FIELD).expect("option description should be present"), - )) - }; - - ParsedOptionEntry { name, name_start, description, next_idx } -} - -fn find_options_block<'a>(source: &'a str, struct_name: &str) -> SourceBlock<'a> { - let mut search_from = 0; - - while let Some(relative_start) = source[search_from..].find("options!") { - let macro_start = search_from + relative_start; - let open_brace = source[macro_start..] - .find('{') - .map(|relative| macro_start + relative) - .expect("options! invocation should contain `{`"); - let close_brace = find_matching_delimiter(source, open_brace, b'{', b'}') - .expect("options! invocation should have a matching `}`"); - let block = &source[open_brace + 1..close_brace]; - - if block.trim_start().starts_with(struct_name) { - return SourceBlock { content: block, offset: open_brace + 1 }; - } - - search_from = close_brace + 1; - } - - panic!("could not find `{struct_name}` options! block"); -} - -fn find_tidy_alphabetical_section(block: SourceBlock<'_>) -> SourceBlock<'_> { - let start_marker = "// tidy-alphabetical-start"; - let end_marker = "// tidy-alphabetical-end"; - - let section_start = block - .content - .find(start_marker) - .map(|start| start + start_marker.len()) - .expect("options! block should contain `// tidy-alphabetical-start`"); - let section_end = block.content[section_start..] - .find(end_marker) - .map(|end| section_start + end) - .expect("options! block should contain `// tidy-alphabetical-end`"); - - SourceBlock { - content: &block.content[section_start..section_end], - offset: block.offset + section_start, - } -} - -fn line_number(source: &str, offset: usize) -> usize { - source[..offset].bytes().filter(|&byte| byte == b'\n').count() + 1 -} - -fn expect_byte(source: &str, idx: usize, expected: u8, context: &str) { - assert_eq!(source.as_bytes().get(idx).copied(), Some(expected), "{context}"); -} - -fn skip_ws_comments_and_attrs(source: &str, idx: &mut usize) { - loop { - skip_ws_comments(source, idx); - - if source[*idx..].starts_with("#[") { - let attr_start = *idx + 1; - let attr_end = find_matching_delimiter(source, attr_start, b'[', b']') - .expect("attribute should have matching `]`"); - *idx = attr_end + 1; - continue; - } - - break; - } -} - -fn skip_ws_comments(source: &str, idx: &mut usize) { - loop { - while let Some(byte) = source.as_bytes().get(*idx) { - if byte.is_ascii_whitespace() { - *idx += 1; - } else { - break; - } - } +fn parse_unstable_options_macro(source: &str) -> syn::Result { + let ast = syn::parse_file(source)?; - if source[*idx..].starts_with("//") { - *idx = source[*idx..].find('\n').map_or(source.len(), |end| *idx + end + 1); + for item in ast.items { + let Item::Macro(item_macro) = item else { continue; - } + }; - if source[*idx..].starts_with("/*") { - *idx = skip_block_comment(source, *idx); + if !item_macro.mac.path.is_ident("options") { continue; } - break; - } -} - -fn skip_block_comment(source: &str, mut idx: usize) -> usize { - let mut depth = 1; - idx += 2; - - while idx < source.len() { - match source.as_bytes().get(idx..idx + 2) { - Some(b"/*") => { - depth += 1; - idx += 2; - } - Some(b"*/") => { - depth -= 1; - idx += 2; - if depth == 0 { - return idx; - } - } - _ => idx += 1, - } - } - - panic!("unterminated block comment"); -} - -fn parse_ident_end(source: &str, start: usize) -> Option { - let bytes = source.as_bytes(); - let first = *bytes.get(start)?; - if !(first == b'_' || first.is_ascii_alphabetic()) { - return None; - } - - let mut idx = start + 1; - while let Some(byte) = bytes.get(idx) { - if *byte == b'_' || byte.is_ascii_alphanumeric() { - idx += 1; - } else { - break; - } - } - - Some(idx) -} - -fn find_char_outside_nested(source: &str, start: usize, needle: u8) -> Option { - let mut idx = start; - let mut paren_depth = 0; - let mut bracket_depth = 0; - let mut brace_depth = 0; - - while idx < source.len() { - match source.as_bytes()[idx] { - b'/' if source[idx..].starts_with("//") => { - idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); - } - b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), - b'"' => idx = skip_string_literal(source, idx), - b'(' => { - paren_depth += 1; - idx += 1; - } - b')' => { - paren_depth -= 1; - idx += 1; - } - b'[' => { - bracket_depth += 1; - idx += 1; - } - b']' => { - bracket_depth -= 1; - idx += 1; - } - b'{' => { - brace_depth += 1; - idx += 1; - } - b'}' => { - brace_depth -= 1; - idx += 1; - } - byte if byte == needle - && paren_depth == 0 - && bracket_depth == 0 - && brace_depth == 0 => - { - return Some(idx); - } - _ => idx += 1, + let parsed = syn::parse2::(item_macro.mac.tokens)?; + if parsed.struct_name == "UnstableOptions" { + return Ok(parsed); } } - None + Err(syn::Error::new( + Span::call_site(), + "could not find `options!` invocation for `UnstableOptions`", + )) } -fn find_matching_delimiter(source: &str, start: usize, open: u8, close: u8) -> Option { - let mut idx = start; - let mut depth = 0; - - while idx < source.len() { - match source.as_bytes()[idx] { - b'/' if source[idx..].starts_with("//") => { - idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); - } - b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), - b'"' => idx = skip_string_literal(source, idx), - byte if byte == open => { - depth += 1; - idx += 1; - } - byte if byte == close => { - depth -= 1; - if depth == 0 { - return Some(idx); - } - idx += 1; - } - _ => idx += 1, - } - } - - None +fn parse_description_field(field: &TokenStream, option_name: &Ident) -> syn::Result { + let lit = syn::parse2::(field.clone()).map_err(|_| { + syn::Error::new_spanned( + field.clone(), + format!("expected description string literal in option `{option_name}`"), + ) + })?; + Ok(lit.value()) } -fn split_top_level_fields(source: &str) -> Vec<&str> { +fn split_tuple_fields(tuple_tokens: TokenStream) -> Vec { let mut fields = Vec::new(); - let mut field_start = 0; - let mut idx = 0; - let mut paren_depth = 0; - let mut bracket_depth = 0; - let mut brace_depth = 0; - - while idx < source.len() { - match source.as_bytes()[idx] { - b'/' if source[idx..].starts_with("//") => { - idx = source[idx..].find('\n').map_or(source.len(), |end| idx + end + 1); - } - b'/' if source[idx..].starts_with("/*") => idx = skip_block_comment(source, idx), - b'"' => idx = skip_string_literal(source, idx), - b'(' => { - paren_depth += 1; - idx += 1; - } - b')' => { - paren_depth -= 1; - idx += 1; - } - b'[' => { - bracket_depth += 1; - idx += 1; - } - b']' => { - bracket_depth -= 1; - idx += 1; - } - b'{' => { - brace_depth += 1; - idx += 1; - } - b'}' => { - brace_depth -= 1; - idx += 1; - } - b',' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => { - fields.push(source[field_start..idx].trim()); - idx += 1; - field_start = idx; - } - _ => idx += 1, - } - } - - fields.push(source[field_start..].trim()); - fields -} - -fn validate_option_fields(fields: &[&str], name: &str) { - assert!( - matches!(fields.len(), REQUIRED_FIELDS | OPTIONAL_FIELDS), - "unexpected field count for option `{name}`: expected 4 or 5 fields, found {}", - fields.len() - ); - assert!( - fields[2].starts_with('[') && fields[2].ends_with(']'), - "expected dep-tracking field in option `{name}`, found `{}`", - fields[2] - ); - assert!( - looks_like_string_literal(fields[DESCRIPTION_FIELD]), - "expected description string literal in option `{name}`, found `{}`", - fields[DESCRIPTION_FIELD] - ); - - if let Some(extra_field) = fields.get(REQUIRED_FIELDS) { - assert!( - extra_field.trim_start().starts_with("is_deprecated_and_do_nothing:"), - "unexpected trailing field in option `{name}`: `{extra_field}`", - ); - } -} - -fn looks_like_string_literal(field: &str) -> bool { - let field = field.trim(); - (field.starts_with('"') && field.ends_with('"')) || parse_raw_string_literal(field).is_some() -} - -fn skip_past_entry_delimiter(source: &str, start: usize, name: &str) -> usize { - let mut idx = start; - skip_ws_comments(source, &mut idx); - - match source.as_bytes().get(idx).copied() { - Some(b',') => idx + 1, - None => idx, - Some(byte) => { - panic!("expected `,` after option entry `{name}`, found {:?}", char::from(byte)) - } - } -} - -fn skip_string_literal(source: &str, mut idx: usize) -> usize { - idx += 1; - - while idx < source.len() { - match source.as_bytes()[idx] { - b'\\' => { - idx += 1; - if idx < source.len() { - idx += 1; - } + let mut current = TokenStream::new(); + + for token in tuple_tokens { + if let TokenTree::Punct(punct) = &token { + if punct.as_char() == ',' { + fields.push(current); + current = TokenStream::new(); + continue; } - b'"' => return idx + 1, - _ => idx += 1, } + current.extend([token]); } + fields.push(current); - panic!("unterminated string literal"); -} - -fn parse_string_literal(literal: &str) -> String { - let literal = literal.trim(); - - if let Some(raw_literal) = parse_raw_string_literal(literal) { - return raw_literal; + while matches!(fields.last(), Some(field) if field.is_empty()) { + fields.pop(); } - let inner = literal - .strip_prefix('"') - .and_then(|value| value.strip_suffix('"')) - .expect("expected a string literal"); - let mut output = String::new(); - let mut chars = inner.chars().peekable(); - - while let Some(ch) = chars.next() { - if ch != '\\' { - output.push(ch); - continue; - } - - let escaped = chars.next().expect("unterminated string escape"); - match escaped { - '\n' => while chars.next_if(|ch| ch.is_whitespace()).is_some() {}, - '\r' => { - let _ = chars.next_if_eq(&'\n'); - while chars.next_if(|ch| ch.is_whitespace()).is_some() {} - } - '"' => output.push('"'), - '\'' => output.push('\''), - '\\' => output.push('\\'), - 'n' => output.push('\n'), - 'r' => output.push('\r'), - 't' => output.push('\t'), - '0' => output.push('\0'), - 'x' => { - let hi = chars.next().expect("missing first hex digit in escape"); - let lo = chars.next().expect("missing second hex digit in escape"); - let byte = u8::from_str_radix(&format!("{hi}{lo}"), 16) - .expect("invalid hex escape in string literal"); - output.push(char::from(byte)); - } - 'u' => { - assert_eq!(chars.next(), Some('{'), "expected `{{` after `\\u`"); - let mut digits = String::new(); - for ch in chars.by_ref() { - if ch == '}' { - break; - } - digits.push(ch); - } - let scalar = - u32::from_str_radix(&digits, 16).expect("invalid unicode escape in string"); - output.push(char::from_u32(scalar).expect("unicode escape should be valid")); - } - _ => panic!("unsupported escape in string literal"), - } - } - - output + fields } -fn parse_raw_string_literal(literal: &str) -> Option { - let rest = literal.strip_prefix('r')?; - let hashes = rest.bytes().take_while(|&byte| byte == b'#').count(); - let quote_idx = 1 + hashes; - - if literal.as_bytes().get(quote_idx) != Some(&b'"') { - return None; - } - - let suffix = format!("\"{}", "#".repeat(hashes)); - let content = literal[quote_idx + 1..] - .strip_suffix(&suffix) - .expect("raw string literal should have a matching terminator"); - - Some(content.to_owned()) +fn is_deprecated_marker_field(field: &TokenStream) -> bool { + let mut tokens = field.clone().into_iter(); + let Some(TokenTree::Ident(name)) = tokens.next() else { + return false; + }; + let Some(TokenTree::Punct(colon)) = tokens.next() else { + return false; + }; + name == "is_deprecated_and_do_nothing" && colon.as_char() == ':' } fn main() { diff --git a/src/tools/unstable-book-gen/src/tests.rs b/src/tools/unstable-book-gen/src/tests.rs index 98008a45be629..9e57f032f737a 100644 --- a/src/tools/unstable-book-gen/src/tests.rs +++ b/src/tools/unstable-book-gen/src/tests.rs @@ -1,83 +1,69 @@ use std::path::{Path, PathBuf}; -use super::{parse_compiler_flags, parse_one_entry, skip_ws_comments_and_attrs}; +use super::parse_compiler_flags; #[test] fn parses_unstable_options_entries() { - let tidy_start = "// tidy-alphabetical-start"; - let tidy_end = "// tidy-alphabetical-end"; - let options_rs = format!( - "\n\ - options! {{\n\ - \x20 UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, \"Z\", \"unstable\",\n\ - \n\ - \x20 {tidy_start}\n\ - \x20 #[rustc_lint_opt_deny_field_access(\"test attr\")]\n\ - \x20 allow_features: Option> = (None, parse_opt_comma_list, [TRACKED],\n\ - \x20 \"only allow the listed language features to be enabled in code (comma separated)\"),\n\ - \x20 dump_mir: Option = (None, parse_opt_string, [UNTRACKED],\n\ - \x20 \"dump MIR state to file.\n\ - \x20 `val` is used to select which passes and functions to dump.\"),\n\ - \x20 join_lines: bool = (false, parse_bool, [TRACKED],\n\ - \x20 \"join \\\n\ - \x20 continued lines\"),\n\ - \x20 help: bool = (false, parse_no_value, [UNTRACKED], \"Print unstable compiler options\"),\n\ - \x20 {tidy_end}\n\ - }}\n" - ); + let options_rs = r#"options! { + UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", + + #[rustc_lint_opt_deny_field_access("test attr")] + allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], + "only allow the listed language features to be enabled in code (comma separated)"), + dump_mir: Option = (None, parse_opt_string, [UNTRACKED], + "dump MIR state to file.\n\ + `val` is used to select which passes and functions to dump."), + join_lines: bool = (false, parse_bool, [TRACKED], + "join \ + continued lines"), + help: bool = (false, parse_no_value, [UNTRACKED], "Print unstable compiler options"), +}"#; - let features = parse_compiler_flags(&options_rs, Path::new("options.rs")); + let features = parse_compiler_flags(options_rs, Path::new("options/unstable.rs")); assert!(features.contains_key("allow_features")); assert!(features.contains_key("dump_mir")); assert!(features.contains_key("join_lines")); assert!(!features.contains_key("help")); + assert!( + features["dump_mir"] + .description + .as_deref() + .expect("dump_mir description should exist") + .starts_with("dump MIR state to file.\n"), + ); + assert_eq!(features["join_lines"].description.as_deref(), Some("join continued lines")); assert_eq!( - features["dump_mir"].description.as_deref(), - Some( - "dump MIR state to file.\n `val` is used to select which passes and functions to dump." - ), + features["allow_features"].description.as_deref(), + Some("only allow the listed language features to be enabled in code (comma separated)"), ); - assert_eq!(features["join_lines"].description.as_deref(), Some("join continued lines"),); - assert_eq!(features["allow_features"].file, PathBuf::from("options.rs")); - assert_eq!(features["allow_features"].line, 7); + assert_eq!(features["allow_features"].file, PathBuf::from("options/unstable.rs")); + assert_eq!(features["allow_features"].line, 5); } #[test] -fn parse_one_entry_skips_help_description_and_advances() { - let section = "\ -help: bool = (false, parse_no_value, [UNTRACKED], \"Print unstable compiler options\"),\n\ -join_lines: bool = (false, parse_bool, [TRACKED], \"join \\\n\ - continued lines\"),\n"; - - let help_entry = parse_one_entry(section, 0); - assert_eq!(help_entry.name, "help"); - assert!(help_entry.description.is_none()); - - let mut next_idx = help_entry.next_idx; - skip_ws_comments_and_attrs(section, &mut next_idx); - let next_entry = parse_one_entry(section, next_idx); +fn parser_accepts_optional_trailing_metadata() { + let options_rs = r##"options! { + UnstableOptions, UnstableOptionsTargetModifiers, Z_OPTIONS, dbopts, "Z", "unstable", - assert_eq!(next_entry.name, "join_lines"); - assert_eq!(next_entry.description.as_deref(), Some("join continued lines"),); -} - -#[test] -fn parse_one_entry_accepts_optional_trailing_metadata() { - let entry = "\ -deprecated_flag: bool = (false, parse_no_value, [UNTRACKED], \"deprecated flag\",\n\ - is_deprecated_and_do_nothing: true),\n"; + deprecated_flag: bool = (false, parse_no_value, [UNTRACKED], "deprecated flag", + is_deprecated_and_do_nothing: true), + raw_description: bool = (false, parse_no_value, [UNTRACKED], r#"raw "quoted" text"#), +}"##; - let parsed = parse_one_entry(entry, 0); - assert_eq!(parsed.name, "deprecated_flag"); - assert_eq!(parsed.description.as_deref(), Some("deprecated flag")); + let features = parse_compiler_flags(options_rs, Path::new("options/unstable.rs")); + assert_eq!(features["deprecated_flag"].description.as_deref(), Some("deprecated flag")); + assert_eq!( + features["raw_description"].description.as_deref(), + Some("raw \"quoted\" text"), + ); } #[test] fn parses_real_unstable_options_file() { let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let options_path = manifest_dir.join("../../../compiler/rustc_session/src/options.rs"); + let options_path = manifest_dir.join("../../../compiler/rustc_session/src/options/unstable.rs"); let options_rs = std::fs::read_to_string(&options_path).unwrap(); let features = parse_compiler_flags(&options_rs, &options_path); From deeb45a5debda9f607753d444bb8e3cd4c6b799e Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:57:31 -0400 Subject: [PATCH 4/5] style: fix rustfmt --- src/tools/unstable-book-gen/src/tests.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tools/unstable-book-gen/src/tests.rs b/src/tools/unstable-book-gen/src/tests.rs index 9e57f032f737a..000cfaebdac30 100644 --- a/src/tools/unstable-book-gen/src/tests.rs +++ b/src/tools/unstable-book-gen/src/tests.rs @@ -54,10 +54,7 @@ fn parser_accepts_optional_trailing_metadata() { let features = parse_compiler_flags(options_rs, Path::new("options/unstable.rs")); assert_eq!(features["deprecated_flag"].description.as_deref(), Some("deprecated flag")); - assert_eq!( - features["raw_description"].description.as_deref(), - Some("raw \"quoted\" text"), - ); + assert_eq!(features["raw_description"].description.as_deref(), Some("raw \"quoted\" text"),); } #[test] From 75bd926afd3a6dc272f4c6c9835fcaff807cc870 Mon Sep 17 00:00:00 2001 From: randomicon00 <20146907+randomicon00@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:46:19 -0400 Subject: [PATCH 5/5] address PR review feedback for unstable-book-gen --- compiler/rustc_session/src/options.rs | 4 ++++ src/tools/unstable-book-gen/src/main.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 836f7d1cb456f..e8681237a03d8 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -604,6 +604,10 @@ macro_rules! tmod_enum { /// necessary code. The main gotcha of this macro is the `cgsetters` module which is a bunch of /// generated code to parse an option into its respective field in the struct. There are a few /// hand-written parsers for parsing specific types of values in this module. +/// +/// Note: this macro's invocation is also parsed by a `syn`-based parser in +/// `src/tools/unstable-book-gen/src/main.rs` to extract unstable option names and descriptions. +/// If the format of this macro changes, that parser may need to be updated as well. macro_rules! options { ($struct_name:ident, $tmod_enum_name:ident, $stat:ident, $optmod:ident, $prefix:expr, $outputname:expr, $($( #[$attr:meta] )* $opt:ident : $t:ty = ( diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs index 8985072cbd27b..21d4da46bef54 100644 --- a/src/tools/unstable-book-gen/src/main.rs +++ b/src/tools/unstable-book-gen/src/main.rs @@ -158,7 +158,7 @@ impl Parse for ParsedOptionEntry { return Err(syn::Error::new( name.span(), format!( - "unexpected field count for option `{name}`: expected 4 or 5, found {}", + "unexpected field count for option `{name}`: expected {REQUIRED_FIELDS} or {OPTIONAL_FIELDS}, found {}", tuple_fields.len() ), ));