Skip to content

Commit 8aeb721

Browse files
committed
Check boolean condition on assert statement
1 parent 10fee88 commit 8aeb721

File tree

6 files changed

+227
-7
lines changed

6 files changed

+227
-7
lines changed

vhdl_lang/src/analysis/expression.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,93 @@ impl<'a> AnalyzeContext<'a> {
533533
self.expr_pos_with_ttyp(scope, target_type, &expr.pos, &mut expr.item, diagnostics)
534534
}
535535

536+
fn implicit_bool_types(&self, scope: &Scope<'a>, pos: &SrcPos) -> FnvHashSet<BaseType<'a>> {
537+
if let Ok(NamedEntities::Overloaded(overloaded)) =
538+
scope.lookup(pos, &Designator::OperatorSymbol(Operator::QueQue))
539+
{
540+
overloaded
541+
.entities()
542+
.filter_map(|ent| ent.formals().nth(0).map(|typ| typ.type_mark().base()))
543+
.collect()
544+
} else {
545+
FnvHashSet::default()
546+
}
547+
}
548+
549+
/// An expression that is either boolean or implicitly boolean via ?? operator
550+
pub fn boolean_expr(
551+
&self,
552+
scope: &Scope<'a>,
553+
expr: &mut WithPos<Expression>,
554+
diagnostics: &mut dyn DiagnosticHandler,
555+
) -> FatalResult {
556+
if let Some(types) = as_fatal(self.expr_type(scope, expr, diagnostics))? {
557+
match types {
558+
ExpressionType::Unambiguous(typ) => {
559+
if typ.base() != self.boolean().base() {
560+
let implicit_bools = self.implicit_bool_types(scope, &expr.pos);
561+
if !implicit_bools.contains(&typ.base()) {
562+
diagnostics.error(
563+
&expr.pos,
564+
format!(
565+
"{} cannot be implictly converted to {}. Operator ?? is not defined for this type.",
566+
typ.describe(),
567+
self.boolean().describe()
568+
),
569+
);
570+
}
571+
}
572+
}
573+
ExpressionType::Ambiguous(types) => {
574+
if types.contains(&self.boolean().base()) {
575+
self.expr_with_ttyp(scope, self.boolean(), expr, diagnostics)?;
576+
} else {
577+
let implicit_bool_types: FnvHashSet<_> = self
578+
.implicit_bool_types(scope, &expr.pos)
579+
.intersection(&types)
580+
.cloned()
581+
.collect();
582+
583+
match implicit_bool_types.len().cmp(&1) {
584+
std::cmp::Ordering::Equal => {
585+
let typ: TypeEnt = types.into_iter().next().unwrap().into();
586+
self.expr_with_ttyp(scope, typ, expr, diagnostics)?;
587+
}
588+
std::cmp::Ordering::Greater => {
589+
let mut diag = Diagnostic::error(
590+
&expr.pos,
591+
"Ambiguous use of implicit boolean conversion ??",
592+
);
593+
diag.add_type_candididates("Could be", implicit_bool_types);
594+
diagnostics.push(diag);
595+
}
596+
597+
std::cmp::Ordering::Less => {
598+
let mut diag = Diagnostic::error(
599+
&expr.pos,
600+
format!(
601+
"Cannot disambiguate expression to {}",
602+
self.boolean().describe()
603+
),
604+
);
605+
diag.add_type_candididates(
606+
"Implicit boolean conversion operator ?? is not defined for",
607+
types,
608+
);
609+
diagnostics.push(diag);
610+
}
611+
}
612+
}
613+
}
614+
ExpressionType::String | ExpressionType::Null | ExpressionType::Aggregate => {
615+
self.expr_with_ttyp(scope, self.boolean(), expr, diagnostics)?;
616+
}
617+
}
618+
}
619+
620+
Ok(())
621+
}
622+
536623
/// Returns true if the name actually matches the target type
537624
/// None if it was uncertain
538625
pub fn expr_pos_with_ttyp(

vhdl_lang/src/analysis/formal_region.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,12 @@ impl<'a> FormalRegion<'a> {
142142
self.entities.get(idx).cloned()
143143
}
144144

145-
pub fn binary(&self) -> Option<(InterfaceEnt, InterfaceEnt<'a>)> {
146-
let left = self.nth(0)?;
147-
let right = self.nth(1)?;
148-
Some((left, right))
145+
pub fn unary(&self) -> Option<InterfaceEnt<'a>> {
146+
if self.len() == 1 {
147+
self.nth(0)
148+
} else {
149+
None
150+
}
149151
}
150152
}
151153

vhdl_lang/src/analysis/named_entity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub enum AnyEntKind<'a> {
5454
}
5555

5656
impl<'a> AnyEntKind<'a> {
57-
pub fn new_function_decl(
57+
pub(crate) fn new_function_decl(
5858
formals: FormalRegion<'a>,
5959
return_type: TypeEnt<'a>,
6060
) -> AnyEntKind<'a> {
@@ -64,7 +64,7 @@ impl<'a> AnyEntKind<'a> {
6464
)))
6565
}
6666

