@@ -13,7 +13,9 @@ use rustc_hir::def::{DefKind, Res};
1313use rustc_hir:: def_id:: DefId ;
1414use rustc_hir:: hir_id:: CRATE_HIR_ID ;
1515use 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+ } ;
1719use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass } ;
1820use rustc_middle:: hir:: map:: Map ;
1921use rustc_middle:: mir:: interpret:: ConstValue ;
@@ -273,6 +275,28 @@ declare_clippy_lint! {
273275 "interning a symbol that is pre-interned and defined as a constant"
274276}
275277
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+
276300declare_lint_pass ! ( ClippyLintsInternal => [ CLIPPY_LINTS_INTERNAL ] ) ;
277301
278302impl EarlyLintPass for ClippyLintsInternal {
@@ -873,7 +897,7 @@ pub struct InterningDefinedSymbol {
873897 symbol_map : FxHashMap < u32 , DefId > ,
874898}
875899
876- impl_lint_pass ! ( InterningDefinedSymbol => [ INTERNING_DEFINED_SYMBOL ] ) ;
900+ impl_lint_pass ! ( InterningDefinedSymbol => [ INTERNING_DEFINED_SYMBOL , UNNECESSARY_SYMBOL_STR ] ) ;
877901
878902impl < ' tcx > LateLintPass < ' tcx > for InterningDefinedSymbol {
879903 fn check_crate ( & mut self , cx : & LateContext < ' _ > , _: & Crate < ' _ > ) {
@@ -919,5 +943,130 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
919943 ) ;
920944 }
921945 }
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+ }
9221071 }
9231072}
0 commit comments