Skip to content

Commit af95457

Browse files
committed
refactor
1 parent e61bdb7 commit af95457

File tree

2 files changed

+114
-101
lines changed

2 files changed

+114
-101
lines changed

vhdl_lang/src/ast/util.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,19 @@ impl Name {
339339
}
340340
}
341341

342+
/// Like [self.get_suffix_reference], but disregards final indexes, such as
343+
/// `foo.bar.baz(0)`
344+
pub fn get_suffix_reference_disregard_index(&self) -> Option<EntityId> {
345+
use Name::*;
346+
match self {
347+
Designator(suffix) => suffix.reference.get(),
348+
Selected(_, suffix) => suffix.item.reference.get(),
349+
Slice(name, _) => name.item.get_suffix_reference_disregard_index(),
350+
CallOrIndexed(coi) => coi.name.item.get_suffix_reference_disregard_index(),
351+
_ => None,
352+
}
353+
}
354+
342355
pub fn prefix(&self) -> Option<&Designator> {
343356
match self {
344357
Self::Attribute(attr) => attr.name.item.prefix(),

vhdl_lang/src/lint/sensitivity_list.rs

Lines changed: 101 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -133,22 +133,23 @@ fn lint_sensitivity_list(
133133
return vec![];
134134
}
135135

136-
let mut signals_in_sensitivity_list: FnvHashMap<_, _> = names
136+
let signals_in_sensitivity_list: FnvHashMap<_, _> = names
137137
.iter()
138138
.flat_map(|name| {
139139
name.item
140-
.get_suffix_reference()
140+
.get_suffix_reference_disregard_index()
141141
.map(|reference| (reference, name.span))
142142
})
143143
.collect();
144-
let required_sensitivity_list =
145-
generate_sensitivity_list(root, process, signals_in_sensitivity_list.clone(), ctx);
146-
let mut missing_signals = Vec::new();
147-
for (eid, src_pos) in required_sensitivity_list {
148-
if signals_in_sensitivity_list.remove(&eid).is_none() {
149-
missing_signals.push((eid, src_pos))
150-
}
151-
}
144+
let mut searcher = SensitivityListChecker {
145+
root,
146+
sensitivity_list: signals_in_sensitivity_list.clone(),
147+
superfluous_entities: signals_in_sensitivity_list,
148+
found_entities: FnvHashMap::default(),
149+
};
150+
let _ = process.statements.search(ctx, &mut searcher);
151+
152+
let mut missing_signals = searcher.found_entities.into_iter().collect_vec();
152153
let mut diagnostics = Vec::new();
153154
if !missing_signals.is_empty() {
154155
missing_signals.sort_by(|(_, pos1), (_, pos2)| pos1.cmp(pos2));
@@ -160,7 +161,7 @@ fn lint_sensitivity_list(
160161
pluralize(missing_signals.len(), "The signal", "Signals"),
161162
missing_signals
162163
.iter()
163-
.map(|(sig, _)| root.get_ent(*sig).designator.to_string())
164+
.map(|(sig, _)| format!("'{}'", root.get_ent(*sig).designator))
164165
.join(", "),
165166
pluralize(missing_signals.len(), "is", "are"),
166167
),
@@ -169,13 +170,16 @@ fn lint_sensitivity_list(
169170
for (signal, pos) in missing_signals {
170171
diag.add_related(
171172
pos,
172-
format!("{} first read here", root.get_ent(signal).describe()),
173+
format!(
174+
"signal '{}' first read here",
175+
root.get_ent(signal).designator
176+
),
173177
)
174178
}
175179
diagnostics.push(diag);
176180
}
177181

178-
for (_, pos) in signals_in_sensitivity_list {
182+
for (_, pos) in searcher.superfluous_entities {
179183
diagnostics.push(Diagnostic::new(
180184
pos.get_pos(ctx),
181185
"Signal is never read in the process",
@@ -236,41 +240,18 @@ fn pluralize<'a>(len: usize, singular: &'a str, plural: &'a str) -> &'a str {
236240
}
237241
}
238242

239-
/// finds all signals that are present in a sensitivity list,
240-
/// if the process contains the `all` keyword inside the sensitivity list.
241-
/// This is also used to compare against the actual values of the sensitivity list
242-
/// so that superfluous / lacking signals can be detected.
243-
///
244-
/// Caveats:
245-
/// - Information from extended names is not included
246-
/// - Information from procedure calls is not included
247-
struct SensitivityListGenerator<'a> {
243+
struct SensitivityListChecker<'a> {
248244
root: &'a DesignRoot,
249245
/// The sensitivity list present at the generate statement
250246
sensitivity_list: FnvHashMap<EntityId, TokenSpan>,
247+
/// Additional entities that are in the Sensitivity list, but are never read in the process.
248+
superfluous_entities: FnvHashMap<EntityId, TokenSpan>,
251249
/// A set of named entities that form the sensitivity list.
252250
/// Additionally, the token span of the first occurrence is also provided.
253251
found_entities: FnvHashMap<EntityId, SrcPos>,
254252
}
255253

256-
/// Generate a sensitivity list from a process statement
257-
fn generate_sensitivity_list(
258-
root: &DesignRoot,
259-
process: &ProcessStatement,
260-
sensitivity_list: FnvHashMap<EntityId, TokenSpan>,
261-
ctx: &dyn TokenAccess,
262-
) -> FnvHashMap<EntityId, SrcPos> {
263-
let mut searcher = SensitivityListGenerator {
264-
root,
265-
sensitivity_list,
266-
found_entities: FnvHashMap::default(),
267-
};
268-
let _ = process.statements.search(ctx, &mut searcher);
269-
270-
searcher.found_entities
271-
}
272-
273-
impl SensitivityListGenerator<'_> {
254+
impl SensitivityListChecker<'_> {
274255
fn analyze_expression(&mut self, expr: &Expression, span: TokenSpan, ctx: &dyn TokenAccess) {
275256
match expr {
276257
Expression::Binary(_, lhs, rhs) => {
@@ -295,7 +276,7 @@ impl SensitivityListGenerator<'_> {
295276
Expression::Qualified(qual_expr) => {
296277
self.analyze_expression(&qual_expr.expr.item, qual_expr.expr.span, ctx)
297278
}
298-
Expression::Name(name) => self.analyze_name(name, span, ctx),
279+
Expression::Name(name) => self.analyze_name(name, span, ctx, false),
299280
Expression::New(allocator) => match &allocator.item {
300281
Allocator::Qualified(qual_expr) => {
301282
self.analyze_expression(&qual_expr.expr.item, qual_expr.expr.span, ctx)
@@ -307,21 +288,27 @@ impl SensitivityListGenerator<'_> {
307288
}
308289
}
309290

310-
fn analyze_name(&mut self, name: &Name, pos: TokenSpan, ctx: &dyn TokenAccess) {
291+
fn analyze_name(
292+
&mut self,
293+
name: &Name,
294+
pos: TokenSpan,
295+
ctx: &dyn TokenAccess,
296+
remove_only: bool,
297+
) {
311298
use Name::*;
312299
match name {
313300
Designator(designator) => {
314-
self.analyze_designator(designator, pos, ctx);
301+
self.analyze_designator(designator, pos, ctx, remove_only);
315302
}
316-
Selected(_, suffix) => {
317-
// self.analyze_name(&prefix.item, prefix.span, ctx);
318-
self.analyze_designator(&suffix.item, pos, ctx);
303+
Selected(prefix, suffix) => {
304+
self.analyze_name(&prefix.item, prefix.span, ctx, true);
305+
self.analyze_designator(&suffix.item, pos, ctx, remove_only);
319306
}
320-
SelectedAll(prefix) => self.analyze_name(&prefix.item, prefix.span, ctx),
321-
Slice(prefix, _) => self.analyze_name(&prefix.item, prefix.span, ctx),
307+
SelectedAll(prefix) => self.analyze_name(&prefix.item, prefix.span, ctx, remove_only),
308+
Slice(prefix, _) => self.analyze_name(&prefix.item, prefix.span, ctx, remove_only),
322309
Attribute(attr) => self.analyze_attribute_name(attr, ctx),
323310
CallOrIndexed(coi) => {
324-
self.analyze_name(&coi.name.item, coi.name.span, ctx);
311+
self.analyze_name(&coi.name.item, coi.name.span, ctx, remove_only);
325312
for item in &coi.parameters.items {
326313
match &item.actual.item {
327314
ActualPart::Expression(expr) => {
@@ -342,17 +329,29 @@ impl SensitivityListGenerator<'_> {
342329
designator: &WithRef<Designator>,
343330
pos: TokenSpan,
344331
ctx: &dyn TokenAccess,
332+
remove_only: bool,
345333
) {
346334
if let Some(reference) = designator.reference.get() {
335+
self.superfluous_entities.remove(&reference);
347336
let ent = self.root.get_ent(reference);
348-
if ent.is_signal() {
349-
self.sensitivity_list.remove(&ent.id);
337+
if ent.is_signal() && !self.sensitivity_list.contains_key(&reference) && !remove_only {
338+
self.found_entities
339+
.entry(reference)
340+
.or_insert_with(|| pos.get_pos(ctx));
350341
}
351342
}
352343
}
353344

354345
fn analyze_attribute_name(&mut self, attr: &AttributeName, ctx: &dyn TokenAccess) {
355-
self.analyze_name(&attr.name.item, attr.name.span, ctx);
346+
use AttributeDesignator::*;
347+
match attr.attr.item {
348+
Ident(_) | Image => {}
349+
// These likely do not belong inside a sensitivity list.
350+
// However, true analysis can only be performed when it is known whether the name is
351+
// static or not.
352+
_ => return,
353+
}
354+
self.analyze_name(&attr.name.item, attr.name.span, ctx, false);
356355
if let Some(expr) = &attr.expr {
357356
self.analyze_expression(&expr.item, expr.span, ctx)
358357
}
@@ -411,7 +410,7 @@ impl SensitivityListGenerator<'_> {
411410
fn analyze_discrete_range(&mut self, range: &DiscreteRange, ctx: &dyn TokenAccess) {
412411
match range {
413412
DiscreteRange::Discrete(name, range) => {
414-
self.analyze_name(&name.item, name.span, ctx);
413+
self.analyze_name(&name.item, name.span, ctx, false);
415414
if let Some(range) = range {
416415
self.analyze_range(range, ctx)
417416
}
@@ -460,7 +459,7 @@ impl SensitivityListGenerator<'_> {
460459
}
461460
}
462461

463-
impl Searcher for SensitivityListGenerator<'_> {
462+
impl Searcher for SensitivityListChecker<'_> {
464463
fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState {
465464
use SequentialStatement::*;
466465
if let DeclarationItem::SequentialStatement(stmt) = &decl.ast {
@@ -546,29 +545,16 @@ enum ProcessCategory {
546545
/// ```
547546
/// but won't work for some more exotic, mixed processes.
548547
fn get_likely_process_category(root: &DesignRoot, process: &ProcessStatement) -> ProcessCategory {
549-
// This might be incorrect for weird mixed processes like
550-
// ```
551-
// process (clk) is
552-
// begin
553-
// if rising_edge(clk) then
554-
// // ...
555-
// end if;
556-
// a <= b or c;
557-
// end process;
558-
// ```
559-
if process.statements.len() > 1 {
560-
return ProcessCategory::Combinational;
561-
};
562-
// using iter().all(...) guards against cases like the following:
548+
// using iter().any(...) guards against edge cases like the following:
563549
// ```
564550
// process(clkA, clkB) is
565551
// begin
566552
// if rising_edge(clkA) then
567553
// end if;
568-
// if rising_edge(clkB) then
569-
// end if;
554+
//
555+
// some_assignment <= xy;
570556
// end process;
571-
let is_likely_clocked = process.statements.iter().all(|stmt| {
557+
let is_likely_clocked = process.statements.iter().any(|stmt| {
572558
match &stmt.statement.item {
573559
SequentialStatement::If(if_stmt) => {
574560
// this is always guaranteed to be present
@@ -630,23 +616,14 @@ fn is_likely_clocked(root: &DesignRoot, expression: &Expression) -> bool {
630616
mod tests {
631617
use super::*;
632618
use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder};
633-
use crate::syntax::test::Code;
619+
use crate::syntax::test::check_diagnostics;
634620
use std::cell::Cell;
635621

636-
fn get_ent(root: &DesignRoot, code: Code) -> (EntityId, SrcPos) {
637-
(
638-
root.search_reference(code.source(), code.start())
639-
.unwrap()
640-
.id,
641-
code.pos(),
642-
)
643-
}
644-
645622
#[test]
646623
fn extract_sensitivity_list_no_items() {
647624
let mut builder = LibraryBuilder::new();
648625

649-
let _ = builder.code(
626+
builder.code(
650627
"libname",
651628
"
652629
entity ent is
@@ -662,11 +639,12 @@ end architecture;",
662639

663640
let (root, diagnostics) = builder.get_analyzed_root();
664641
check_no_diagnostics(&diagnostics);
642+
665643
let num_of_searches = Cell::new(0);
666644
let mut searcher = ProcessSearcher::new(|proc, ctx| {
667645
num_of_searches.set(num_of_searches.get() + 1);
668-
let res = generate_sensitivity_list(&root, proc, ctx);
669-
assert_eq!(res, FnvHashMap::default());
646+
let diag = lint_sensitivity_list(&root, ctx, proc);
647+
assert_eq!(diag, Vec::default());
670648
});
671649
let _ = root.search(&mut searcher);
672650
assert_eq!(num_of_searches.get(), 1)
@@ -688,15 +666,33 @@ end entity;
688666
architecture a of ent is
689667
-- all of these signals should be considered as being 'read' in the sensitivity list
690668
signal sig_b, sig_c, sig_d, sig_e, sig_f, sig_g, sig_h, sig_i, sig_j, sig_k, sig_l : bit;
669+
-- all of these signals are fine and present in the sensitivity list
670+
signal sig_0 : bit;
671+
signal sig_1 : bit_vector(1 downto 0);
672+
type t_rec is record
673+
foo : bit;
674+
end record;
675+
signal sig_2 : t_rec;
676+
signal sig_3, sig_4, sig_5, sig_6 : bit;
677+
-- superfluous signal
678+
signal additional : bit;
691679
signal void : bit;
692680
693681
function func(din: bit) return bit is
694682
begin
695683
end func;
696684
begin
697-
process is
685+
process(sig_0, sig_1(0), sig_2, sig_3, sig_4, sig_5, sig_6, additional) is
698686
variable void_var : bit;
699687
begin
688+
if (sig_5 = '1') then
689+
void <= sig_6;
690+
end if;
691+
void <= sig_3 or sig_4;
692+
void <= sig_3;
693+
void <= sig_2.foo;
694+
void <= sig_0;
695+
void <= sig_1(0);
700696
void <= sig_a;
701697
void <= sig_b;
702698
void <= sig_b;
@@ -720,25 +716,29 @@ end architecture;",
720716
check_no_diagnostics(&diagnostics);
721717

722718
let expected_signals = [
723-
get_ent(&root, code.s("sig_a", 2)),
724-
get_ent(&root, code.s("sig_b", 2)),
725-
get_ent(&root, code.s("sig_c", 2)),
726-
get_ent(&root, code.s("sig_d", 2)),
727-
get_ent(&root, code.s("sig_e", 2)),
728-
get_ent(&root, code.s("sig_f", 2)),
729-
get_ent(&root, code.s("sig_g", 2)),
730-
get_ent(&root, code.s("sig_h", 2)),
731-
get_ent(&root, code.s("sig_i", 2)),
732-
get_ent(&root, code.s("sig_j", 2)),
733-
get_ent(&root, code.s("sig_k", 2)),
734-
get_ent(&root, code.s("sig_l", 2)),
735-
];
719+
"sig_a", "sig_b", "sig_c", "sig_d", "sig_e", "sig_f", "sig_g", "sig_h", "sig_i",
720+
"sig_j", "sig_k", "sig_l",
721+
]
722+
.map(|value| (value, code.s(value, 2).pos()));
736723

737724
let num_of_searches = Cell::new(0);
738725
let mut searcher = ProcessSearcher::new(|proc, ctx| {
739726
num_of_searches.set(num_of_searches.get() + 1);
740-
let res = generate_sensitivity_list(&root, proc, ctx);
741-
assert_eq!(res, expected_signals.iter().cloned().collect());
727+
let res = lint_sensitivity_list(&root, ctx, proc);
728+
let mut expected_missing_diag = Diagnostic::new(
729+
code.s1("process").pos(),
730+
"Signals 'sig_a', 'sig_b', 'sig_c', 'sig_d', 'sig_e', 'sig_f', 'sig_g', 'sig_h', 'sig_i', 'sig_j', 'sig_k', 'sig_l' are not read in the sensitivity list",
731+
ErrorCode::MissingInSensitivityList
732+
);
733+
for (name, pos) in &expected_signals {
734+
expected_missing_diag.add_related(pos, format!("signal '{name}' first read here"));
735+
}
736+
let expected_superfluous_diag = Diagnostic::new(
737+
code.s("additional", 2),
738+
"Signal is never read in the process",
739+
ErrorCode::SuperfluousInSensitivityList,
740+
);
741+
check_diagnostics(res, vec![expected_missing_diag, expected_superfluous_diag]);
742742
});
743743
let _ = root.search(&mut searcher);
744744
assert_eq!(num_of_searches.get(), 1)
@@ -845,6 +845,6 @@ end architecture;",
845845
idx.set(idx.get() + 1);
846846
});
847847
let _ = root.search(&mut searcher);
848-
assert_eq!(idx.get(), 8);
848+
assert_eq!(idx.get(), 9);
849849
}
850850
}

0 commit comments

Comments
 (0)