67-
pub fn new_procedure_decl(formals: FormalRegion<'a>) -> AnyEntKind<'a> {
67+
pub(crate) fn new_procedure_decl(formals: FormalRegion<'a>) -> AnyEntKind<'a> {
6868
AnyEntKind::Overloaded(Overloaded::SubprogramDecl(Signature::new(formals, None)))
6969
}
7070

vhdl_lang/src/analysis/semantic.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,21 @@ impl Diagnostic {
223223
}
224224
}
225225
}
226+
227+
pub fn add_type_candididates<'a>(
228+
&mut self,
229+
prefix: &str,
230+
candidates: impl IntoIterator<Item = BaseType<'a>>,
231+
) {
232+
let mut candidates: Vec<_> = candidates.into_iter().collect();
233+
candidates.sort_by(|x, y| x.decl_pos().cmp(&y.decl_pos()));
234+
235+
for ent in candidates {
236+
if let Some(decl_pos) = ent.decl_pos() {
237+
self.add_related(decl_pos, format!("{} {}", prefix, ent.describe()))
238+
}
239+
}
240+
}
226241
}
227242

228243
impl<'a> AnyEnt<'a> {

vhdl_lang/src/analysis/sequential.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ impl<'a> AnalyzeContext<'a> {
7474
report,
7575
severity,
7676
} = assert_stmt;
77-
self.analyze_expression(scope, condition, diagnostics)?;
77+
self.boolean_expr(scope, condition, diagnostics)?;
7878
if let Some(expr) = report {
7979
self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?;
8080
}

vhdl_lang/src/analysis/tests/typecheck_expression.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,8 @@ begin
12371237
assert true report 16#bad#;
12381238
assert true report \"good\" severity error;
12391239
assert true report \"good\" severity \"bad\";
1240+
assert 123;
1241+
assert bit'('0');
12401242
end;
12411243
",
12421244
);
@@ -1253,6 +1255,120 @@ end;
12531255
code.s1("\"bad\""),
12541256
"string literal does not match type 'SEVERITY_LEVEL'",
12551257
),
1258+
Diagnostic::error(
1259+
code.s1("123"),
1260+
"type universal_integer cannot be implictly converted to type 'BOOLEAN'. Operator ?? is not defined for this type.",
1261+
),
12561262
],
12571263
);
12581264
}
1265+
1266+
#[test]
1267+
fn ambiguous_boolean_conversion_favors_boolean() {
1268+
let mut builder = LibraryBuilder::new();
1269+
let code = builder.in_declarative_region(
1270+
"
1271+
function myfun return boolean is
1272+
begin
1273+
return true;
1274+
end function;
1275+
1276+
function myfun return bit is
1277+
begin
1278+
return '0';
1279+
end function;
1280+
1281+
procedure wrapper is
1282+
begin
1283+
assert myfun;
1284+
end;
1285+
",
1286+
);
1287+
1288+
let (root, diagnostics) = builder.get_analyzed_root();
1289+
check_no_diagnostics(&diagnostics);
1290+
1291+
let decl = root
1292+
.search_reference(code.source(), code.s1("assert myfun;").s1("myfun").start())
1293+
.unwrap();
1294+
1295+
// Favors boolean
1296+
assert_eq!(
1297+
decl.decl_pos().unwrap(),
1298+
&code
1299+
.s1("function myfun return boolean is")
1300+
.s1("myfun")
1301+
.pos(),
1302+
);
1303+
}
1304+
1305+
#[test]
1306+
fn ambiguous_qq_conversion() {
1307+
let mut builder = LibraryBuilder::new();
1308+
let code = builder.in_declarative_region(
1309+
"
1310+
type typ1_t is (alpha, beta);
1311+
type typ2_t is (alpha, beta);
1312+
type typ3_t is (alpha, beta);
1313+
1314+
function \"??\"(val : typ1_t) return boolean is
1315+
begin
1316+
return true;
1317+
end function;
1318+
1319+
function \"??\"(val : typ2_t) return boolean is
1320+
begin
1321+
return true;
1322+
end function;
1323+
1324+
procedure wrapper is
1325+
begin
1326+
assert alpha;
1327+
end;
1328+
",
1329+
);
1330+
1331+
let diagnostics = builder.analyze();
1332+
check_diagnostics(
1333+
diagnostics,
1334+
vec![Diagnostic::error(
1335+
code.s1("assert alpha").s1("alpha"),
1336+
"Ambiguous use of implicit boolean conversion ??",
1337+
)
1338+
.related(code.s1("typ1_t"), "Could be type 'typ1_t'")
1339+
.related(code.s1("typ2_t"), "Could be type 'typ2_t'")],
1340+
);
1341+
}
1342+
1343+
#[test]
1344+
fn ambiguous_qq_conversion_no_candidates() {
1345+
let mut builder = LibraryBuilder::new();
1346+
let code = builder.in_declarative_region(
1347+
"
1348+
type typ1_t is (alpha, beta);
1349+
type typ2_t is (alpha, beta);
1350+
1351+
procedure wrapper is
1352+
begin
1353+
assert alpha;
1354+
end;
1355+
",
1356+
);
1357+
1358+
let diagnostics = builder.analyze();
1359+
check_diagnostics(
1360+
diagnostics,
1361+
vec![Diagnostic::error(
1362+
code.s1("assert alpha").s1("alpha"),
1363+
"Cannot disambiguate expression to type 'BOOLEAN'",
1364+
)
1365+
.related(
1366+
code.s1("typ1_t"),
1367+
"Implicit boolean conversion operator ?? is not defined for type 'typ1_t'",
1368+
)
1369+
.related(
1370+
code.s1("typ2_t"),
1371+
"Implicit boolean conversion operator ?? is not defined for type 'typ2_t'",
1372+
)],
1373+
);
1374+
}

0 commit comments

Comments
 (0)