@@ -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