@@ -1853,13 +1853,7 @@ fn load_toml_config(
1853
1853
} else {
1854
1854
toml_path. clone ( )
1855
1855
} ) ;
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)
1863
1857
} else {
1864
1858
( TomlConfig :: default ( ) , None )
1865
1859
}
@@ -1892,10 +1886,8 @@ fn postprocess_toml(
1892
1886
. unwrap ( )
1893
1887
. join ( include_path) ;
1894
1888
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) ) ;
1899
1891
toml. merge (
1900
1892
Some ( include_path) ,
1901
1893
& mut Default :: default ( ) ,
@@ -2398,3 +2390,98 @@ pub(crate) fn read_file_by_commit<'a>(
2398
2390
git. arg ( "show" ) . arg ( format ! ( "{commit}:{}" , file. to_str( ) . unwrap( ) ) ) ;
2399
2391
git. run_capture_stdout ( dwn_ctx. exec_ctx ) . stdout ( )
2400
2392
}
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