@@ -13,7 +13,9 @@ use rustc_hir::def::{DefKind, Res};
13
13
use rustc_hir:: def_id:: DefId ;
14
14
use rustc_hir:: hir_id:: CRATE_HIR_ID ;
15
15
use rustc_hir:: intravisit:: { NestedVisitorMap , Visitor } ;
16
- use rustc_hir:: { Crate , Expr , ExprKind , HirId , Item , MutTy , Mutability , Node , Path , StmtKind , Ty , TyKind } ;
16
+ use rustc_hir:: {
17
+ BinOpKind , Crate , Expr , ExprKind , HirId , Item , MutTy , Mutability , Node , Path , StmtKind , Ty , TyKind , UnOp ,
18
+ } ;
17
19
use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass } ;
18
20
use rustc_middle:: hir:: map:: Map ;
19
21
use rustc_middle:: mir:: interpret:: ConstValue ;
@@ -273,6 +275,28 @@ declare_clippy_lint! {
273
275
"interning a symbol that is pre-interned and defined as a constant"
274
276
}
275
277
278
+ declare_clippy_lint ! {
279
+ /// **What it does:** Checks for unnecessary conversion from Symbol to a string.
280
+ ///
281
+ /// **Why is this bad?** It's faster use symbols directly intead of strings.
282
+ ///
283
+ /// **Known problems:** None.
284
+ ///
285
+ /// **Example:**
286
+ /// Bad:
287
+ /// ```rust,ignore
288
+ /// symbol.as_str() == "clippy";
289
+ /// ```
290
+ ///
291
+ /// Good:
292
+ /// ```rust,ignore
293
+ /// symbol == sym::clippy;
294
+ /// ```
295
+ pub UNNECESSARY_SYMBOL_STR ,
296
+ internal,
297
+ "unnecessary conversion between Symbol and string"
298
+ }
299
+
276
300
declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
277
301
278
302
impl EarlyLintPass for ClippyLintsInternal {
@@ -873,7 +897,7 @@ pub struct InterningDefinedSymbol {
873
897
symbol_map : FxHashMap < u32 , DefId > ,
874
898
}
875
899
876
- impl_lint_pass ! ( InterningDefinedSymbol => [ INTERNING_DEFINED_SYMBOL ] ) ;
900
+ impl_lint_pass ! ( InterningDefinedSymbol => [ INTERNING_DEFINED_SYMBOL , UNNECESSARY_SYMBOL_STR ] ) ;
877
901
878
902
impl < ' tcx > LateLintPass < ' tcx > for InterningDefinedSymbol {
879
903
fn check_crate ( & mut self , cx : & LateContext < ' _ > , _: & Crate < ' _ > ) {
@@ -919,5 +943,130 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
919
943
) ;
920
944
}
921
945
}
946
+ if let ExprKind :: Binary ( op, left, right) = expr. kind {
947
+ if matches ! ( op. node, BinOpKind :: Eq | BinOpKind :: Ne ) {
948
+ let data = [
949
+ ( left, self . symbol_str_expr ( left, cx) ) ,
950
+ ( right, self . symbol_str_expr ( right, cx) ) ,
951
+ ] ;
952
+ match data {
953
+ // both operands are a symbol string
954
+ [ ( _, Some ( left) ) , ( _, Some ( right) ) ] => {
955
+ span_lint_and_sugg (
956
+ cx,
957
+ UNNECESSARY_SYMBOL_STR ,
958
+ expr. span ,
959
+ "unnecessary `Symbol` to string conversion" ,
960
+ "try" ,
961
+ format ! (
962
+ "{} {} {}" ,
963
+ left. as_symbol_snippet( cx) ,
964
+ op. node. as_str( ) ,
965
+ right. as_symbol_snippet( cx) ,
966
+ ) ,
967
+ Applicability :: MachineApplicable ,
968
+ ) ;
969
+ } ,
970
+ // one of the operands is a symbol string
971
+ [ ( expr, Some ( symbol) ) , _] | [ _, ( expr, Some ( symbol) ) ] => {
972
+ // creating an owned string for comparison
973
+ if matches ! ( symbol, SymbolStrExpr :: Expr { is_to_owned: true , .. } ) {
974
+ span_lint_and_sugg (
975
+ cx,
976
+ UNNECESSARY_SYMBOL_STR ,
977
+ expr. span ,
978
+ "unnecessary string allocation" ,
979
+ "try" ,
980
+ format ! ( "{}.as_str()" , symbol. as_symbol_snippet( cx) ) ,
981
+ Applicability :: MachineApplicable ,
982
+ ) ;
983
+ }
984
+ } ,
985
+ // nothing found
986
+ [ ( _, None ) , ( _, None ) ] => { } ,
987
+ }
988
+ }
989
+ }
990
+ }
991
+ }
992
+
993
+ impl InterningDefinedSymbol {
994
+ fn symbol_str_expr < ' tcx > ( & self , expr : & ' tcx Expr < ' tcx > , cx : & LateContext < ' tcx > ) -> Option < SymbolStrExpr < ' tcx > > {
995
+ static IDENT_STR_PATHS : & [ & [ & str ] ] = & [ & paths:: IDENT_AS_STR , & paths:: TO_STRING_METHOD ] ;
996
+ static SYMBOL_STR_PATHS : & [ & [ & str ] ] = & [
997
+ & paths:: SYMBOL_AS_STR ,
998
+ & paths:: SYMBOL_TO_IDENT_STRING ,
999
+ & paths:: TO_STRING_METHOD ,
1000
+ ] ;
1001
+ // SymbolStr might be de-referenced: `&*symbol.as_str()`
1002
+ let call = if_chain ! {
1003
+ if let ExprKind :: AddrOf ( _, _, e) = expr. kind;
1004
+ if let ExprKind :: Unary ( UnOp :: UnDeref , e) = e. kind;
1005
+ then { e } else { expr }
1006
+ } ;
1007
+ if_chain ! {
1008
+ // is a method call
1009
+ if let ExprKind :: MethodCall ( _, _, [ item] , _) = call. kind;
1010
+ if let Some ( did) = cx. typeck_results( ) . type_dependent_def_id( call. hir_id) ;
1011
+ let ty = cx. typeck_results( ) . expr_ty( item) ;
1012
+ // ...on either an Ident or a Symbol
1013
+ if let Some ( is_ident) = if match_type( cx, ty, & paths:: SYMBOL ) {
1014
+ Some ( false )
1015
+ } else if match_type( cx, ty, & paths:: IDENT ) {
1016
+ Some ( true )
1017
+ } else {
1018
+ None
1019
+ } ;
1020
+ // ...which converts it to a string
1021
+ let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS } ;
1022
+ if let Some ( path) = paths. iter( ) . find( |path| match_def_path( cx, did, path) ) ;
1023
+ then {
1024
+ let is_to_owned = path. last( ) . unwrap( ) . ends_with( "string" ) ;
1025
+ return Some ( SymbolStrExpr :: Expr {
1026
+ item,
1027
+ is_ident,
1028
+ is_to_owned,
1029
+ } ) ;
1030
+ }
1031
+ }
1032
+ // is a string constant
1033
+ if let Some ( Constant :: Str ( s) ) = constant_simple ( cx, cx. typeck_results ( ) , expr) {
1034
+ let value = Symbol :: intern ( & s) . as_u32 ( ) ;
1035
+ // ...which matches a symbol constant
1036
+ if let Some ( & def_id) = self . symbol_map . get ( & value) {
1037
+ return Some ( SymbolStrExpr :: Const ( def_id) ) ;
1038
+ }
1039
+ }
1040
+ None
1041
+ }
1042
+ }
1043
+
1044
+ enum SymbolStrExpr < ' tcx > {
1045
+ /// a string constant with a corresponding symbol constant
1046
+ Const ( DefId ) ,
1047
+ /// a "symbol to string" expression like `symbol.as_str()`
1048
+ Expr {
1049
+ /// part that evaluates to `Symbol` or `Ident`
1050
+ item : & ' tcx Expr < ' tcx > ,
1051
+ is_ident : bool ,
1052
+ /// whether an owned `String` is created like `to_ident_string()`
1053
+ is_to_owned : bool ,
1054
+ } ,
1055
+ }
1056
+
1057
+ impl < ' tcx > SymbolStrExpr < ' tcx > {
1058
+ /// Returns a snippet that evaluates to a `Symbol` and is const if possible
1059
+ fn as_symbol_snippet ( & self , cx : & LateContext < ' _ > ) -> Cow < ' tcx , str > {
1060
+ match * self {
1061
+ Self :: Const ( def_id) => cx. tcx . def_path_str ( def_id) . into ( ) ,
1062
+ Self :: Expr { item, is_ident, .. } => {
1063
+ let mut snip = snippet ( cx, item. span . source_callsite ( ) , ".." ) ;
1064
+ if is_ident {
1065
+ // get `Ident.name`
1066
+ snip. to_mut ( ) . push_str ( ".name" ) ;
1067
+ }
1068
+ snip
1069
+ } ,
1070
+ }
922
1071
}
923
1072
}
0 commit comments