@@ -2,7 +2,9 @@ use crate::msrvs::Msrv;
2
2
use crate :: types:: { DisallowedPath , MacroMatcher , MatchLintBehaviour , PubUnderscoreFieldsBehaviour , Rename } ;
3
3
use crate :: ClippyConfiguration ;
4
4
use rustc_data_structures:: fx:: FxHashSet ;
5
+ use rustc_errors:: Applicability ;
5
6
use rustc_session:: Session ;
7
+ use rustc_span:: edit_distance:: edit_distance;
6
8
use rustc_span:: { BytePos , Pos , SourceFile , Span , SyntaxContext } ;
7
9
use serde:: de:: { IgnoredAny , IntoDeserializer , MapAccess , Visitor } ;
8
10
use serde:: { Deserialize , Deserializer , Serialize } ;
@@ -59,18 +61,25 @@ impl TryConf {
59
61
#[ derive( Debug ) ]
60
62
struct ConfError {
61
63
message : String ,
64
+ suggestion : Option < Suggestion > ,
62
65
span : Span ,
63
66
}
64
67
65
68
impl ConfError {
66
69
fn from_toml ( file : & SourceFile , error : & toml:: de:: Error ) -> Self {
67
70
let span = error. span ( ) . unwrap_or ( 0 ..file. source_len . 0 as usize ) ;
68
- Self :: spanned ( file, error. message ( ) , span)
71
+ Self :: spanned ( file, error. message ( ) , None , span)
69
72
}
70
73
71
- fn spanned ( file : & SourceFile , message : impl Into < String > , span : Range < usize > ) -> Self {
74
+ fn spanned (
75
+ file : & SourceFile ,
76
+ message : impl Into < String > ,
77
+ suggestion : Option < Suggestion > ,
78
+ span : Range < usize > ,
79
+ ) -> Self {
72
80
Self {
73
81
message : message. into ( ) ,
82
+ suggestion,
74
83
span : Span :: new (
75
84
file. start_pos + BytePos :: from_usize ( span. start ) ,
76
85
file. start_pos + BytePos :: from_usize ( span. end ) ,
@@ -147,16 +156,18 @@ macro_rules! define_Conf {
147
156
match Field :: deserialize( name. get_ref( ) . as_str( ) . into_deserializer( ) ) {
148
157
Err ( e) => {
149
158
let e: FieldError = e;
150
- errors. push( ConfError :: spanned( self . 0 , e. 0 , name. span( ) ) ) ;
159
+ errors. push( ConfError :: spanned( self . 0 , e. error , e . suggestion , name. span( ) ) ) ;
151
160
}
152
161
$( Ok ( Field :: $name) => {
153
- $( warnings. push( ConfError :: spanned( self . 0 , format!( "deprecated field `{}`. {}" , name. get_ref( ) , $dep) , name. span( ) ) ) ; ) ?
162
+ $( warnings. push( ConfError :: spanned( self . 0 , format!( "deprecated field `{}`. {}" , name. get_ref( ) , $dep) , None , name. span( ) ) ) ; ) ?
154
163
let raw_value = map. next_value:: <toml:: Spanned <toml:: Value >>( ) ?;
155
164
let value_span = raw_value. span( ) ;
156
165
match <$ty>:: deserialize( raw_value. into_inner( ) ) {
157
- Err ( e) => errors. push( ConfError :: spanned( self . 0 , e. to_string( ) . replace( '\n' , " " ) . trim( ) , value_span) ) ,
166
+ Err ( e) => errors. push( ConfError :: spanned( self . 0 , e. to_string( ) . replace( '\n' , " " ) . trim( ) , None , value_span) ) ,
158
167
Ok ( value) => match $name {
159
- Some ( _) => errors. push( ConfError :: spanned( self . 0 , format!( "duplicate field `{}`" , name. get_ref( ) ) , name. span( ) ) ) ,
168
+ Some ( _) => {
169
+ errors. push( ConfError :: spanned( self . 0 , format!( "duplicate field `{}`" , name. get_ref( ) ) , None , name. span( ) ) ) ;
170
+ }
160
171
None => {
161
172
$name = Some ( value) ;
162
173
// $new_conf is the same as one of the defined `$name`s, so
@@ -165,7 +176,7 @@ macro_rules! define_Conf {
165
176
Some ( _) => errors. push( ConfError :: spanned( self . 0 , concat!(
166
177
"duplicate field `" , stringify!( $new_conf) ,
167
178
"` (provided as `" , stringify!( $name) , "`)"
168
- ) , name. span( ) ) ) ,
179
+ ) , None , name. span( ) ) ) ,
169
180
None => $new_conf = $name. clone( ) ,
170
181
} ) ?
171
182
} ,
@@ -673,10 +684,16 @@ impl Conf {
673
684
674
685
// all conf errors are non-fatal, we just use the default conf in case of error
675
686
for error in errors {
676
- sess. dcx ( ) . span_err (
687
+ let mut diag = sess. dcx ( ) . struct_span_err (
677
688
error. span ,
678
689
format ! ( "error reading Clippy's configuration file: {}" , error. message) ,
679
690
) ;
691
+
692
+ if let Some ( sugg) = error. suggestion {
693
+ diag. span_suggestion ( error. span , sugg. message , sugg. suggestion , Applicability :: MaybeIncorrect ) ;
694
+ }
695
+
696
+ diag. emit ( ) ;
680
697
}
681
698
682
699
for warning in warnings {
@@ -693,19 +710,31 @@ impl Conf {
693
710
const SEPARATOR_WIDTH : usize = 4 ;
694
711
695
712
#[ derive( Debug ) ]
696
- struct FieldError ( String ) ;
713
+ struct FieldError {
714
+ error : String ,
715
+ suggestion : Option < Suggestion > ,
716
+ }
717
+
718
+ #[ derive( Debug ) ]
719
+ struct Suggestion {
720
+ message : & ' static str ,
721
+ suggestion : & ' static str ,
722
+ }
697
723
698
724
impl std:: error:: Error for FieldError { }
699
725
700
726
impl Display for FieldError {
701
727
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
702
- f. pad ( & self . 0 )
728
+ f. pad ( & self . error )
703
729
}
704
730
}
705
731
706
732
impl serde:: de:: Error for FieldError {
707
733
fn custom < T : Display > ( msg : T ) -> Self {
708
- Self ( msg. to_string ( ) )
734
+ Self {
735
+ error : msg. to_string ( ) ,
736
+ suggestion : None ,
737
+ }
709
738
}
710
739
711
740
fn unknown_field ( field : & str , expected : & ' static [ & ' static str ] ) -> Self {
@@ -727,7 +756,20 @@ impl serde::de::Error for FieldError {
727
756
write ! ( msg, "{:SEPARATOR_WIDTH$}{field:column_width$}" , " " ) . unwrap ( ) ;
728
757
}
729
758
}
730
- Self ( msg)
759
+
760
+ let suggestion = expected
761
+ . iter ( )
762
+ . filter_map ( |expected| {
763
+ let dist = edit_distance ( field, expected, 4 ) ?;
764
+ Some ( ( dist, expected) )
765
+ } )
766
+ . min_by_key ( |& ( dist, _) | dist)
767
+ . map ( |( _, suggestion) | Suggestion {
768
+ message : "perhaps you meant" ,
769
+ suggestion,
770
+ } ) ;
771
+
772
+ Self { error : msg, suggestion }
731
773
}
732
774
}
733
775
0 commit comments