Skip to content

Commit d1ed4f4

Browse files
authored
Rollup merge of rust-lang#146611 - lolbinarycat:bootstrap-toml-wrong-section-diagnostic, r=Kobzol
bootstrap: emit hint if a config key is used in the wrong section based on discussion on rust-lang#146591 now, if the user puts `build.download-rustc` in `bootstrap.toml`, they'll get a diagnostic: ``hint: try moving `download-rustc` to the `rust` section`` and if they nest things too much (`rust.rust.download-rustc`): ``hint: section name `rust` used as a key within a section`` if they specify a top-level key within a section (`rust.profile`): ``hint: try using `profile` as a top level key`` r? `@Kobzol`
2 parents b1a7246 + 9c42379 commit d1ed4f4

File tree

1 file changed

+98
-11
lines changed

1 file changed

+98
-11
lines changed

src/bootstrap/src/core/config/config.rs

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,13 +1853,7 @@ fn load_toml_config(
18531853
} else {
18541854
toml_path.clone()
18551855
});
1856-
(
1857-
get_toml(&toml_path).unwrap_or_else(|e| {
1858-
eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
1859-
exit!(2);
1860-
}),
1861-
path,
1862-
)
1856+
(get_toml(&toml_path).unwrap_or_else(|e| bad_config(&toml_path, e)), path)
18631857
} else {
18641858
(TomlConfig::default(), None)
18651859
}
@@ -1892,10 +1886,8 @@ fn postprocess_toml(
18921886
.unwrap()
18931887
.join(include_path);
18941888

1895-
let included_toml = get_toml(&include_path).unwrap_or_else(|e| {
1896-
eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display());
1897-
exit!(2);
1898-
});
1889+
let included_toml =
1890+
get_toml(&include_path).unwrap_or_else(|e| bad_config(&include_path, e));
18991891
toml.merge(
19001892
Some(include_path),
19011893
&mut Default::default(),
@@ -2398,3 +2390,98 @@ pub(crate) fn read_file_by_commit<'a>(
23982390
git.arg("show").arg(format!("{commit}:{}", file.to_str().unwrap()));
23992391
git.run_capture_stdout(dwn_ctx.exec_ctx).stdout()
24002392
}
2393+
2394+
fn bad_config(toml_path: &Path, e: toml::de::Error) -> ! {
2395+
eprintln!("ERROR: Failed to parse '{}': {e}", toml_path.display());
2396+
let e_s = e.to_string();
2397+
if e_s.contains("unknown field")
2398+
&& let Some(field_name) = e_s.split("`").nth(1)
2399+
&& let sections = find_correct_section_for_field(field_name)
2400+
&& !sections.is_empty()
2401+
{
2402+
if sections.len() == 1 {
2403+
match sections[0] {
2404+
WouldBeValidFor::TopLevel { is_section } => {
2405+
if is_section {
2406+
eprintln!(
2407+
"hint: section name `{field_name}` used as a key within a section"
2408+
);
2409+
} else {
2410+
eprintln!("hint: try using `{field_name}` as a top level key");
2411+
}
2412+
}
2413+
WouldBeValidFor::Section(section) => {
2414+
eprintln!("hint: try moving `{field_name}` to the `{section}` section")
2415+
}
2416+
}
2417+
} else {
2418+
eprintln!(
2419+
"hint: `{field_name}` would be valid {}",
2420+
join_oxford_comma(sections.iter(), "or"),
2421+
);
2422+
}
2423+
}
2424+
2425+
exit!(2);
2426+
}
2427+
2428+
#[derive(Copy, Clone, Debug)]
2429+
enum WouldBeValidFor {
2430+
TopLevel { is_section: bool },
2431+
Section(&'static str),
2432+
}
2433+
2434+
fn join_oxford_comma(
2435+
mut parts: impl ExactSizeIterator<Item = impl std::fmt::Display>,
2436+
conj: &str,
2437+
) -> String {
2438+
use std::fmt::Write;
2439+
let mut out = String::new();
2440+
2441+
assert!(parts.len() > 1);
2442+
while let Some(part) = parts.next() {
2443+
if parts.len() == 0 {
2444+
write!(&mut out, "{conj} {part}")
2445+
} else {
2446+
write!(&mut out, "{part}, ")
2447+
}
2448+
.unwrap();
2449+
}
2450+
out
2451+
}
2452+
2453+
impl std::fmt::Display for WouldBeValidFor {
2454+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2455+
match self {
2456+
Self::TopLevel { .. } => write!(f, "at top level"),
2457+
Self::Section(section_name) => write!(f, "in section `{section_name}`"),
2458+
}
2459+
}
2460+
}
2461+
2462+
fn find_correct_section_for_field(field_name: &str) -> Vec<WouldBeValidFor> {
2463+
let sections = ["build", "install", "llvm", "gcc", "rust", "dist"];
2464+
sections
2465+
.iter()
2466+
.map(Some)
2467+
.chain([None])
2468+
.filter_map(|section_name| {
2469+
let dummy_config_str = if let Some(section_name) = section_name {
2470+
format!("{section_name}.{field_name} = 0\n")
2471+
} else {
2472+
format!("{field_name} = 0\n")
2473+
};
2474+
let is_unknown_field = toml::from_str::<toml::Value>(&dummy_config_str)
2475+
.and_then(TomlConfig::deserialize)
2476+
.err()
2477+
.is_some_and(|e| e.to_string().contains("unknown field"));
2478+
if is_unknown_field {
2479+
None
2480+
} else {
2481+
Some(section_name.copied().map(WouldBeValidFor::Section).unwrap_or_else(|| {
2482+
WouldBeValidFor::TopLevel { is_section: sections.contains(&field_name) }
2483+
}))
2484+
}
2485+
})
2486+
.collect()
2487+
}

0 commit comments

Comments
 (0)