1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use rustc_ast
::LitKind
;
3 use rustc_errors
::Applicability
::MachineApplicable
;
4 use rustc_hir
::{Expr, ExprKind, PathSegment, QPath, TyKind}
;
5 use rustc_lint
::{LateContext, LateLintPass}
;
7 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
8 use rustc_span
::{sym, symbol, Span}
;
10 declare_clippy_lint
! {
13 /// Checks for usage of `""` to create a `String`, such as `"".to_string()`, `"".to_owned()`,
14 /// `String::from("")` and others.
16 /// ### Why is this bad?
18 /// Different ways of creating an empty string makes your code less standardized, which can
23 /// let a = "".to_string();
24 /// let b: String = "".into();
28 /// let a = String::new();
29 /// let b = String::new();
31 #[clippy::version = "1.65.0"]
32 pub MANUAL_STRING_NEW
,
34 "empty String is being created manually"
36 declare_lint_pass
!(ManualStringNew
=> [MANUAL_STRING_NEW
]);
38 impl LateLintPass
<'_
> for ManualStringNew
{
39 fn check_expr(&mut self, cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
40 if expr
.span
.from_expansion() {
44 let ty
= cx
.typeck_results().expr_ty(expr
);
46 ty
::Adt(adt_def
, _
) if adt_def
.is_struct() => {
47 if cx
.tcx
.lang_items().string() != Some(adt_def
.did()) {
55 ExprKind
::Call(func
, args
) => {
56 parse_call(cx
, expr
.span
, func
, args
);
58 ExprKind
::MethodCall(path_segment
, receiver
, ..) => {
59 parse_method_call(cx
, expr
.span
, path_segment
, receiver
);
66 /// Checks if an expression's kind corresponds to an empty &str.
67 fn is_expr_kind_empty_str(expr_kind
: &ExprKind
<'_
>) -> bool
{
68 if let ExprKind
::Lit(lit
) = expr_kind
&&
69 let LitKind
::Str(value
, _
) = lit
.node
&&
70 value
== symbol
::kw
::Empty
78 fn warn_then_suggest(cx
: &LateContext
<'_
>, span
: Span
) {
83 "empty String is being created manually",
85 "String::new()".into(),
90 /// Tries to parse an expression as a method call, emitting the warning if necessary.
91 fn parse_method_call(cx
: &LateContext
<'_
>, span
: Span
, path_segment
: &PathSegment
<'_
>, receiver
: &Expr
<'_
>) {
92 let ident
= path_segment
.ident
.as_str();
93 let method_arg_kind
= &receiver
.kind
;
94 if ["to_string", "to_owned", "into"].contains(&ident
) && is_expr_kind_empty_str(method_arg_kind
) {
95 warn_then_suggest(cx
, span
);
96 } else if let ExprKind
::Call(func
, args
) = method_arg_kind
{
97 // If our first argument is a function call itself, it could be an `unwrap`-like function.
98 // E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc.
99 parse_call(cx
, span
, func
, args
);
103 /// Tries to parse an expression as a function call, emitting the warning if necessary.
104 fn parse_call(cx
: &LateContext
<'_
>, span
: Span
, func
: &Expr
<'_
>, args
: &[Expr
<'_
>]) {
109 let arg_kind
= &args
[0].kind
;
110 if let ExprKind
::Path(qpath
) = &func
.kind
{
111 if let QPath
::TypeRelative(_
, _
) = qpath
{
112 // String::from(...) or String::try_from(...)
113 if let QPath
::TypeRelative(ty
, path_seg
) = qpath
&&
114 [sym
::from
, sym
::try_from
].contains(&path_seg
.ident
.name
) &&
115 let TyKind
::Path(qpath
) = &ty
.kind
&&
116 let QPath
::Resolved(_
, path
) = qpath
&&
117 let [path_seg
] = path
.segments
&&
118 path_seg
.ident
.name
== sym
::String
&&
119 is_expr_kind_empty_str(arg_kind
)
121 warn_then_suggest(cx
, span
);
123 } else if let QPath
::Resolved(_
, path
) = qpath
{
124 // From::from(...) or TryFrom::try_from(...)
125 if let [path_seg1
, path_seg2
] = path
.segments
&&
126 is_expr_kind_empty_str(arg_kind
) && (
127 (path_seg1
.ident
.name
== sym
::From
&& path_seg2
.ident
.name
== sym
::from
) ||
128 (path_seg1
.ident
.name
== sym
::TryFrom
&& path_seg2
.ident
.name
== sym
::try_from
)
131 warn_then_suggest(cx
, span
);