]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/open_options.rs
Auto merge of #101249 - matthiaskrgr:rollup-wahnoz8, r=matthiaskrgr
[rust.git] / clippy_lints / src / methods / open_options.rs
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::paths;
3 use clippy_utils::ty::match_type;
4 use rustc_ast::ast::LitKind;
5 use rustc_hir::{Expr, ExprKind};
6 use rustc_lint::LateContext;
7 use rustc_span::source_map::{Span, Spanned};
8
9 use super::NONSENSICAL_OPEN_OPTIONS;
10
11 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
12     if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
13         && let Some(impl_id) = cx.tcx.impl_of_method(method_id)
14         && match_type(cx, cx.tcx.type_of(impl_id), &paths::OPEN_OPTIONS)
15     {
16         let mut options = Vec::new();
17         get_open_options(cx, recv, &mut options);
18         check_open_options(cx, &options, e.span);
19     }
20 }
21
22 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
23 enum Argument {
24     True,
25     False,
26     Unknown,
27 }
28
29 #[derive(Debug)]
30 enum OpenOption {
31     Write,
32     Read,
33     Truncate,
34     Create,
35     Append,
36 }
37
38 fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) {
39     if let ExprKind::MethodCall(path, arguments, _) = argument.kind {
40         let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs();
41
42         // Only proceed if this is a call on some object of type std::fs::OpenOptions
43         if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 {
44             let argument_option = match arguments[1].kind {
45                 ExprKind::Lit(ref span) => {
46                     if let Spanned {
47                         node: LitKind::Bool(lit),
48                         ..
49                     } = *span
50                     {
51                         if lit { Argument::True } else { Argument::False }
52                     } else {
53                         // The function is called with a literal which is not a boolean literal.
54                         // This is theoretically possible, but not very likely.
55                         return;
56                     }
57                 },
58                 _ => Argument::Unknown,
59             };
60
61             match path.ident.as_str() {
62                 "create" => {
63                     options.push((OpenOption::Create, argument_option));
64                 },
65                 "append" => {
66                     options.push((OpenOption::Append, argument_option));
67                 },
68                 "truncate" => {
69                     options.push((OpenOption::Truncate, argument_option));
70                 },
71                 "read" => {
72                     options.push((OpenOption::Read, argument_option));
73                 },
74                 "write" => {
75                     options.push((OpenOption::Write, argument_option));
76                 },
77                 _ => (),
78             }
79
80             get_open_options(cx, &arguments[0], options);
81         }
82     }
83 }
84
85 fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) {
86     let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
87     let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
88         (false, false, false, false, false);
89     // This code is almost duplicated (oh, the irony), but I haven't found a way to
90     // unify it.
91
92     for option in options {
93         match *option {
94             (OpenOption::Create, arg) => {
95                 if create {
96                     span_lint(
97                         cx,
98                         NONSENSICAL_OPEN_OPTIONS,
99                         span,
100                         "the method `create` is called more than once",
101                     );
102                 } else {
103                     create = true;
104                 }
105                 create_arg = create_arg || (arg == Argument::True);
106             },
107             (OpenOption::Append, arg) => {
108                 if append {
109                     span_lint(
110                         cx,
111                         NONSENSICAL_OPEN_OPTIONS,
112                         span,
113                         "the method `append` is called more than once",
114                     );
115                 } else {
116                     append = true;
117                 }
118                 append_arg = append_arg || (arg == Argument::True);
119             },
120             (OpenOption::Truncate, arg) => {
121                 if truncate {
122                     span_lint(
123                         cx,
124                         NONSENSICAL_OPEN_OPTIONS,
125                         span,
126                         "the method `truncate` is called more than once",
127                     );
128                 } else {
129                     truncate = true;
130                 }
131                 truncate_arg = truncate_arg || (arg == Argument::True);
132             },
133             (OpenOption::Read, arg) => {
134                 if read {
135                     span_lint(
136                         cx,
137                         NONSENSICAL_OPEN_OPTIONS,
138                         span,
139                         "the method `read` is called more than once",
140                     );
141                 } else {
142                     read = true;
143                 }
144                 read_arg = read_arg || (arg == Argument::True);
145             },
146             (OpenOption::Write, arg) => {
147                 if write {
148                     span_lint(
149                         cx,
150                         NONSENSICAL_OPEN_OPTIONS,
151                         span,
152                         "the method `write` is called more than once",
153                     );
154                 } else {
155                     write = true;
156                 }
157                 write_arg = write_arg || (arg == Argument::True);
158             },
159         }
160     }
161
162     if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
163         span_lint(
164             cx,
165             NONSENSICAL_OPEN_OPTIONS,
166             span,
167             "file opened with `truncate` and `read`",
168         );
169     }
170     if append && truncate && append_arg && truncate_arg {
171         span_lint(
172             cx,
173             NONSENSICAL_OPEN_OPTIONS,
174             span,
175             "file opened with `append` and `truncate`",
176         );
177     }
178 }