1 use clippy_utils
::consts
::{constant_simple, Constant}
;
2 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}
;
3 use clippy_utils
::source
::snippet
;
4 use clippy_utils
::ty
::match_type
;
6 is_else_clause
, is_expn_of
, is_expr_path_def_path
, is_lint_allowed
, match_def_path
, method_calls
, path_to_res
,
9 use if_chain
::if_chain
;
10 use rustc_ast
::ast
::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId}
;
11 use rustc_ast
::visit
::FnKind
;
12 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
13 use rustc_errors
::Applicability
;
15 use rustc_hir
::def
::{DefKind, Res}
;
16 use rustc_hir
::def_id
::DefId
;
17 use rustc_hir
::hir_id
::CRATE_HIR_ID
;
18 use rustc_hir
::intravisit
::{NestedVisitorMap, Visitor}
;
20 BinOpKind
, Block
, Crate
, Expr
, ExprKind
, HirId
, Item
, Local
, MatchSource
, MutTy
, Mutability
, Node
, Path
, Stmt
,
21 StmtKind
, Ty
, TyKind
, UnOp
,
23 use rustc_lint
::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
;
24 use rustc_middle
::hir
::map
::Map
;
25 use rustc_middle
::mir
::interpret
::ConstValue
;
27 use rustc_session
::{declare_lint_pass, declare_tool_lint, impl_lint_pass}
;
28 use rustc_span
::source_map
::Spanned
;
29 use rustc_span
::symbol
::{Symbol, SymbolStr}
;
30 use rustc_span
::{BytePos, Span}
;
31 use rustc_typeck
::hir_ty_to_ty
;
33 use std
::borrow
::{Borrow, Cow}
;
35 #[cfg(feature = "metadata-collector-lint")]
36 pub mod metadata_collector
;
38 declare_clippy_lint
! {
40 /// Checks for various things we like to keep tidy in clippy.
42 /// ### Why is this bad?
43 /// We like to pretend we're an example of tidy code.
46 /// Wrong ordering of the util::paths constants.
47 pub CLIPPY_LINTS_INTERNAL
,
49 "various things that will negatively affect your clippy experience"
52 declare_clippy_lint
! {
54 /// Ensures every lint is associated to a `LintPass`.
56 /// ### Why is this bad?
57 /// The compiler only knows lints via a `LintPass`. Without
58 /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
59 /// know the name of the lint.
61 /// ### Known problems
62 /// Only checks for lints associated using the
63 /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
67 /// declare_lint! { pub LINT_1, ... }
68 /// declare_lint! { pub LINT_2, ... }
69 /// declare_lint! { pub FORGOTTEN_LINT, ... }
71 /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
72 /// // missing FORGOTTEN_LINT
74 pub LINT_WITHOUT_LINT_PASS
,
76 "declaring a lint without associating it in a LintPass"
79 declare_clippy_lint
! {
81 /// Checks for calls to `cx.span_lint*` and suggests to use the `utils::*`
82 /// variant of the function.
84 /// ### Why is this bad?
85 /// The `utils::*` variants also add a link to the Clippy documentation to the
86 /// warning/error messages.
91 /// cx.span_lint(LINT_NAME, "message");
96 /// utils::span_lint(cx, LINT_NAME, "message");
98 pub COMPILER_LINT_FUNCTIONS
,
100 "usage of the lint functions of the compiler instead of the utils::* variant"
103 declare_clippy_lint
! {
105 /// Checks for calls to `cx.outer().expn_data()` and suggests to use
106 /// the `cx.outer_expn_data()`
108 /// ### Why is this bad?
109 /// `cx.outer_expn_data()` is faster and more concise.
114 /// expr.span.ctxt().outer().expn_data()
119 /// expr.span.ctxt().outer_expn_data()
121 pub OUTER_EXPN_EXPN_DATA
,
123 "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
126 declare_clippy_lint
! {
128 /// Not an actual lint. This lint is only meant for testing our customized internal compiler
129 /// error message by calling `panic`.
131 /// ### Why is this bad?
132 /// ICE in large quantities can damage your teeth
141 "this message should not appear anywhere as we ICE before and don't emit the lint"
144 declare_clippy_lint
! {
146 /// Checks for cases of an auto-generated lint without an updated description,
147 /// i.e. `default lint description`.
149 /// ### Why is this bad?
150 /// Indicates that the lint is not finished.
155 /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
160 /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
164 "found 'default lint description' in a lint declaration"
167 declare_clippy_lint
! {
169 /// Lints `span_lint_and_then` function calls, where the
170 /// closure argument has only one statement and that statement is a method
171 /// call to `span_suggestion`, `span_help`, `span_note` (using the same
172 /// span), `help` or `note`.
174 /// These usages of `span_lint_and_then` should be replaced with one of the
175 /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
176 /// `span_lint_and_note`.
178 /// ### Why is this bad?
179 /// Using the wrapper `span_lint_and_*` functions, is more
180 /// convenient, readable and less error prone.
185 /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
186 /// diag.span_suggestion(
189 /// sugg.to_string(),
190 /// Applicability::MachineApplicable,
193 /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
194 /// diag.span_help(expr.span, help_msg);
196 /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
197 /// diag.help(help_msg);
199 /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
200 /// diag.span_note(expr.span, note_msg);
202 /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
203 /// diag.note(note_msg);
209 /// span_lint_and_sugg(
215 /// sugg.to_string(),
216 /// Applicability::MachineApplicable,
218 /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
219 /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
220 /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
221 /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
223 pub COLLAPSIBLE_SPAN_LINT_CALLS
,
225 "found collapsible `span_lint_and_then` calls"
228 declare_clippy_lint
! {
230 /// Checks for calls to `utils::match_type()` on a type diagnostic item
231 /// and suggests to use `utils::is_type_diagnostic_item()` instead.
233 /// ### Why is this bad?
234 /// `utils::is_type_diagnostic_item()` does not require hardcoded paths.
239 /// utils::match_type(cx, ty, &paths::VEC)
244 /// utils::is_type_diagnostic_item(cx, ty, sym::vec_type)
246 pub MATCH_TYPE_ON_DIAGNOSTIC_ITEM
,
248 "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
251 declare_clippy_lint
! {
253 /// Checks the paths module for invalid paths.
255 /// ### Why is this bad?
256 /// It indicates a bug in the code.
265 declare_clippy_lint
! {
267 /// Checks for interning symbols that have already been pre-interned and defined as constants.
269 /// ### Why is this bad?
270 /// It's faster and easier to use the symbol constant.
275 /// let _ = sym!(f32);
280 /// let _ = sym::f32;
282 pub INTERNING_DEFINED_SYMBOL
,
284 "interning a symbol that is pre-interned and defined as a constant"
287 declare_clippy_lint
! {
289 /// Checks for unnecessary conversion from Symbol to a string.
291 /// ### Why is this bad?
292 /// It's faster use symbols directly intead of strings.
297 /// symbol.as_str() == "clippy";
302 /// symbol == sym::clippy;
304 pub UNNECESSARY_SYMBOL_STR
,
306 "unnecessary conversion between Symbol and string"
309 declare_clippy_lint
! {
310 /// Finds unidiomatic usage of `if_chain!`
313 "non-idiomatic `if_chain!` usage"
316 declare_lint_pass
!(ClippyLintsInternal
=> [CLIPPY_LINTS_INTERNAL
]);
318 impl EarlyLintPass
for ClippyLintsInternal
{
319 fn check_crate(&mut self, cx
: &EarlyContext
<'_
>, krate
: &AstCrate
) {
320 if let Some(utils
) = krate
.items
.iter().find(|item
| item
.ident
.name
.as_str() == "utils") {
321 if let ItemKind
::Mod(_
, ModKind
::Loaded(ref items
, ..)) = utils
.kind
{
322 if let Some(paths
) = items
.iter().find(|item
| item
.ident
.name
.as_str() == "paths") {
323 if let ItemKind
::Mod(_
, ModKind
::Loaded(ref items
, ..)) = paths
.kind
{
324 let mut last_name
: Option
<SymbolStr
> = None
;
326 let name
= item
.ident
.as_str();
327 if let Some(ref last_name
) = last_name
{
328 if **last_name
> *name
{
331 CLIPPY_LINTS_INTERNAL
,
333 "this constant should be before the previous constant due to lexical \
338 last_name
= Some(name
);
347 #[derive(Clone, Debug, Default)]
348 pub struct LintWithoutLintPass
{
349 declared_lints
: FxHashMap
<Symbol
, Span
>,
350 registered_lints
: FxHashSet
<Symbol
>,
353 impl_lint_pass
!(LintWithoutLintPass
=> [DEFAULT_LINT
, LINT_WITHOUT_LINT_PASS
]);
355 impl<'tcx
> LateLintPass
<'tcx
> for LintWithoutLintPass
{
356 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
357 if is_lint_allowed(cx
, DEFAULT_LINT
, item
.hir_id()) {
361 if let hir
::ItemKind
::Static(ty
, Mutability
::Not
, body_id
) = item
.kind
{
362 if is_lint_ref_type(cx
, ty
) {
363 let expr
= &cx
.tcx
.hir().body(body_id
).value
;
365 if let ExprKind
::AddrOf(_
, _
, inner_exp
) = expr
.kind
;
366 if let ExprKind
::Struct(_
, fields
, _
) = inner_exp
.kind
;
369 .find(|f
| f
.ident
.as_str() == "desc")
370 .expect("lints must have a description field");
371 if let ExprKind
::Lit(Spanned
{
372 node
: LitKind
::Str(ref sym
, _
),
374 }) = field
.expr
.kind
;
375 if sym
.as_str() == "default lint description";
382 &format
!("the lint `{}` has the default lint description", item
.ident
.name
),
386 self.declared_lints
.insert(item
.ident
.name
, item
.span
);
388 } else if is_expn_of(item
.span
, "impl_lint_pass").is_some()
389 || is_expn_of(item
.span
, "declare_lint_pass").is_some()
391 if let hir
::ItemKind
::Impl(hir
::Impl
{
393 items
: impl_item_refs
,
397 let mut collector
= LintCollector
{
398 output
: &mut self.registered_lints
,
401 let body_id
= cx
.tcx
.hir().body_owned_by(
404 .find(|iiref
| iiref
.ident
.as_str() == "get_lints")
405 .expect("LintPass needs to implement get_lints")
409 collector
.visit_expr(&cx
.tcx
.hir().body(body_id
).value
);
414 fn check_crate_post(&mut self, cx
: &LateContext
<'tcx
>, _
: &'tcx Crate
<'_
>) {
415 if is_lint_allowed(cx
, LINT_WITHOUT_LINT_PASS
, CRATE_HIR_ID
) {
419 for (lint_name
, &lint_span
) in &self.declared_lints
{
420 // When using the `declare_tool_lint!` macro, the original `lint_span`'s
421 // file points to "<rustc macros>".
422 // `compiletest-rs` thinks that's an error in a different file and
423 // just ignores it. This causes the test in compile-fail/lint_pass
424 // not able to capture the error.
425 // Therefore, we need to climb the macro expansion tree and find the
426 // actual span that invoked `declare_tool_lint!`:
427 let lint_span
= lint_span
.ctxt().outer_expn_data().call_site
;
429 if !self.registered_lints
.contains(lint_name
) {
432 LINT_WITHOUT_LINT_PASS
,
434 &format
!("the lint `{}` is not added to any `LintPass`", lint_name
),
441 fn is_lint_ref_type
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: &Ty
<'_
>) -> bool
{
446 mutbl
: Mutability
::Not
,
450 if let TyKind
::Path(ref path
) = inner
.kind
{
451 if let Res
::Def(DefKind
::Struct
, def_id
) = cx
.qpath_res(path
, inner
.hir_id
) {
452 return match_def_path(cx
, def_id
, &paths
::LINT
);
460 struct LintCollector
<'a
, 'tcx
> {
461 output
: &'a
mut FxHashSet
<Symbol
>,
462 cx
: &'a LateContext
<'tcx
>,
465 impl<'a
, 'tcx
> Visitor
<'tcx
> for LintCollector
<'a
, 'tcx
> {
466 type Map
= Map
<'tcx
>;
468 fn visit_path(&mut self, path
: &'tcx Path
<'_
>, _
: HirId
) {
469 if path
.segments
.len() == 1 {
470 self.output
.insert(path
.segments
[0].ident
.name
);
474 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
475 NestedVisitorMap
::All(self.cx
.tcx
.hir())
479 #[derive(Clone, Default)]
480 pub struct CompilerLintFunctions
{
481 map
: FxHashMap
<&'
static str, &'
static str>,
484 impl CompilerLintFunctions
{
486 pub fn new() -> Self {
487 let mut map
= FxHashMap
::default();
488 map
.insert("span_lint", "utils::span_lint");
489 map
.insert("struct_span_lint", "utils::span_lint");
490 map
.insert("lint", "utils::span_lint");
491 map
.insert("span_lint_note", "utils::span_lint_and_note");
492 map
.insert("span_lint_help", "utils::span_lint_and_help");
497 impl_lint_pass
!(CompilerLintFunctions
=> [COMPILER_LINT_FUNCTIONS
]);
499 impl<'tcx
> LateLintPass
<'tcx
> for CompilerLintFunctions
{
500 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
501 if is_lint_allowed(cx
, COMPILER_LINT_FUNCTIONS
, expr
.hir_id
) {
506 if let ExprKind
::MethodCall(path
, _
, args
, _
) = expr
.kind
;
507 let fn_name
= path
.ident
;
508 if let Some(sugg
) = self.map
.get(&*fn_name
.as_str());
509 let ty
= cx
.typeck_results().expr_ty(&args
[0]).peel_refs();
510 if match_type(cx
, ty
, &paths
::EARLY_CONTEXT
)
511 || match_type(cx
, ty
, &paths
::LATE_CONTEXT
);
515 COMPILER_LINT_FUNCTIONS
,
517 "usage of a compiler lint function",
519 &format
!("please use the Clippy variant of this function: `{}`", sugg
),
526 declare_lint_pass
!(OuterExpnDataPass
=> [OUTER_EXPN_EXPN_DATA
]);
528 impl<'tcx
> LateLintPass
<'tcx
> for OuterExpnDataPass
{
529 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
530 if is_lint_allowed(cx
, OUTER_EXPN_EXPN_DATA
, expr
.hir_id
) {
534 let (method_names
, arg_lists
, spans
) = method_calls(expr
, 2);
535 let method_names
: Vec
<SymbolStr
> = method_names
.iter().map(|s
| s
.as_str()).collect();
536 let method_names
: Vec
<&str> = method_names
.iter().map(|s
| &**s
).collect();
538 if let ["expn_data", "outer_expn"] = method_names
.as_slice();
539 let args
= arg_lists
[1];
541 let self_arg
= &args
[0];
542 let self_ty
= cx
.typeck_results().expr_ty(self_arg
).peel_refs();
543 if match_type(cx
, self_ty
, &paths
::SYNTAX_CONTEXT
);
547 OUTER_EXPN_EXPN_DATA
,
548 spans
[1].with_hi(expr
.span
.hi()),
549 "usage of `outer_expn().expn_data()`",
551 "outer_expn_data()".to_string(),
552 Applicability
::MachineApplicable
,
559 declare_lint_pass
!(ProduceIce
=> [PRODUCE_ICE
]);
561 impl EarlyLintPass
for ProduceIce
{
562 fn check_fn(&mut self, _
: &EarlyContext
<'_
>, fn_kind
: FnKind
<'_
>, _
: Span
, _
: NodeId
) {
563 if is_trigger_fn(fn_kind
) {
564 panic
!("Would you like some help with that?");
569 fn is_trigger_fn(fn_kind
: FnKind
<'_
>) -> bool
{
571 FnKind
::Fn(_
, ident
, ..) => ident
.name
.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
572 FnKind
::Closure(..) => false,
576 declare_lint_pass
!(CollapsibleCalls
=> [COLLAPSIBLE_SPAN_LINT_CALLS
]);
578 impl<'tcx
> LateLintPass
<'tcx
> for CollapsibleCalls
{
579 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
580 if is_lint_allowed(cx
, COLLAPSIBLE_SPAN_LINT_CALLS
, expr
.hir_id
) {
585 if let ExprKind
::Call(func
, and_then_args
) = expr
.kind
;
586 if is_expr_path_def_path(cx
, func
, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
587 if and_then_args
.len() == 5;
588 if let ExprKind
::Closure(_
, _
, body_id
, _
, _
) = &and_then_args
[4].kind
;
589 let body
= cx
.tcx
.hir().body(*body_id
);
590 if let ExprKind
::Block(block
, _
) = &body
.value
.kind
;
591 let stmts
= &block
.stmts
;
592 if stmts
.len() == 1 && block
.expr
.is_none();
593 if let StmtKind
::Semi(only_expr
) = &stmts
[0].kind
;
594 if let ExprKind
::MethodCall(ps
, _
, span_call_args
, _
) = &only_expr
.kind
;
596 let and_then_snippets
= get_and_then_snippets(cx
, and_then_args
);
597 let mut sle
= SpanlessEq
::new(cx
).deny_side_effects();
598 match &*ps
.ident
.as_str() {
599 "span_suggestion" if sle
.eq_expr(&and_then_args
[2], &span_call_args
[1]) => {
600 suggest_suggestion(cx
, expr
, &and_then_snippets
, &span_suggestion_snippets(cx
, span_call_args
));
602 "span_help" if sle
.eq_expr(&and_then_args
[2], &span_call_args
[1]) => {
603 let help_snippet
= snippet(cx
, span_call_args
[2].span
, r
#""...""#);
604 suggest_help(cx
, expr
, &and_then_snippets
, help_snippet
.borrow(), true);
606 "span_note" if sle
.eq_expr(&and_then_args
[2], &span_call_args
[1]) => {
607 let note_snippet
= snippet(cx
, span_call_args
[2].span
, r
#""...""#);
608 suggest_note(cx
, expr
, &and_then_snippets
, note_snippet
.borrow(), true);
611 let help_snippet
= snippet(cx
, span_call_args
[1].span
, r
#""...""#);
612 suggest_help(cx
, expr
, &and_then_snippets
, help_snippet
.borrow(), false);
615 let note_snippet
= snippet(cx
, span_call_args
[1].span
, r
#""...""#);
616 suggest_note(cx
, expr
, &and_then_snippets
, note_snippet
.borrow(), false);
625 struct AndThenSnippets
<'a
> {
632 fn get_and_then_snippets
<'a
, 'hir
>(cx
: &LateContext
<'_
>, and_then_snippets
: &'hir
[Expr
<'hir
>]) -> AndThenSnippets
<'a
> {
633 let cx_snippet
= snippet(cx
, and_then_snippets
[0].span
, "cx");
634 let lint_snippet
= snippet(cx
, and_then_snippets
[1].span
, "..");
635 let span_snippet
= snippet(cx
, and_then_snippets
[2].span
, "span");
636 let msg_snippet
= snippet(cx
, and_then_snippets
[3].span
, r
#""...""#);
646 struct SpanSuggestionSnippets
<'a
> {
649 applicability
: Cow
<'a
, str>,
652 fn span_suggestion_snippets
<'a
, 'hir
>(
653 cx
: &LateContext
<'_
>,
654 span_call_args
: &'hir
[Expr
<'hir
>],
655 ) -> SpanSuggestionSnippets
<'a
> {
656 let help_snippet
= snippet(cx
, span_call_args
[2].span
, r
#""...""#);
657 let sugg_snippet
= snippet(cx
, span_call_args
[3].span
, "..");
658 let applicability_snippet
= snippet(cx
, span_call_args
[4].span
, "Applicability::MachineApplicable");
660 SpanSuggestionSnippets
{
663 applicability
: applicability_snippet
,
667 fn suggest_suggestion(
668 cx
: &LateContext
<'_
>,
670 and_then_snippets
: &AndThenSnippets
<'_
>,
671 span_suggestion_snippets
: &SpanSuggestionSnippets
<'_
>,
675 COLLAPSIBLE_SPAN_LINT_CALLS
,
677 "this call is collapsible",
680 "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
681 and_then_snippets
.cx
,
682 and_then_snippets
.lint
,
683 and_then_snippets
.span
,
684 and_then_snippets
.msg
,
685 span_suggestion_snippets
.help
,
686 span_suggestion_snippets
.sugg
,
687 span_suggestion_snippets
.applicability
689 Applicability
::MachineApplicable
,
694 cx
: &LateContext
<'_
>,
696 and_then_snippets
: &AndThenSnippets
<'_
>,
700 let option_span
= if with_span
{
701 format
!("Some({})", and_then_snippets
.span
)
708 COLLAPSIBLE_SPAN_LINT_CALLS
,
710 "this call is collapsible",
713 "span_lint_and_help({}, {}, {}, {}, {}, {})",
714 and_then_snippets
.cx
,
715 and_then_snippets
.lint
,
716 and_then_snippets
.span
,
717 and_then_snippets
.msg
,
721 Applicability
::MachineApplicable
,
726 cx
: &LateContext
<'_
>,
728 and_then_snippets
: &AndThenSnippets
<'_
>,
732 let note_span
= if with_span
{
733 format
!("Some({})", and_then_snippets
.span
)
740 COLLAPSIBLE_SPAN_LINT_CALLS
,
742 "this call is collspible",
745 "span_lint_and_note({}, {}, {}, {}, {}, {})",
746 and_then_snippets
.cx
,
747 and_then_snippets
.lint
,
748 and_then_snippets
.span
,
749 and_then_snippets
.msg
,
753 Applicability
::MachineApplicable
,
757 declare_lint_pass
!(MatchTypeOnDiagItem
=> [MATCH_TYPE_ON_DIAGNOSTIC_ITEM
]);
759 impl<'tcx
> LateLintPass
<'tcx
> for MatchTypeOnDiagItem
{
760 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
761 if is_lint_allowed(cx
, MATCH_TYPE_ON_DIAGNOSTIC_ITEM
, expr
.hir_id
) {
766 // Check if this is a call to utils::match_type()
767 if let ExprKind
::Call(fn_path
, [context
, ty
, ty_path
]) = expr
.kind
;
768 if is_expr_path_def_path(cx
, fn_path
, &["clippy_utils", "ty", "match_type"]);
769 // Extract the path to the matched type
770 if let Some(segments
) = path_to_matched_type(cx
, ty_path
);
771 let segments
: Vec
<&str> = segments
.iter().map(|sym
| &**sym
).collect();
772 if let Some(ty_did
) = path_to_res(cx
, &segments
[..]).opt_def_id();
773 // Check if the matched type is a diagnostic item
774 let diag_items
= cx
.tcx
.diagnostic_items(ty_did
.krate
);
775 if let Some(item_name
) = diag_items
.iter().find_map(|(k
, v
)| if *v
== ty_did { Some(k) }
else { None }
);
777 // TODO: check paths constants from external crates.
778 let cx_snippet
= snippet(cx
, context
.span
, "_");
779 let ty_snippet
= snippet(cx
, ty
.span
, "_");
783 MATCH_TYPE_ON_DIAGNOSTIC_ITEM
,
785 "usage of `clippy_utils::ty::match_type()` on a type diagnostic item",
787 format
!("clippy_utils::ty::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet
, ty_snippet
, item_name
),
788 Applicability
::MaybeIncorrect
,
795 fn path_to_matched_type(cx
: &LateContext
<'_
>, expr
: &hir
::Expr
<'_
>) -> Option
<Vec
<SymbolStr
>> {
796 use rustc_hir
::ItemKind
;
799 ExprKind
::AddrOf(.., expr
) => return path_to_matched_type(cx
, expr
),
800 ExprKind
::Path(qpath
) => match cx
.qpath_res(qpath
, expr
.hir_id
) {
801 Res
::Local(hir_id
) => {
802 let parent_id
= cx
.tcx
.hir().get_parent_node(hir_id
);
803 if let Some(Node
::Local(local
)) = cx
.tcx
.hir().find(parent_id
) {
804 if let Some(init
) = local
.init
{
805 return path_to_matched_type(cx
, init
);
809 Res
::Def(DefKind
::Const
| DefKind
::Static
, def_id
) => {
810 if let Some(Node
::Item(item
)) = cx
.tcx
.hir().get_if_local(def_id
) {
811 if let ItemKind
::Const(.., body_id
) | ItemKind
::Static(.., body_id
) = item
.kind
{
812 let body
= cx
.tcx
.hir().body(body_id
);
813 return path_to_matched_type(cx
, &body
.value
);
819 ExprKind
::Array(exprs
) => {
820 let segments
: Vec
<SymbolStr
> = exprs
823 if let ExprKind
::Lit(lit
) = &expr
.kind
{
824 if let LitKind
::Str(sym
, _
) = lit
.node
{
825 return Some(sym
.as_str());
833 if segments
.len() == exprs
.len() {
834 return Some(segments
);
843 // This is not a complete resolver for paths. It works on all the paths currently used in the paths
844 // module. That's all it does and all it needs to do.
845 pub fn check_path(cx
: &LateContext
<'_
>, path
: &[&str]) -> bool
{
846 if path_to_res(cx
, path
) != Res
::Err
{
850 // Some implementations can't be found by `path_to_res`, particularly inherent
851 // implementations of native types. Check lang items.
852 let path_syms
: Vec
<_
> = path
.iter().map(|p
| Symbol
::intern(p
)).collect();
853 let lang_items
= cx
.tcx
.lang_items();
854 for item_def_id
in lang_items
.items().iter().flatten() {
855 let lang_item_path
= cx
.get_def_path(*item_def_id
);
856 if path_syms
.starts_with(&lang_item_path
) {
857 if let [item
] = &path_syms
[lang_item_path
.len()..] {
858 for child
in cx
.tcx
.item_children(*item_def_id
) {
859 if child
.ident
.name
== *item
{
870 declare_lint_pass
!(InvalidPaths
=> [INVALID_PATHS
]);
872 impl<'tcx
> LateLintPass
<'tcx
> for InvalidPaths
{
873 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
874 let local_def_id
= &cx
.tcx
.parent_module(item
.hir_id());
875 let mod_name
= &cx
.tcx
.item_name(local_def_id
.to_def_id());
877 if mod_name
.as_str() == "paths";
878 if let hir
::ItemKind
::Const(ty
, body_id
) = item
.kind
;
879 let ty
= hir_ty_to_ty(cx
.tcx
, ty
);
880 if let ty
::Array(el_ty
, _
) = &ty
.kind();
881 if let ty
::Ref(_
, el_ty
, _
) = &el_ty
.kind();
883 let body
= cx
.tcx
.hir().body(body_id
);
884 let typeck_results
= cx
.tcx
.typeck_body(body_id
);
885 if let Some(Constant
::Vec(path
)) = constant_simple(cx
, typeck_results
, &body
.value
);
886 let path
: Vec
<&str> = path
.iter().map(|x
| {
887 if let Constant
::Str(s
) = x
{
890 // We checked the type of the constant above
894 if !check_path(cx
, &path
[..]);
896 span_lint(cx
, CLIPPY_LINTS_INTERNAL
, item
.span
, "invalid path");
903 pub struct InterningDefinedSymbol
{
904 // Maps the symbol value to the constant DefId.
905 symbol_map
: FxHashMap
<u32, DefId
>,
908 impl_lint_pass
!(InterningDefinedSymbol
=> [INTERNING_DEFINED_SYMBOL
, UNNECESSARY_SYMBOL_STR
]);
910 impl<'tcx
> LateLintPass
<'tcx
> for InterningDefinedSymbol
{
911 fn check_crate(&mut self, cx
: &LateContext
<'_
>, _
: &Crate
<'_
>) {
912 if !self.symbol_map
.is_empty() {
916 for &module
in &[&paths
::KW_MODULE
, &paths
::SYM_MODULE
] {
917 if let Some(def_id
) = path_to_res(cx
, module
).opt_def_id() {
918 for item
in cx
.tcx
.item_children(def_id
).iter() {
920 if let Res
::Def(DefKind
::Const
, item_def_id
) = item
.res
;
921 let ty
= cx
.tcx
.type_of(item_def_id
);
922 if match_type(cx
, ty
, &paths
::SYMBOL
);
923 if let Ok(ConstValue
::Scalar(value
)) = cx
.tcx
.const_eval_poly(item_def_id
);
924 if let Ok(value
) = value
.to_u32();
926 self.symbol_map
.insert(value
, item_def_id
);
934 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
936 if let ExprKind
::Call(func
, [arg
]) = &expr
.kind
;
937 if let ty
::FnDef(def_id
, _
) = cx
.typeck_results().expr_ty(func
).kind();
938 if match_def_path(cx
, *def_id
, &paths
::SYMBOL_INTERN
);
939 if let Some(Constant
::Str(arg
)) = constant_simple(cx
, cx
.typeck_results(), arg
);
940 let value
= Symbol
::intern(&arg
).as_u32();
941 if let Some(&def_id
) = self.symbol_map
.get(&value
);
945 INTERNING_DEFINED_SYMBOL
,
946 is_expn_of(expr
.span
, "sym").unwrap_or(expr
.span
),
947 "interning a defined symbol",
949 cx
.tcx
.def_path_str(def_id
),
950 Applicability
::MachineApplicable
,
954 if let ExprKind
::Binary(op
, left
, right
) = expr
.kind
{
955 if matches
!(op
.node
, BinOpKind
::Eq
| BinOpKind
::Ne
) {
957 (left
, self.symbol_str_expr(left
, cx
)),
958 (right
, self.symbol_str_expr(right
, cx
)),
961 // both operands are a symbol string
962 [(_
, Some(left
)), (_
, Some(right
))] => {
965 UNNECESSARY_SYMBOL_STR
,
967 "unnecessary `Symbol` to string conversion",
971 left
.as_symbol_snippet(cx
),
973 right
.as_symbol_snippet(cx
),
975 Applicability
::MachineApplicable
,
978 // one of the operands is a symbol string
979 [(expr
, Some(symbol
)), _
] | [_
, (expr
, Some(symbol
))] => {
980 // creating an owned string for comparison
981 if matches
!(symbol
, SymbolStrExpr
::Expr { is_to_owned: true, .. }
) {
984 UNNECESSARY_SYMBOL_STR
,
986 "unnecessary string allocation",
988 format
!("{}.as_str()", symbol
.as_symbol_snippet(cx
)),
989 Applicability
::MachineApplicable
,
994 [(_
, None
), (_
, None
)] => {}
,
1001 impl InterningDefinedSymbol
{
1002 fn symbol_str_expr
<'tcx
>(&self, expr
: &'tcx Expr
<'tcx
>, cx
: &LateContext
<'tcx
>) -> Option
<SymbolStrExpr
<'tcx
>> {
1003 static IDENT_STR_PATHS
: &[&[&str]] = &[&paths
::IDENT_AS_STR
, &paths
::TO_STRING_METHOD
];
1004 static SYMBOL_STR_PATHS
: &[&[&str]] = &[
1005 &paths
::SYMBOL_AS_STR
,
1006 &paths
::SYMBOL_TO_IDENT_STRING
,
1007 &paths
::TO_STRING_METHOD
,
1009 // SymbolStr might be de-referenced: `&*symbol.as_str()`
1010 let call
= if_chain
! {
1011 if let ExprKind
::AddrOf(_
, _
, e
) = expr
.kind
;
1012 if let ExprKind
::Unary(UnOp
::Deref
, e
) = e
.kind
;
1013 then { e }
else { expr }
1017 if let ExprKind
::MethodCall(_
, _
, [item
], _
) = call
.kind
;
1018 if let Some(did
) = cx
.typeck_results().type_dependent_def_id(call
.hir_id
);
1019 let ty
= cx
.typeck_results().expr_ty(item
);
1020 // ...on either an Ident or a Symbol
1021 if let Some(is_ident
) = if match_type(cx
, ty
, &paths
::SYMBOL
) {
1023 } else if match_type(cx
, ty
, &paths
::IDENT
) {
1028 // ...which converts it to a string
1029 let paths
= if is_ident { IDENT_STR_PATHS }
else { SYMBOL_STR_PATHS }
;
1030 if let Some(path
) = paths
.iter().find(|path
| match_def_path(cx
, did
, path
));
1032 let is_to_owned
= path
.last().unwrap().ends_with("string");
1033 return Some(SymbolStrExpr
::Expr
{
1040 // is a string constant
1041 if let Some(Constant
::Str(s
)) = constant_simple(cx
, cx
.typeck_results(), expr
) {
1042 let value
= Symbol
::intern(&s
).as_u32();
1043 // ...which matches a symbol constant
1044 if let Some(&def_id
) = self.symbol_map
.get(&value
) {
1045 return Some(SymbolStrExpr
::Const(def_id
));
1052 enum SymbolStrExpr
<'tcx
> {
1053 /// a string constant with a corresponding symbol constant
1055 /// a "symbol to string" expression like `symbol.as_str()`
1057 /// part that evaluates to `Symbol` or `Ident`
1058 item
: &'tcx Expr
<'tcx
>,
1060 /// whether an owned `String` is created like `to_ident_string()`
1065 impl<'tcx
> SymbolStrExpr
<'tcx
> {
1066 /// Returns a snippet that evaluates to a `Symbol` and is const if possible
1067 fn as_symbol_snippet(&self, cx
: &LateContext
<'_
>) -> Cow
<'tcx
, str> {
1069 Self::Const(def_id
) => cx
.tcx
.def_path_str(def_id
).into(),
1070 Self::Expr { item, is_ident, .. }
=> {
1071 let mut snip
= snippet(cx
, item
.span
.source_callsite(), "..");
1074 snip
.to_mut().push_str(".name");
1082 declare_lint_pass
!(IfChainStyle
=> [IF_CHAIN_STYLE
]);
1084 impl<'tcx
> LateLintPass
<'tcx
> for IfChainStyle
{
1085 fn check_block(&mut self, cx
: &LateContext
<'tcx
>, block
: &'tcx hir
::Block
<'_
>) {
1086 let (local
, after
, if_chain_span
) = if_chain
! {
1087 if let [Stmt { kind: StmtKind::Local(local), .. }
, after @
..] = block
.stmts
;
1088 if let Some(if_chain_span
) = is_expn_of(block
.span
, "if_chain");
1089 then { (local, after, if_chain_span) }
else { return }
1091 if is_first_if_chain_expr(cx
, block
.hir_id
, if_chain_span
) {
1095 if_chain_local_span(cx
, local
, if_chain_span
),
1096 "`let` expression should be above the `if_chain!`",
1098 } else if local
.span
.ctxt() == block
.span
.ctxt() && is_if_chain_then(after
, block
.expr
, if_chain_span
) {
1102 if_chain_local_span(cx
, local
, if_chain_span
),
1103 "`let` expression should be inside `then { .. }`",
1108 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
1109 let (cond
, then
, els
) = match expr
.kind
{
1110 ExprKind
::If(cond
, then
, els
) => (Some(cond
), then
, els
.is_some()),
1114 MatchSource
::IfLetDesugar
{
1115 contains_else_clause
: els
,
1117 ) => (None
, arm
.body
, els
),
1120 let then_block
= match then
.kind
{
1121 ExprKind
::Block(block
, _
) => block
,
1124 let if_chain_span
= is_expn_of(expr
.span
, "if_chain");
1126 check_nested_if_chains(cx
, expr
, then_block
, if_chain_span
);
1128 let if_chain_span
= match if_chain_span
{
1132 // check for `if a && b;`
1134 if let Some(cond
) = cond
;
1135 if let ExprKind
::Binary(op
, _
, _
) = cond
.kind
;
1136 if op
.node
== BinOpKind
::And
;
1137 if cx
.sess().source_map().is_multiline(cond
.span
);
1139 span_lint(cx
, IF_CHAIN_STYLE
, cond
.span
, "`if a && b;` should be `if a; if b;`");
1142 if is_first_if_chain_expr(cx
, expr
.hir_id
, if_chain_span
)
1143 && is_if_chain_then(then_block
.stmts
, then_block
.expr
, if_chain_span
)
1145 span_lint(cx
, IF_CHAIN_STYLE
, expr
.span
, "`if_chain!` only has one `if`");
1150 fn check_nested_if_chains(
1151 cx
: &LateContext
<'_
>,
1153 then_block
: &Block
<'_
>,
1154 if_chain_span
: Option
<Span
>,
1157 let (head
, tail
) = match *then_block
{
1158 Block { stmts, expr: Some(tail), .. }
=> (stmts
, tail
),
1162 Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
1169 if matches
!(tail
.kind
,
1170 ExprKind
::If(_
, _
, None
)
1171 | ExprKind
::Match(.., MatchSource
::IfLetDesugar { contains_else_clause: false }
));
1172 let sm
= cx
.sess().source_map();
1175 .all(|stmt
| matches
!(stmt
.kind
, StmtKind
::Local(..)) && !sm
.is_multiline(stmt
.span
));
1176 if if_chain_span
.is_some() || !is_else_clause(cx
.tcx
, if_expr
);
1177 then {}
else { return }
1179 let (span
, msg
) = match (if_chain_span
, is_expn_of(tail
.span
, "if_chain")) {
1180 (None
, Some(_
)) => (if_expr
.span
, "this `if` can be part of the inner `if_chain!`"),
1181 (Some(_
), None
) => (tail
.span
, "this `if` can be part of the outer `if_chain!`"),
1182 (Some(a
), Some(b
)) if a
!= b
=> (b
, "this `if_chain!` can be merged with the outer `if_chain!`"),
1185 span_lint_and_then(cx
, IF_CHAIN_STYLE
, span
, msg
, |diag
| {
1186 let (span
, msg
) = match head
{
1188 [stmt
] => (stmt
.span
, "this `let` statement can also be in the `if_chain!`"),
1191 "these `let` statements can also be in the `if_chain!`",
1194 diag
.span_help(span
, msg
);
1198 fn is_first_if_chain_expr(cx
: &LateContext
<'_
>, hir_id
: HirId
, if_chain_span
: Span
) -> bool
{
1201 .parent_iter(hir_id
)
1204 !matches
!(node
, Node
::Expr(Expr { kind: ExprKind::Block(..), .. }
) | Node
::Stmt(_
))
1206 .map_or(false, |(id
, _
)| {
1207 is_expn_of(cx
.tcx
.hir().span(id
), "if_chain") != Some(if_chain_span
)
1211 /// Checks a trailing slice of statements and expression of a `Block` to see if they are part
1212 /// of the `then {..}` portion of an `if_chain!`
1213 fn is_if_chain_then(stmts
: &[Stmt
<'_
>], expr
: Option
<&Expr
<'_
>>, if_chain_span
: Span
) -> bool
{
1214 let span
= if let [stmt
, ..] = stmts
{
1216 } else if let Some(expr
) = expr
{
1222 is_expn_of(span
, "if_chain").map_or(true, |span
| span
!= if_chain_span
)
1225 /// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
1226 fn if_chain_local_span(cx
: &LateContext
<'_
>, local
: &Local
<'_
>, if_chain_span
: Span
) -> Span
{
1227 let mut span
= local
.pat
.span
;
1228 if let Some(init
) = local
.init
{
1229 span
= span
.to(init
.span
);
1231 span
.adjust(if_chain_span
.ctxt().outer_expn());
1232 let sm
= cx
.sess().source_map();
1233 let span
= sm
.span_extend_to_prev_str(span
, "let", false);
1234 let span
= sm
.span_extend_to_next_char(span
, '
;'
, false);
1235 Span
::new(span
.lo() - BytePos(3), span
.hi() + BytePos(1), span
.ctxt())