Skip to content

Commit 6a02808

Browse files
authored
refactor: Remove analysis result in favour of EvalResult (#267)
* Remove more analysis errors * lookup_operator to eval result * name analysis function to eval result * Remove AnalysisResult * Replace catch_diagnostic with IntoEvalResult * Fix: Typo * Add documentation
1 parent d65dcd2 commit 6a02808

File tree

7 files changed

+460
-391
lines changed

7 files changed

+460
-391
lines changed

vhdl_lang/src/analysis/analyze.rs

Lines changed: 137 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,35 @@ use fnv::FnvHashSet;
1414
use std::cell::RefCell;
1515
use std::ops::Deref;
1616

17-
#[derive(Debug, PartialEq, Eq)]
18-
pub enum AnalysisError {
19-
Fatal(CircularDependencyError),
20-
NotFatal(Diagnostic),
21-
}
22-
23-
impl AnalysisError {
24-
pub fn not_fatal_error(pos: impl AsRef<SrcPos>, msg: impl Into<String>) -> AnalysisError {
25-
AnalysisError::NotFatal(Diagnostic::error(pos, msg))
26-
}
27-
}
28-
17+
/// Indicates that a circular dependency is found at the position denoted by `reference`.
18+
///
19+
/// A circular dependency occurs when module A uses module B, which in turn
20+
/// (either directly or indirectly via other modules) uses module A again.
21+
///
22+
/// ## Example
23+
///
24+
/// ```vhdl
25+
/// use work.bar;
26+
///
27+
/// package foo is
28+
/// end package;
29+
///
30+
/// use work.foo;
31+
///
32+
/// package bar is
33+
/// end package;
34+
/// ```
35+
/// In this example, the package `bar` uses the package `foo` which in turn uses package `bar` –
36+
/// making the dependency chain cyclic.
37+
///
38+
/// Commonly, two or more `CircularDependencyError`s are pushed to indicate what modules
39+
/// the error affects.
2940
#[derive(Clone, Debug, PartialEq, Eq)]
3041
#[must_use]
3142
pub struct CircularDependencyError {
43+
/// The position where the circular dependency was found.
44+
/// Is `None` when the circular dependency is found in the standard library.
45+
/// This should, in practice, never happen.
3246
reference: Option<SrcPos>,
3347
}
3448

@@ -39,97 +53,84 @@ impl CircularDependencyError {
3953
}
4054
}
4155

56+
/// Pushes this error into a diagnostic handler.
4257
pub fn push_into(self, diagnostics: &mut dyn DiagnosticHandler) {
4358
if let Some(pos) = self.reference {
44-
diagnostics.push(Diagnostic::error(pos, "Found circular dependency"));
59+
diagnostics.error(pos, "Found circular dependency");
4560
}
4661
}
4762
}
4863

49-
pub type AnalysisResult<T> = Result<T, AnalysisError>;
64+
/// A `FatalResult` is a result that is either OK or contains a `CircularDependencyError`.
65+
/// If this type contains the error case, most other errors encountered during analysis are ignored
66+
/// as analysis cannot continue (resp. no further analysis is pursued)
5067
pub type FatalResult<T = ()> = Result<T, CircularDependencyError>;
5168

52-
pub fn as_fatal<T>(res: EvalResult<T>) -> FatalResult<Option<T>> {
53-
match res {
54-
Ok(val) => Ok(Some(val)),
55-
Err(EvalError::Unknown) => Ok(None),
56-
Err(EvalError::Circular(circ)) => Err(circ),
57-
}
58-
}
59-
60-
pub fn catch_diagnostic<T>(
61-
res: Result<T, Diagnostic>,
62-
diagnostics: &mut dyn DiagnosticHandler,
63-
) -> EvalResult<T> {
64-
match res {
65-
Ok(val) => Ok(val),
66-
Err(diag) => {
67-
diagnostics.push(diag);
68-
Err(EvalError::Unknown)
69-
}
70-
}
71-
}
72-
73-
pub fn catch_analysis_err<T>(
74-
res: AnalysisResult<T>,
75-
diagnostics: &mut dyn DiagnosticHandler,
76-
) -> EvalResult<T> {
77-
match res {
78-
Ok(val) => Ok(val),
79-
Err(AnalysisError::Fatal(err)) => Err(EvalError::Circular(err)),
80-
Err(AnalysisError::NotFatal(diag)) => {
81-
diagnostics.push(diag);
82-
Err(EvalError::Unknown)
83-
}
84-
}
85-
}
86-
8769
#[derive(Debug, PartialEq, Eq)]
8870
pub enum EvalError {
89-
// A circular dependency was found
71+
/// A circular dependency was found, see [CircularDependencyError](CircularDependencyError)
9072
Circular(CircularDependencyError),
91-
// Evaluation is no longer possible, for example if encountering illegal code
92-
// Typically functions returning Unknown will have published diagnostics on the side-channel
93-
// And the unknown is returned to stop further upstream analysis
73+
/// Indicates that evaluation is no longer possible, for example if encountering illegal code.
74+
/// Typically, functions returning Unknown will have published diagnostics on the side-channel
75+
/// and the unknown is returned to stop further upstream analysis
9476
Unknown,
9577
}
9678

