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