lisette_semantics/passes/lints/
from_facts.rs1use rustc_hash::FxHashSet as HashSet;
2
3use crate::facts::Facts;
4use diagnostics::LisetteDiagnostic;
5use diagnostics::LocalSink;
6use syntax::program::UnusedInfo;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum Lint {
10 UnusedVariable,
11 UnusedParameter,
12 UnusedMut,
13 UnusedImport,
14 UnusedType,
15 UnusedFunction,
16 UnusedConstant,
17 UnusedStructField,
18 UnusedEnumVariant,
19 UnusedLiteral,
20 UnusedResult,
21 UnusedOption,
22 UnusedValue,
23 DeadCodeAfterReturn,
24 DeadCodeAfterBreak,
25 DeadCodeAfterContinue,
26 DeadCodeAfterDivergingIf,
27 DeadCodeAfterDivergingMatch,
28 DeadCodeAfterInfiniteLoop,
29 DeadCodeAfterDivergingCall,
30 DoubleBoolNegation,
31 DoubleIntNegation,
32 SelfComparison,
33 SelfAssignment,
34 MatchLiteralCollection,
35 EmptyMatchArm,
36 InternalTypeLeak,
37 UnnecessaryReference,
38 UnusedTypeParameter,
39 TypeParamOnlyInBound,
40 RestOnlySlicePattern,
41 NonPascalCaseType,
42 NonPascalCaseTypeParameter,
43 NonPascalCaseEnumVariant,
44 NonSnakeCaseFunction,
45 NonSnakeCaseVariable,
46 NonSnakeCaseParameter,
47 NonSnakeCaseStructField,
48 NonScreamingSnakeCaseConstant,
49 RedundantIfLet,
50 RedundantLetElse,
51 SingleArmMatch,
52 RedundantIfLetElse,
53 UnreachableIfLetElse,
54 TryBlockNoSuccessPath,
55 ExcessParensOnCondition,
56 ReplaceableWithZeroFill,
57}
58
59#[derive(Debug, Clone, Default)]
60pub struct LintConfig {
61 disabled: HashSet<Lint>,
62}
63
64impl LintConfig {
65 pub fn is_enabled(&self, lint: Lint) -> bool {
66 !self.disabled.contains(&lint)
67 }
68}
69
70pub(crate) fn run(facts: &Facts, unused: &mut UnusedInfo, sink: &LocalSink) {
71 let mut diagnostics: Vec<LisetteDiagnostic> = Vec::new();
72
73 collect_bindings(facts, unused, &mut diagnostics);
74 collect_dead_code(facts, &mut diagnostics);
75 collect_pattern_issues(facts, &mut diagnostics);
76 collect_unused_expressions(facts, &mut diagnostics);
77 collect_discarded_tail_expressions(facts, &mut diagnostics);
78 collect_overused_references(facts, &mut diagnostics);
79 collect_unused_type_params(facts, &mut diagnostics);
80 collect_type_params_only_in_bound(facts, &mut diagnostics);
81 collect_always_failing_try_blocks(facts, &mut diagnostics);
82 collect_expression_only_fstrings(facts, &mut diagnostics);
83
84 diagnostics.sort_by(LisetteDiagnostic::sort_key);
85 sink.extend(diagnostics);
86}
87
88fn collect_bindings(facts: &Facts, unused: &mut UnusedInfo, out: &mut Vec<LisetteDiagnostic>) {
89 for b in facts.bindings.values() {
90 let is_anon = b.name.starts_with('_');
91 let written_but_not_read = b.kind.is_mutable() && b.mutated && !b.used && !is_anon;
92 let is_write_only_param = written_but_not_read && b.kind.is_param();
93
94 if !b.used && !is_write_only_param {
95 if !is_anon && b.kind.is_param() && !b.is_typedef && b.name != "self" {
96 out.push(diagnostics::lint::unused_parameter(&b.span, &b.name));
97 } else if !written_but_not_read
98 && !is_anon
99 && !b.kind.is_param()
100 && (!b.kind.is_pattern_position() || b.is_as_alias)
101 {
102 out.push(diagnostics::lint::unused_variable(
103 &b.span,
104 &b.name,
105 b.is_struct_field,
106 ));
107 }
108 unused.mark_binding_unused(b.span);
109 }
110
111 if b.kind.is_mutable() && !b.mutated {
112 out.push(diagnostics::lint::unused_mut(&b.span));
113 }
114
115 if written_but_not_read {
116 out.push(diagnostics::lint::written_but_not_read(&b.span, &b.name));
117 }
118 }
119}
120
121fn collect_dead_code(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
122 for dc in &facts.dead_code {
123 out.push(diagnostics::lint::dead_code(&dc.span, dc.cause));
124 }
125}
126
127fn collect_pattern_issues(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
128 for issue in &facts.pattern_issues {
129 out.push(diagnostics::lint::pattern_issue(&issue.span, issue.kind));
130 }
131}
132
133fn collect_unused_expressions(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
134 for fact in &facts.unused_expressions {
135 out.push(diagnostics::lint::unused_expression(&fact.span, fact.kind));
136 }
137}
138
139fn collect_discarded_tail_expressions(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
140 for fact in &facts.discarded_tail_expressions {
141 out.push(diagnostics::infer::mismatched_tail_value(
142 &fact.span,
143 &fact.return_type,
144 &fact.expected_span,
145 &fact.expected_type,
146 ));
147 }
148}
149
150fn collect_overused_references(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
151 for fact in &facts.overused_references {
152 out.push(diagnostics::lint::unnecessary_reference(
153 &fact.span,
154 fact.name.as_deref(),
155 ));
156 }
157}
158
159fn collect_unused_type_params(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
160 for fact in &facts.unused_type_params {
161 out.push(diagnostics::lint::unused_type_parameter(&fact.span));
162 }
163}
164
165fn collect_type_params_only_in_bound(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
166 for fact in &facts.type_params_only_in_bound {
167 out.push(diagnostics::lint::type_param_only_in_bound(
168 &fact.span, &fact.name,
169 ));
170 }
171}
172
173fn collect_always_failing_try_blocks(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
174 for span in &facts.always_failing_try_blocks {
175 out.push(diagnostics::lint::ineffective_try_block(span));
176 }
177}
178
179fn collect_expression_only_fstrings(facts: &Facts, out: &mut Vec<LisetteDiagnostic>) {
180 for span in &facts.expression_only_fstrings {
181 out.push(diagnostics::lint::expression_only_fstring(span));
182 }
183}