|
1 | | -use fe_analyzer::context; |
2 | 1 | use fe_analyzer::namespace::items::{self, Item, TypeDef}; |
3 | | -use fe_analyzer::namespace::types::{Event, FixedSize, FunctionSignature, Type}; |
| 2 | +use fe_analyzer::namespace::types::{Event, FixedSize}; |
4 | 3 | use fe_analyzer::{AnalyzerDb, Db}; |
5 | 4 | use fe_common::diagnostics::{diagnostics_string, print_diagnostics, Diagnostic, Label, Severity}; |
6 | 5 | use fe_common::files::FileStore; |
7 | 6 | use fe_parser::node::NodeId; |
8 | 7 | use fe_parser::node::Span; |
9 | 8 | use indexmap::IndexMap; |
10 | 9 | use insta::assert_snapshot; |
| 10 | +use smallvec::SmallVec; |
11 | 11 | use std::collections::hash_map::DefaultHasher; |
12 | 12 | use std::collections::HashMap; |
13 | 13 | use std::fmt::Debug; |
@@ -46,10 +46,10 @@ macro_rules! test_analysis { |
46 | 46 | // for larger diffs. I recommend commenting out all tests but one. |
47 | 47 | fe_common::assert_snapshot_wasm!( |
48 | 48 | concat!("snapshots/analysis__", stringify!($name), ".snap"), |
49 | | - build_snapshot(files, module, &db) |
| 49 | + build_snapshot(&files, module, &db) |
50 | 50 | ); |
51 | 51 | } else { |
52 | | - assert_snapshot!(build_snapshot(files, module, &db)); |
| 52 | + assert_snapshot!(build_snapshot(&files, module, &db)); |
53 | 53 | } |
54 | 54 | } |
55 | 55 | }; |
@@ -162,128 +162,155 @@ test_analysis! { data_copying_stress, "stress/data_copying_stress.fe"} |
162 | 162 | test_analysis! { tuple_stress, "stress/tuple_stress.fe"} |
163 | 163 | test_analysis! { type_aliases, "features/type_aliases.fe"} |
164 | 164 |
|
165 | | -fn build_snapshot(file_store: FileStore, module: items::ModuleId, db: &dyn AnalyzerDb) -> String { |
166 | | - // contract and struct types aren't worth printing |
167 | | - let type_aliases = module |
| 165 | +fn build_snapshot(file_store: &FileStore, module: items::ModuleId, db: &dyn AnalyzerDb) -> String { |
| 166 | + let diagnostics = module |
168 | 167 | .all_items(db) |
169 | 168 | .iter() |
170 | | - .filter_map(|def| match def { |
171 | | - Item::Type(TypeDef::Alias(alias)) => { |
172 | | - Some((alias.data(db).ast.span, alias.typ(db).unwrap())) |
173 | | - } |
174 | | - _ => None, |
175 | | - }) |
176 | | - .collect::<Vec<(Span, Type)>>(); |
| 169 | + .map(|item| match item { |
| 170 | + Item::Type(TypeDef::Alias(alias)) => vec![build_display_diagnostic( |
| 171 | + alias.data(db).ast.span, |
| 172 | + &alias.typ(db).unwrap(), |
| 173 | + )], |
| 174 | + Item::Type(TypeDef::Struct(struct_)) => [label_in_non_overlapping_groups( |
| 175 | + &struct_ |
| 176 | + .all_fields(db) |
| 177 | + .iter() |
| 178 | + .map(|field| (field.data(db).ast.span, field.typ(db).unwrap())) |
| 179 | + .collect::<Vec<_>>(), |
| 180 | + )] |
| 181 | + .concat(), |
| 182 | + Item::Type(TypeDef::Contract(contract)) => [ |
| 183 | + label_in_non_overlapping_groups( |
| 184 | + &contract |
| 185 | + .all_fields(db) |
| 186 | + .iter() |
| 187 | + .map(|field| (field.data(db).ast.span, field.typ(db).unwrap())) |
| 188 | + .collect::<Vec<_>>(), |
| 189 | + ), |
| 190 | + contract |
| 191 | + .events(db) |
| 192 | + .values() |
| 193 | + .map(|id| event_diagnostics(*id, db)) |
| 194 | + .flatten() |
| 195 | + .collect(), |
| 196 | + contract |
| 197 | + .functions(db) |
| 198 | + .values() |
| 199 | + .map(|id| function_diagnostics(*id, db)) |
| 200 | + .flatten() |
| 201 | + .collect(), |
| 202 | + ] |
| 203 | + .concat(), |
177 | 204 |
|
178 | | - let struct_fields: Vec<(Span, Type)> = module |
179 | | - .all_structs(db) |
180 | | - .iter() |
181 | | - .map(|struc| { |
182 | | - struc |
183 | | - .all_fields(db) |
184 | | - .iter() |
185 | | - .map(|field| (field.data(db).ast.span, field.typ(db).unwrap().into())) |
186 | | - .collect::<Vec<_>>() |
187 | | - }) |
188 | | - .flatten() |
189 | | - .collect(); |
| 205 | + Item::Function(id) => function_diagnostics(*id, db), |
| 206 | + Item::Constant(id) => vec![build_display_diagnostic(id.span(db), &id.typ(db).unwrap())], |
190 | 207 |
|
191 | | - let contract_ids = module.all_contracts(db); |
192 | | - let contract_fields: Vec<(Span, Type)> = contract_ids |
193 | | - .iter() |
194 | | - .map(|contract| { |
195 | | - contract |
196 | | - .all_fields(db) |
197 | | - .iter() |
198 | | - .map(|field| (field.data(db).ast.span, field.typ(db).unwrap())) |
199 | | - .collect::<Vec<_>>() |
200 | | - }) |
201 | | - .flatten() |
202 | | - .collect(); |
203 | | - let event_fields = contract_ids |
204 | | - .iter() |
205 | | - .map(|contract| { |
206 | | - contract |
207 | | - .all_events(db) |
208 | | - .iter() |
209 | | - .map(|event| { |
210 | | - // Event field spans are a bit of a hassle right now |
211 | | - event |
212 | | - .data(db) |
213 | | - .ast |
214 | | - .kind |
215 | | - .fields |
216 | | - .iter() |
217 | | - .map(|node| node.span) |
218 | | - .zip( |
219 | | - event |
220 | | - .typ(db) |
221 | | - .fields |
222 | | - .iter() |
223 | | - .map(|field| field.typ.clone().unwrap()), |
224 | | - ) |
225 | | - .collect::<Vec<(Span, FixedSize)>>() |
226 | | - }) |
227 | | - .flatten() |
228 | | - .collect::<Vec<(Span, FixedSize)>>() |
| 208 | + // Events can't be defined at the module level yet. |
| 209 | + Item::Event(_) => vec![], |
| 210 | + |
| 211 | + // Built-in stuff |
| 212 | + Item::Type(TypeDef::Primitive(_)) |
| 213 | + | Item::GenericType(_) |
| 214 | + | Item::BuiltinFunction(_) |
| 215 | + | Item::Object(_) => vec![], |
229 | 216 | }) |
230 | 217 | .flatten() |
231 | | - .collect::<Vec<(Span, FixedSize)>>(); |
| 218 | + .collect::<Vec<_>>(); |
232 | 219 |
|
233 | | - let all_function_ids: Vec<items::FunctionId> = contract_ids |
234 | | - .iter() |
235 | | - .map(|contract| contract.all_functions(db).as_ref().clone()) |
236 | | - .flatten() |
237 | | - .collect(); |
| 220 | + diagnostics_string(&diagnostics, &file_store) |
| 221 | +} |
238 | 222 |
|
239 | | - let function_sigs = all_function_ids |
240 | | - .iter() |
241 | | - .map(|fun| (fun.data(db).ast.span, fun.signature(db))) |
242 | | - .collect::<Vec<(Span, Rc<FunctionSignature>)>>(); |
| 223 | +fn new_diagnostic(labels: Vec<Label>) -> Diagnostic { |
| 224 | + Diagnostic { |
| 225 | + severity: Severity::Note, |
| 226 | + message: String::new(), |
| 227 | + labels: labels.to_vec(), |
| 228 | + notes: vec![], |
| 229 | + } |
| 230 | +} |
243 | 231 |
|
244 | | - let all_function_bodies: Vec<Rc<context::FunctionBody>> = |
245 | | - all_function_ids.iter().map(|func| func.body(db)).collect(); |
| 232 | +fn label_in_non_overlapping_groups(spans: &[(Span, impl Display)]) -> Vec<Diagnostic> { |
| 233 | + // Accumulate labels in a vec until we reach a span that overlaps |
| 234 | + // the labeled range, then emit a Diagnostic with the accumulated labels |
| 235 | + // and begin again. This assumes that all spans are within the same file. |
| 236 | + let file_id = if let Some((span, _)) = spans.first() { |
| 237 | + span.file_id |
| 238 | + } else { |
| 239 | + return vec![]; |
| 240 | + }; |
246 | 241 |
|
247 | | - let expressions = all_function_bodies |
| 242 | + spans |
248 | 243 | .iter() |
249 | | - .map(|body| lookup_spans(&body.expressions, &body.spans)) |
| 244 | + .enumerate() |
| 245 | + .scan( |
| 246 | + (Span::zero(file_id), vec![]), |
| 247 | + |(labeled, labels), (idx, (span, attr))| { |
| 248 | + let mut diags = SmallVec::<[Diagnostic; 2]>::new(); |
| 249 | + |
| 250 | + let overlaps = span.start < labeled.end && span.end > labeled.start; |
| 251 | + |
| 252 | + // If the current span overlaps with the union of the current set of labels, |
| 253 | + // emit a diagnostic, and clear the set of labels. |
| 254 | + if overlaps { |
| 255 | + diags.push(new_diagnostic(labels.to_vec())); |
| 256 | + labels.clear(); |
| 257 | + *labeled = *span; |
| 258 | + } |
| 259 | + labels.push(Label::primary(*span, format!("{}", attr))); |
| 260 | + *labeled += *span; |
| 261 | + |
| 262 | + // If this is the last thing to label, emit a diagnostic. |
| 263 | + if idx == spans.len() - 1 { |
| 264 | + diags.push(new_diagnostic(labels.to_vec())); |
| 265 | + } |
| 266 | + Some(diags) |
| 267 | + }, |
| 268 | + ) |
250 | 269 | .flatten() |
251 | | - .collect::<Vec<_>>(); |
252 | | - let emits = all_function_bodies |
253 | | - .iter() |
254 | | - .map(|body| { |
255 | | - lookup_spans(&body.emits, &body.spans) |
| 270 | + .collect() |
| 271 | +} |
| 272 | + |
| 273 | +fn function_diagnostics(fun: items::FunctionId, db: &dyn AnalyzerDb) -> Vec<Diagnostic> { |
| 274 | + let body = fun.body(db); |
| 275 | + [ |
| 276 | + // signature |
| 277 | + build_debug_diagnostics(&[(fun.data(db).ast.span, &fun.signature(db))]), |
| 278 | + // declarations |
| 279 | + label_in_non_overlapping_groups(&lookup_spans(&body.var_decl_types, &body.spans)), |
| 280 | + // expressions |
| 281 | + label_in_non_overlapping_groups(&lookup_spans(&body.expressions, &body.spans)), |
| 282 | + // emits |
| 283 | + build_debug_diagnostics( |
| 284 | + &lookup_spans(&body.emits, &body.spans) |
256 | 285 | .into_iter() |
257 | 286 | .map(|(span, eventid)| (span, eventid.typ(db))) |
258 | | - .collect::<Vec<(Span, Rc<Event>)>>() |
259 | | - }) |
260 | | - .flatten() |
261 | | - .collect::<Vec<_>>(); |
262 | | - let declarations = all_function_bodies |
263 | | - .iter() |
264 | | - .map(|body| lookup_spans(&body.var_decl_types, &body.spans)) |
265 | | - .flatten() |
266 | | - .collect::<Vec<_>>(); |
267 | | - let calls = all_function_bodies |
268 | | - .iter() |
269 | | - .map(|body| lookup_spans(&body.calls, &body.spans)) |
270 | | - .flatten() |
271 | | - .collect::<Vec<_>>(); |
272 | | - |
273 | | - let diagnostics = [ |
274 | | - build_display_diagnostics(&type_aliases), |
275 | | - build_display_diagnostics(&struct_fields), |
276 | | - build_display_diagnostics(&contract_fields), |
277 | | - build_display_diagnostics(&event_fields), |
278 | | - build_debug_diagnostics(&function_sigs), |
279 | | - build_display_diagnostics(&expressions), |
280 | | - build_debug_diagnostics(&emits), |
281 | | - build_display_diagnostics(&declarations), |
282 | | - build_debug_diagnostics(&calls), |
| 287 | + .collect::<Vec<(Span, Rc<Event>)>>(), |
| 288 | + ), |
| 289 | + // calls |
| 290 | + build_debug_diagnostics(&lookup_spans(&body.calls, &body.spans)), |
283 | 291 | ] |
284 | | - .concat(); |
| 292 | + .concat() |
| 293 | +} |
285 | 294 |
|
286 | | - diagnostics_string(&diagnostics, &file_store) |
| 295 | +fn event_diagnostics(event: items::EventId, db: &dyn AnalyzerDb) -> Vec<Diagnostic> { |
| 296 | + // Event field spans are a bit of a hassle right now |
| 297 | + label_in_non_overlapping_groups( |
| 298 | + &event |
| 299 | + .data(db) |
| 300 | + .ast |
| 301 | + .kind |
| 302 | + .fields |
| 303 | + .iter() |
| 304 | + .map(|node| node.span) |
| 305 | + .zip( |
| 306 | + event |
| 307 | + .typ(db) |
| 308 | + .fields |
| 309 | + .iter() |
| 310 | + .map(|field| field.typ.clone().unwrap()), |
| 311 | + ) |
| 312 | + .collect::<Vec<(Span, FixedSize)>>(), |
| 313 | + ) |
287 | 314 | } |
288 | 315 |
|
289 | 316 | fn lookup_spans<T: Clone>( |
@@ -314,15 +341,7 @@ fn build_debug_diagnostic<T: Debug>(span: Span, attributes: &T) -> Diagnostic { |
314 | 341 | } |
315 | 342 | } |
316 | 343 |
|
317 | | -fn build_display_diagnostics<T: Display>(spanned_attributes: &[(Span, T)]) -> Vec<Diagnostic> { |
318 | | - spanned_attributes |
319 | | - .iter() |
320 | | - .map(|(span, attributes)| build_display_diagnostic(*span, attributes)) |
321 | | - .collect::<Vec<_>>() |
322 | | -} |
323 | | - |
324 | 344 | fn build_display_diagnostic<T: Display>(span: Span, attributes: &T) -> Diagnostic { |
325 | | - // Hash the attributes and label the span with it. |
326 | 345 | let label = Label::primary(span, format!("{}", attributes)); |
327 | 346 | Diagnostic { |
328 | 347 | severity: Severity::Note, |
|
0 commit comments