clippy_utils/
disallowed_profiles.rs1use crate::sym;
2use rustc_ast::ast::{LitKind, MetaItemInner};
3use rustc_data_structures::fx::FxHashMap;
4use rustc_data_structures::smallvec::SmallVec;
5use rustc_hir::{Attribute, HirId};
6use rustc_lint::LateContext;
7use rustc_span::{Span, Symbol};
8
9#[derive(Copy, Clone)]
16pub struct ProfileEntry {
17 pub attr_name: Symbol,
18 pub name: Symbol,
19 pub span: Span,
20}
21
22#[derive(Clone)]
28pub struct ProfileSelection {
29 entries: SmallVec<[ProfileEntry; 2]>,
30}
31
32impl ProfileSelection {
33 pub fn new(entries: SmallVec<[ProfileEntry; 2]>) -> Self {
34 Self { entries }
35 }
36
37 pub fn is_empty(&self) -> bool {
38 self.entries.is_empty()
39 }
40
41 pub fn iter(&self) -> impl Iterator<Item = &ProfileEntry> {
42 self.entries.iter()
43 }
44}
45
46#[derive(Default)]
47pub struct ProfileResolver {
48 cache: FxHashMap<HirId, Option<ProfileSelection>>,
49}
50
51impl ProfileResolver {
52 pub fn active_profiles(&mut self, cx: &LateContext<'_>, hir_id: HirId) -> Option<&ProfileSelection> {
53 if self.cache.contains_key(&hir_id) {
56 return self.cache.get(&hir_id).and_then(|selection| selection.as_ref());
57 }
58
59 let (resolved, visited) = self.resolve(cx, hir_id);
60
61 for id in visited {
62 self.cache.entry(id).or_insert_with(|| resolved.clone());
63 }
64 self.cache.insert(hir_id, resolved);
65
66 self.cache.get(&hir_id).and_then(|selection| selection.as_ref())
67 }
68
69 fn resolve(&self, cx: &LateContext<'_>, start: HirId) -> (Option<ProfileSelection>, SmallVec<[HirId; 8]>) {
70 let mut visited = SmallVec::<[HirId; 8]>::new();
71 let mut current = Some(start);
72
73 while let Some(id) = current {
74 if let Some(cached) = self.cache.get(&id) {
75 return (cached.clone(), visited);
76 }
77
78 visited.push(id);
79
80 if let Some(selection) = profiles_from_attrs(cx, cx.tcx.hir_attrs(id)) {
81 return (Some(selection), visited);
82 }
83
84 if id == rustc_hir::CRATE_HIR_ID {
85 current = None;
86 } else {
87 current = Some(cx.tcx.parent_hir_id(id));
88 }
89 }
90
91 (None, visited)
92 }
93}
94
95fn profiles_from_attrs(cx: &LateContext<'_>, attrs: &[Attribute]) -> Option<ProfileSelection> {
96 let mut entries = SmallVec::<[ProfileEntry; 2]>::new();
97
98 for attr in attrs {
99 let path = attr.path();
100 if path.len() != 2 || path[0] != sym::clippy {
101 continue;
102 }
103
104 let name = path[1];
105 if name != sym::disallowed_profile && name != sym::disallowed_profiles {
106 continue;
107 }
108
109 let attr_label = if name == sym::disallowed_profiles {
110 "`clippy::disallowed_profiles`"
111 } else {
112 "`clippy::disallowed_profile`"
113 };
114
115 let Some(items) = attr.meta_item_list() else {
116 cx.tcx
117 .sess
118 .dcx()
119 .struct_span_err(attr.span(), format!("{attr_label} expects string arguments"))
120 .emit();
121 continue;
122 };
123
124 if items.is_empty() {
125 cx.tcx
126 .sess
127 .dcx()
128 .struct_span_err(attr.span(), format!("{attr_label} expects at least one profile name"))
129 .emit();
130 continue;
131 }
132
133 if name == sym::disallowed_profile && items.len() != 1 {
134 cx.tcx
135 .sess
136 .dcx()
137 .struct_span_err(attr.span(), "use `clippy::disallowed_profiles` for multiple profiles")
138 .emit();
139 }
140
141 for item in items {
142 match literal_symbol(&item) {
143 Some((symbol, span)) => entries.push(ProfileEntry {
144 attr_name: name,
145 name: symbol,
146 span,
147 }),
148 None => emit_string_error(cx, &item),
149 }
150 }
151 }
152
153 if entries.is_empty() {
154 None
155 } else {
156 Some(ProfileSelection::new(entries))
157 }
158}
159
160fn literal_symbol(item: &MetaItemInner) -> Option<(Symbol, Span)> {
161 match item {
162 MetaItemInner::Lit(lit) => {
163 let LitKind::Str(symbol, _) = lit.kind else { return None };
164 Some((symbol, lit.span))
165 },
166 MetaItemInner::MetaItem(_) => None,
167 }
168}
169
170fn emit_string_error(cx: &LateContext<'_>, item: &MetaItemInner) {
171 let span = match item {
172 MetaItemInner::Lit(lit) => lit.span,
173 MetaItemInner::MetaItem(meta) => meta.span,
174 };
175 cx.tcx
176 .sess
177 .dcx()
178 .struct_span_err(span, "expected string literal profile name")
179 .emit();
180}