97-
pub type EvalResult<T = ()> = Result<T, EvalError>;
98-
99-
impl From<CircularDependencyError> for AnalysisError {
100-
fn from(err: CircularDependencyError) -> AnalysisError {
101-
AnalysisError::Fatal(err)
102-
}
103-
}
104-
10579
impl From<CircularDependencyError> for EvalError {
10680
fn from(err: CircularDependencyError) -> EvalError {
10781
EvalError::Circular(err)
10882
}
10983
}
11084

111-
impl From<Diagnostic> for AnalysisError {
112-
fn from(diagnostic: Diagnostic) -> AnalysisError {
113-
AnalysisError::NotFatal(diagnostic)
114-
}
85+
/// The result of the evaluation of an AST element.
86+
/// The result has either a value of `Ok(T)`, indicating a successful evaluation of
87+
/// the AST and returning the result of that evaluation, or `Err(EvalError)`, indicating
88+
/// an error during evaluation.
89+
///
90+
/// Most of the time, the error will be `EvalError::Unknown`. This means that the evaluation
91+
/// step has pushed found problems in the code to some side-channel and simply returns an error,
92+
/// signifying that some problem was found without further specifying that problem.
93+
pub type EvalResult<T = ()> = Result<T, EvalError>;
94+
95+
/// Pushes the diagnostic to the provided handler and returns
96+
/// with an `EvalError::Unknown` result.
97+
///
98+
/// This macro can be used for the common case of encountering an analysis error and
99+
/// immediately returning as the error is not recoverable.
100+
macro_rules! bail {
101+
($diagnostics:expr, $error:expr) => {
102+
$diagnostics.push($error);
103+
return Err(EvalError::Unknown);
104+
};
115105
}
116106

117-
impl AnalysisError {
118-
// Add Non-fatal error to diagnostics or return fatal error
119-
pub fn add_to(self, diagnostics: &mut dyn DiagnosticHandler) -> FatalResult {
120-
let diag = self.into_non_fatal()?;
121-
diagnostics.push(diag);
122-
Ok(())
123-
}
107+
pub trait IntoEvalResult<T> {
108+
/// Transforms `Self` into an `EvalResult`.
109+
///
110+
/// If `Self` has any severe errors, these should be pushed to the provided handler
111+
/// and an `Err(EvalError::Unknown)` should be returned.
112+
fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult<T>;
113+
}
124114

125-
pub fn into_non_fatal(self) -> FatalResult<Diagnostic> {
115+
impl<T> IntoEvalResult<T> for Result<T, Diagnostic> {
116+
fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult<T> {
126117
match self {
127-
AnalysisError::Fatal(err) => Err(err),
128-
AnalysisError::NotFatal(diag) => Ok(diag),
118+
Ok(value) => Ok(value),
119+
Err(diagnostic) => {
120+
bail!(diagnostics, diagnostic);
121+
}
129122
}
130123
}
131124
}
132125

126+
pub fn as_fatal<T>(res: EvalResult<T>) -> FatalResult<Option<T>> {
127+
match res {
128+
Ok(val) => Ok(Some(val)),
129+
Err(EvalError::Unknown) => Ok(None),
130+
Err(EvalError::Circular(circ)) => Err(circ),
131+
}
132+
}
133+
133134
pub(super) struct AnalyzeContext<'a> {
134135
pub(super) root: &'a DesignRoot,
135136

@@ -138,7 +139,7 @@ pub(super) struct AnalyzeContext<'a> {
138139
standard_sym: Symbol,
139140
pub(super) is_std_logic_1164: bool,
140141

141-
// Record dependencies and sensitivies when
142+
// Record dependencies and sensitives when
142143
// analyzing design units
143144
//
144145
// Dependencies define the order in which design units must be analyzed
@@ -354,78 +355,104 @@ impl<'a> AnalyzeContext<'a> {
354355
None
355356
}
356357

358+
/// Given an Entity, returns a reference to the architecture denoted by `architecture_name`.
359+
/// If this architecture cannot be found, pushes an appropriate error to the diagnostics-handler
360+
/// and returns `EvalError::Unknown`.
361+
///
362+
/// # Arguments
363+
///
364+
/// * `diagnostics` Reference to the diagnostic handler
365+
/// * `library_name` The name of the library where the entity resides
366+
/// * `pos` The position where the architecture name was declared
367+
/// * `entity_name` Name of the entity
368+
/// * `architecture_name` Name of the architecture to be resolved
357369
pub(super) fn get_architecture(
358370
&self,
371+
diagnostics: &mut dyn DiagnosticHandler,
359372
library_name: &Symbol,
360373
pos: &SrcPos,
361374
entity_name: &Symbol,
362375
architecture_name: &Symbol,
363-
) -> AnalysisResult<DesignEnt<'a>> {
376+
) -> EvalResult<DesignEnt<'a>> {
364377
if let Some(unit) = self.get_secondary_unit(library_name, entity_name, architecture_name) {
365378
let data = self.get_analysis(Some(pos), unit)?;
366379
if let AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(arch)) = data.deref() {
367380
if let Some(id) = arch.ident.decl.get() {
368381
let ent = self.arena.get(id);
369-
let design = DesignEnt::from_any(ent).ok_or_else(|| {
382+
if let Some(design) = DesignEnt::from_any(ent) {
383+
return Ok(design);
384+
} else {
370385
// Almost impossible but better not fail silently
371-
Diagnostic::error(
372-
pos,
373-
format!(
374-
"Found non-design {} unit within library {}",
375-
ent.describe(),
376-
library_name
377-
),
378-
)
379-
})?;
380-
return Ok(design);
386+
bail!(
387+
diagnostics,
388+
Diagnostic::error(
389+
pos,
390+
format!(
391+
"Found non-design {} unit within library {}",
392+
ent.describe(),
393+
library_name
394+
),
395+
)
396+
);
397+
}
381398
}
382399
}
383400
}
384401

385-
Err(AnalysisError::NotFatal(Diagnostic::error(
386-
pos,
387-
format!(
402+
bail!(
403+
diagnostics,
404+
Diagnostic::error(
405+
pos,
406+
format!(
388407
"No architecture '{architecture_name}' for entity '{library_name}.{entity_name}'"
389408
),
390-
)))
409+
)
410+
);
391411
}
392412

393413
pub fn lookup_in_library(
394414
&self,
415+
diagnostics: &mut dyn DiagnosticHandler,
395416
library_name: &Symbol,
396417
pos: &SrcPos,
397418
primary_name: &Designator,
398-
) -> AnalysisResult<DesignEnt<'a>> {
419+
) -> EvalResult<DesignEnt<'a>> {
399420
if let Designator::Identifier(ref primary_name) = primary_name {
400421
if let Some(unit) = self.get_primary_unit(library_name, primary_name) {
401422
let data = self.get_analysis(Some(pos), unit)?;
402423
if let AnyDesignUnit::Primary(primary) = data.deref() {
403424
if let Some(id) = primary.ent_id() {
404425
let ent = self.arena.get(id);
405-
let design = DesignEnt::from_any(ent).ok_or_else(|| {
406-
// Almost impossible but better not fail silently
407-
Diagnostic::error(
408-
pos,
409-
format!(
410-
"Found non-design {} unit within library {}",
411-
ent.describe(),
412-
library_name
413-
),
414-
)
415-
})?;
416-
return Ok(design);
426+
if let Some(design) = DesignEnt::from_any(ent) {
427+
return Ok(design);
428+
} else {
429+
bail!(
430+
diagnostics,
431+
Diagnostic::error(
432+
pos,
433+
format!(
434+
"Found non-design {} unit within library {}",
435+
ent.describe(),
436+
library_name
437+
),
438+
)
439+
);
440+
}
417441
}
418442
}
419443
}
420444
}
421445

422-
Err(AnalysisError::NotFatal(Diagnostic::error(
423-
pos,
424-
format!("No primary unit '{primary_name}' within library '{library_name}'"),
425-
)))
446+
bail!(
447+
diagnostics,
448+
Diagnostic::error(
449+
pos,
450+
format!("No primary unit '{primary_name}' within library '{library_name}'"),
451+
)
452+
);
426453
}
427454

428-
// Returns None when analyzing the standard package itsel
455+
// Returns None when analyzing the standard package itself
429456
fn standard_package_region(&self) -> Option<&'a Region<'a>> {
430457
if let Some(pkg) = self.root.standard_pkg_id.as_ref() {
431458
self.arena.link(self.root.standard_arena.as_ref().unwrap());

0 commit comments

Comments
 (0)