1use super::{CoverageItem, CoverageItemKind, SourceLocation};
2use alloy_primitives::map::{HashMap, HashSet};
3use foundry_common::TestFunctionExt;
4use foundry_compilers::ProjectCompileOutput;
5use rayon::prelude::*;
6use solar::{
7 ast::{self, ExprKind, ItemKind, StmtKind, yul},
8 data_structures::{Never, map::FxHashMap},
9 interface::{BytePos, Span},
10 sema::{Gcx, hir},
11};
12use std::{
13 ops::{ControlFlow, Range},
14 path::PathBuf,
15 sync::Arc,
16};
17
18#[derive(Clone)]
20struct SourceVisitor<'gcx> {
21 source_id: u32,
23 gcx: Gcx<'gcx>,
25
26 contract_name: Arc<str>,
28
29 branch_id: u32,
31
32 items: Vec<CoverageItem>,
34
35 all_lines: Vec<u32>,
36 ternary_branches: Vec<u32>,
38 function_calls: Vec<(Span, Arc<str>)>,
41 function_call_scopes: FxHashMap<Span, Arc<str>>,
42}
43
44struct SourceVisitorCheckpoint {
45 items: usize,
46 all_lines: usize,
47 function_calls: usize,
48 ternary_branches: usize,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub(crate) enum EmptySpecialFunctionKind {
53 Constructor,
54 Receive,
55 Fallback,
56}
57
58struct ResolvedEmptySpecialFunction {
59 contract_source_id: u32,
60 contract_name: Arc<str>,
61 function_source_id: u32,
62 function_contract_name: Arc<str>,
63 function_bytes: Range<u32>,
64 kind: EmptySpecialFunctionKind,
65}
66
67fn resolve_empty_special_functions(
68 gcx: Gcx<'_>,
69 data: &SourceFiles,
70) -> Vec<ResolvedEmptySpecialFunction> {
71 let hir_source_ids = data
72 .sources
73 .iter()
74 .map(|(&source_id, path)| {
75 let (hir_source_id, _) = gcx.get_hir_source(path).unwrap();
76 (hir_source_id, source_id)
77 })
78 .collect::<HashMap<_, _>>();
79 let mut resolved = Vec::new();
80 for contract_id in gcx.hir.contract_ids() {
81 let contract = gcx.hir.contract(contract_id);
82 let Some(&contract_source_id) = hir_source_ids.get(&contract.source) else { continue };
83 let constructors = contract.linearized_bases.iter().filter_map(|&base_id| {
84 gcx.hir
85 .contract(base_id)
86 .ctor
87 .map(|function_id| (function_id, EmptySpecialFunctionKind::Constructor))
88 });
89 let runtime_functions = [
90 (contract.receive, EmptySpecialFunctionKind::Receive),
91 (contract.fallback, EmptySpecialFunctionKind::Fallback),
92 ]
93 .into_iter()
94 .filter_map(|(function_id, kind)| function_id.map(|function_id| (function_id, kind)));
95 for (function_id, kind) in constructors.chain(runtime_functions) {
96 let function = gcx.hir.function(function_id);
97 if !function.body.is_some_and(|body| body.is_empty()) {
98 continue;
99 }
100 let Some(&function_source_id) = hir_source_ids.get(&function.source) else { continue };
101 let Some(function_contract_id) = function.contract else { continue };
102 let function_contract = gcx.hir.contract(function_contract_id);
103 let function_bytes = gcx.sess.source_map().span_to_source(function.span).unwrap().data;
104 resolved.push(ResolvedEmptySpecialFunction {
105 contract_source_id,
106 contract_name: contract.name.as_str().into(),
107 function_source_id,
108 function_contract_name: function_contract.name.as_str().into(),
109 function_bytes: function_bytes.start as u32..function_bytes.end as u32,
110 kind,
111 });
112 }
113 }
114 resolved
115}
116
117impl<'gcx> SourceVisitor<'gcx> {
118 fn new(source_id: u32, gcx: Gcx<'gcx>) -> Self {
119 Self {
120 source_id,
121 gcx,
122 contract_name: Arc::default(),
123 branch_id: 0,
124 ternary_branches: Default::default(),
125 all_lines: Default::default(),
126 function_calls: Default::default(),
127 function_call_scopes: Default::default(),
128 items: Default::default(),
129 }
130 }
131
132 const fn checkpoint(&self) -> SourceVisitorCheckpoint {
133 SourceVisitorCheckpoint {
134 items: self.items.len(),
135 all_lines: self.all_lines.len(),
136 function_calls: self.function_calls.len(),
137 ternary_branches: self.ternary_branches.len(),
138 }
139 }
140
141 fn restore_checkpoint(&mut self, checkpoint: SourceVisitorCheckpoint) {
142 let SourceVisitorCheckpoint { items, all_lines, function_calls, ternary_branches } =
143 checkpoint;
144 self.items.truncate(items);
145 self.all_lines.truncate(all_lines);
146 self.function_calls.truncate(function_calls);
147 self.ternary_branches.truncate(ternary_branches);
148 }
149
150 fn visit_contract<'ast>(&mut self, contract: &'ast ast::ItemContract<'ast>) {
151 let _ = ast::Visit::visit_item_contract(self, contract);
152 }
153
154 fn has_tests(&self, checkpoint: &SourceVisitorCheckpoint) -> bool {
156 self.items[checkpoint.items..].iter().any(|item| {
157 if let CoverageItemKind::Function { name } = &item.kind {
158 name.is_any_test()
159 } else {
160 false
161 }
162 })
163 }
164
165 fn disambiguate_functions(&mut self) {
169 let mut dups = HashMap::<_, Vec<usize>>::default();
170 for (i, item) in self.items.iter().enumerate() {
171 if let CoverageItemKind::Function { name } = &item.kind {
172 dups.entry((item.loc.contract_name.clone(), name.clone())).or_default().push(i);
173 }
174 }
175 for dups in dups.values() {
176 if dups.len() > 1 {
177 for (i, &dup) in dups.iter().enumerate() {
178 let item = &mut self.items[dup];
179 if let CoverageItemKind::Function { name } = &item.kind {
180 item.kind =
181 CoverageItemKind::Function { name: format!("{name}.{i}").into() };
182 }
183 }
184 }
185 }
186 }
187
188 fn resolve_function_calls(&mut self, hir_source_id: hir::SourceId) {
189 self.function_call_scopes = self.function_calls.iter().cloned().collect();
190 let _ = hir::Visit::visit_nested_source(self, hir_source_id);
191 }
192
193 fn sort(&mut self) {
194 self.items.sort();
195 }
196
197 fn push_lines(&mut self) {
198 self.all_lines.sort_unstable();
199 self.all_lines.dedup();
200 let mut lines = Vec::with_capacity(self.all_lines.len());
201 let mut items = self.items.iter().peekable();
203 for &line in &self.all_lines {
204 while items.peek().is_some_and(|item| item.loc.lines.start < line) {
205 items.next();
206 }
207 if let Some(mut reference_item) = items.next_if(|item| item.loc.lines.start == line) {
208 while let Some(item) = items.next_if(|item| item.loc.lines.start == line) {
211 if item.loc.bytes.start < reference_item.loc.bytes.start {
212 reference_item = item;
213 }
214 }
215 lines.push(CoverageItem {
216 kind: CoverageItemKind::Line,
217 loc: reference_item.loc.clone(),
218 anchor_loc: None,
219 hits: 0,
220 });
221 }
222 }
223 self.items.extend(lines);
224 }
225
226 fn push_stmt(&mut self, span: Span) {
227 self.push_item_kind(CoverageItemKind::Statement, span);
228 }
229
230 fn push_item_kind(&mut self, kind: CoverageItemKind, span: Span) -> &mut CoverageItem {
233 let item =
234 CoverageItem { kind, loc: self.source_location_for(span), anchor_loc: None, hits: 0 };
235
236 debug_assert!(!matches!(item.kind, CoverageItemKind::Line));
237 self.all_lines.push(item.loc.lines.start);
238
239 self.items.push(item);
240 self.items.last_mut().unwrap()
241 }
242
243 fn source_location_for(&self, mut span: Span) -> SourceLocation {
244 if let Ok(snippet) = self.gcx.sess.source_map().span_to_snippet(span)
246 && let Some(stripped) = snippet.strip_suffix(';')
247 {
248 let stripped = stripped.trim_end();
249 let skipped = snippet.len() - stripped.len();
250 span = span.with_hi(span.hi() - BytePos::from_usize(skipped));
251 }
252
253 SourceLocation {
254 source_id: self.source_id as usize,
255 contract_name: self.contract_name.clone(),
256 bytes: self.byte_range(span),
257 lines: self.line_range(span),
258 }
259 }
260
261 fn byte_range(&self, span: Span) -> Range<u32> {
262 let bytes_usize = self.gcx.sess.source_map().span_to_source(span).unwrap().data;
263 bytes_usize.start as u32..bytes_usize.end as u32
264 }
265
266 fn line_range(&self, span: Span) -> Range<u32> {
267 let lines = self.gcx.sess.source_map().span_to_lines(span).unwrap().data;
268 assert!(!lines.is_empty());
269 let first = lines.first().unwrap();
270 let last = lines.last().unwrap();
271 first.line_index as u32 + 1..last.line_index as u32 + 2
272 }
273
274 const fn next_branch_id(&mut self) -> u32 {
275 let id = self.branch_id;
276 self.branch_id = id + 1;
277 id
278 }
279}
280
281impl<'ast> ast::Visit<'ast> for SourceVisitor<'_> {
282 type BreakValue = Never;
283
284 fn visit_item_contract(
285 &mut self,
286 contract: &'ast ast::ItemContract<'ast>,
287 ) -> ControlFlow<Self::BreakValue> {
288 self.contract_name = contract.name.as_str().into();
289 self.walk_item_contract(contract)
290 }
291
292 #[expect(clippy::single_match)]
293 fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow<Self::BreakValue> {
294 match &item.kind {
295 ItemKind::Function(func) => {
296 if func.kind == ast::FunctionKind::Modifier && !has_statements(func.body.as_ref()) {
299 return ControlFlow::Continue(());
300 }
301
302 let name = func.header.name.as_ref().map(|n| n.as_str()).unwrap_or_else(|| {
303 match func.kind {
304 ast::FunctionKind::Constructor => "constructor",
305 ast::FunctionKind::Receive => "receive",
306 ast::FunctionKind::Fallback => "fallback",
307 ast::FunctionKind::Function | ast::FunctionKind::Modifier => unreachable!(),
308 }
309 });
310
311 let exclude_func = func.header.virtual_() && !func.is_implemented();
313 if !exclude_func {
314 self.push_item_kind(
315 CoverageItemKind::Function { name: name.into() },
316 item.span,
317 );
318 }
319
320 TernaryVisitor(self).walk_item(item)?;
322 self.walk_item(item)?;
323 }
324 _ => {}
325 }
326 ControlFlow::Continue(())
328 }
329
330 fn visit_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) -> ControlFlow<Self::BreakValue> {
331 match &stmt.kind {
332 StmtKind::Break | StmtKind::Continue | StmtKind::Emit(..) | StmtKind::Revert(..) => {
333 self.push_stmt(stmt.span);
334 return ControlFlow::Continue(());
336 }
337 StmtKind::Return(_) | StmtKind::DeclSingle(_) | StmtKind::DeclMulti(..) => {
338 self.push_stmt(stmt.span);
339 }
340
341 StmtKind::If(_cond, then_stmt, else_stmt) => {
342 let branch_id = self.next_branch_id();
343
344 if stmt_has_statements(then_stmt)
346 || else_stmt.as_ref().is_some_and(|s| stmt_has_statements(s))
347 {
348 let anchor_loc = self.source_location_for(then_stmt.span);
351 self.push_item_kind(
352 CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: true },
353 stmt.span,
354 )
355 .anchor_loc = Some(anchor_loc);
356 if let Some(else_stmt) = else_stmt {
357 let is_first_opcode = stmt_has_statements(else_stmt);
358 let anchor_loc =
359 is_first_opcode.then(|| self.source_location_for(else_stmt.span));
360 self.push_item_kind(
361 CoverageItemKind::Branch { branch_id, path_id: 1, is_first_opcode },
362 stmt.span,
363 )
364 .anchor_loc = anchor_loc;
365 }
366 }
367 }
368
369 StmtKind::Try(ast::StmtTry { expr: _, clauses }) => {
370 let branch_id = self.next_branch_id();
371
372 let mut path_id = 0;
373 for catch in clauses.iter() {
374 let ast::TryCatchClause { span, name: _, args, block } = catch;
375 let span = if path_id == 0 { stmt.span.to(*span) } else { *span };
376 if path_id == 0 || has_statements(Some(block)) {
377 self.push_item_kind(
378 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: true },
379 span,
380 );
381 path_id += 1;
382 } else if !args.is_empty() {
383 self.push_stmt(span);
387 }
388 }
389 }
390
391 StmtKind::Assembly(_)
393 | StmtKind::Block(_)
394 | StmtKind::UncheckedBlock(_)
395 | StmtKind::Placeholder
396 | StmtKind::Expr(_)
397 | StmtKind::While(..)
398 | StmtKind::DoWhile(..)
399 | StmtKind::For { .. } => {}
400 }
401 self.walk_stmt(stmt)
402 }
403
404 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
405 match &expr.kind {
406 ExprKind::Assign(..)
407 | ExprKind::Unary(..)
408 | ExprKind::Binary(..)
409 | ExprKind::Ternary(..) => {
410 self.push_stmt(expr.span);
411 if matches!(expr.kind, ExprKind::Binary(..)) {
412 return self.walk_expr(expr);
413 }
414 }
415 ExprKind::Call(callee, _args) => {
416 self.function_calls.push((expr.span, self.contract_name.clone()));
418
419 if let ExprKind::Ident(ident) = &callee.kind {
420 if ident.as_str() == "require" {
423 let branch_id = self.next_branch_id();
424 self.push_item_kind(
425 CoverageItemKind::Branch {
426 branch_id,
427 path_id: 0,
428 is_first_opcode: false,
429 },
430 expr.span,
431 );
432 self.push_item_kind(
433 CoverageItemKind::Branch {
434 branch_id,
435 path_id: 1,
436 is_first_opcode: false,
437 },
438 expr.span,
439 );
440 }
441 }
442 }
443 _ => {}
444 }
445 ControlFlow::Continue(())
447 }
448
449 fn visit_yul_stmt(&mut self, stmt: &'ast yul::Stmt<'ast>) -> ControlFlow<Self::BreakValue> {
450 use yul::StmtKind;
451 match &stmt.kind {
452 StmtKind::VarDecl(..)
453 | StmtKind::AssignSingle(..)
454 | StmtKind::AssignMulti(..)
455 | StmtKind::Leave
456 | StmtKind::Break
457 | StmtKind::Continue => {
458 self.push_stmt(stmt.span);
459 return ControlFlow::Continue(());
461 }
462 StmtKind::If(..) => {
463 let branch_id = self.next_branch_id();
464 for path_id in 0..2 {
466 self.push_item_kind(
467 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: false },
468 stmt.span,
469 );
470 }
471 }
472 StmtKind::For(yul::StmtFor { body, .. }) => {
473 self.push_stmt(body.span);
474 }
475 StmtKind::Switch(switch) => {
476 for case in switch.cases.iter() {
477 self.push_stmt(case.span);
478 self.push_stmt(case.body.span);
479 }
480 }
481 StmtKind::FunctionDef(func) => {
482 let name = func.name.as_str();
483 self.push_item_kind(CoverageItemKind::Function { name: name.into() }, stmt.span);
484 }
485 StmtKind::Expr(_) => {
487 self.push_stmt(stmt.span);
488 return ControlFlow::Continue(());
489 }
490 StmtKind::Block(_) => {}
491 }
492 self.walk_yul_stmt(stmt)
493 }
494
495 fn visit_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
496 use yul::ExprKind;
497 match &expr.kind {
498 ExprKind::Path(_) | ExprKind::Lit(_) => {}
499 ExprKind::Call(_) => self.push_stmt(expr.span),
500 }
501 ControlFlow::Continue(())
503 }
504}
505
506struct TernaryVisitor<'a, 'gcx>(&'a mut SourceVisitor<'gcx>);
509
510impl<'ast> ast::Visit<'ast> for TernaryVisitor<'_, '_> {
511 type BreakValue = Never;
512
513 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
514 if matches!(expr.kind, ExprKind::Ternary(..)) {
515 let branch_id = self.0.next_branch_id();
516 self.0.ternary_branches.push(branch_id);
517 for path_id in 0..2 {
519 self.0.push_item_kind(
520 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: false },
521 expr.span,
522 );
523 }
524 }
525 self.walk_expr(expr)
526 }
527}
528
529impl<'gcx> hir::Visit<'gcx> for SourceVisitor<'gcx> {
530 type BreakValue = Never;
531
532 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
533 &self.gcx.hir
534 }
535
536 fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
537 if let hir::ExprKind::Call(lhs, ..) = &expr.kind
538 && is_regular_call(lhs)
539 && let Some(scope) = self.function_call_scopes.get(&expr.span).cloned()
540 {
541 let prev = std::mem::replace(&mut self.contract_name, scope);
544 self.push_stmt(expr.span);
545 self.contract_name = prev;
546 }
547 self.walk_expr(expr)
548 }
549}
550
551fn is_regular_call(lhs: &hir::Expr<'_>) -> bool {
554 match lhs.peel_parens().kind {
555 hir::ExprKind::Ident([hir::Res::Item(hir::ItemId::Struct(_))]) => false,
557 hir::ExprKind::Type(_) => false,
559 _ => true,
560 }
561}
562
563fn has_statements(block: Option<&ast::Block<'_>>) -> bool {
564 block.is_some_and(|block| !block.is_empty())
565}
566
567fn stmt_has_statements(stmt: &ast::Stmt<'_>) -> bool {
568 match &stmt.kind {
569 StmtKind::Assembly(a) => !a.block.is_empty(),
570 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => has_statements(Some(b)),
571 _ => true,
572 }
573}
574
575#[derive(Clone, Debug, Default)]
577pub struct SourceAnalysis {
578 all_items: Vec<CoverageItem>,
580 ternary_branches: HashSet<(u32, u32)>,
582 map: Vec<(u32, u32)>,
584 empty_special_functions: HashMap<u32, EmptySpecialFunctionKind>,
586 contract_empty_special_functions: HashMap<u32, HashMap<Arc<str>, Vec<u32>>>,
588}
589
590impl SourceAnalysis {
591 #[instrument(name = "SourceAnalysis::new", skip_all)]
605 pub fn new(data: &SourceFiles, output: &ProjectCompileOutput) -> eyre::Result<Self> {
606 let mut resolved_empty_special_functions = Vec::new();
607 let mut sourced_items = output.parser().solc().compiler().enter(|compiler| {
608 resolved_empty_special_functions =
609 resolve_empty_special_functions(compiler.gcx(), data);
610 data.sources
611 .par_iter()
612 .map(|(&source_id, path)| {
613 let _guard = debug_span!("SourceAnalysis::new::visit", ?path).entered();
614
615 let (_, source) = compiler.gcx().get_ast_source(path).unwrap();
616 let ast = source.ast.as_ref().unwrap();
617 let (hir_source_id, _) = compiler.gcx().get_hir_source(path).unwrap();
618
619 let mut visitor = SourceVisitor::new(source_id, compiler.gcx());
620 for item in ast.items.iter() {
621 match &item.kind {
622 ItemKind::Contract(contract) => {
624 if contract.kind.is_interface() {
626 continue;
627 }
628
629 let checkpoint = visitor.checkpoint();
630 visitor.visit_contract(contract);
631 if visitor.has_tests(&checkpoint) {
632 visitor.restore_checkpoint(checkpoint);
633 }
634 }
635 ItemKind::Function(_) => {
639 visitor.contract_name = Arc::default();
643 let _ = ast::Visit::visit_item(&mut visitor, item);
644 }
645 _ => {}
646 }
647 }
648
649 if !visitor.function_calls.is_empty() {
650 visitor.resolve_function_calls(hir_source_id);
651 }
652
653 if !visitor.items.is_empty() {
654 visitor.disambiguate_functions();
655 visitor.sort();
656 visitor.push_lines();
657 visitor.sort();
658 }
659 (source_id, visitor.items, visitor.ternary_branches)
660 })
661 .collect::<Vec<_>>()
662 });
663
664 sourced_items.sort_by_key(|(id, items, _)| (*id, items.first().map(|i| i.loc.bytes.start)));
666 let Some(&(max_idx, _, _)) = sourced_items.last() else { return Ok(Self::default()) };
667 let len = max_idx + 1;
668 let mut all_items = Vec::new();
669 let mut map = vec![(u32::MAX, 0); len as usize];
670 let mut ternary_branches = HashSet::default();
671 for (idx, items, branches) in sourced_items {
672 ternary_branches.extend(branches.into_iter().map(|branch| (idx, branch)));
673 let idx = idx as usize;
675 if map[idx].0 == u32::MAX {
676 map[idx].0 = all_items.len() as u32;
677 }
678 map[idx].1 += items.len() as u32;
679 all_items.extend(items);
680 }
681
682 let mut empty_special_functions = HashMap::default();
683 let mut contract_empty_special_functions =
684 HashMap::<u32, HashMap<Arc<str>, Vec<u32>>>::default();
685 for resolved in resolved_empty_special_functions {
686 let item_ids = all_items
687 .iter()
688 .enumerate()
689 .filter(|(_, item)| {
690 item.loc.source_id == resolved.function_source_id as usize
691 && item.loc.contract_name == resolved.function_contract_name
692 && item.loc.bytes == resolved.function_bytes
693 })
694 .map(|(item_id, _)| item_id as u32)
695 .collect::<Vec<_>>();
696 empty_special_functions
697 .extend(item_ids.iter().map(|&item_id| (item_id, resolved.kind)));
698 contract_empty_special_functions
699 .entry(resolved.contract_source_id)
700 .or_default()
701 .entry(resolved.contract_name)
702 .or_default()
703 .extend(item_ids);
704 }
705
706 Ok(Self {
707 all_items,
708 map,
709 ternary_branches,
710 empty_special_functions,
711 contract_empty_special_functions,
712 })
713 }
714
715 pub(crate) fn is_ternary_branch(&self, source_id: u32, branch_id: u32) -> bool {
716 self.ternary_branches.contains(&(source_id, branch_id))
717 }
718
719 pub fn all_items(&self) -> &[CoverageItem] {
721 &self.all_items
722 }
723
724 pub const fn all_items_mut(&mut self) -> &mut Vec<CoverageItem> {
726 &mut self.all_items
727 }
728
729 pub fn items_for_source_enumerated(
731 &self,
732 source_id: u32,
733 ) -> impl Iterator<Item = (u32, &CoverageItem)> {
734 let (base_id, items) = self.items_for_source(source_id);
735 items.iter().enumerate().map(move |(idx, item)| (base_id + idx as u32, item))
736 }
737
738 pub fn items_for_source(&self, source_id: u32) -> (u32, &[CoverageItem]) {
740 let (mut offset, len) = self.map.get(source_id as usize).copied().unwrap_or_default();
741 if offset == u32::MAX {
742 offset = 0;
743 }
744 (offset, &self.all_items[offset as usize..][..len as usize])
745 }
746
747 #[inline]
749 pub fn get(&self, item_id: u32) -> Option<&CoverageItem> {
750 self.all_items.get(item_id as usize)
751 }
752
753 pub(crate) fn empty_special_function_kind(
754 &self,
755 item_id: u32,
756 ) -> Option<EmptySpecialFunctionKind> {
757 self.empty_special_functions.get(&item_id).copied()
758 }
759
760 pub(crate) fn empty_special_function_ids(
761 &self,
762 source_id: u32,
763 contract_name: &str,
764 ) -> impl Iterator<Item = u32> + '_ {
765 self.contract_empty_special_functions
766 .get(&source_id)
767 .and_then(|contracts| contracts.get(contract_name))
768 .into_iter()
769 .flatten()
770 .copied()
771 }
772}
773
774#[derive(Default)]
776pub struct SourceFiles {
777 pub sources: HashMap<u32, PathBuf>,
779}