Skip to content

Commit ed71032

Browse files
authored
Check that non-void functions always return (#2434)
1 parent 91c296d commit ed71032

File tree

3 files changed

+454
-3
lines changed

3 files changed

+454
-3
lines changed

compiler/qsc_qasm/src/semantic/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ pub enum SemanticErrorKind {
160160
#[error("control counts must be postitive integers")]
161161
#[diagnostic(code("Qasm.Lowerer.NegativeControlCount"))]
162162
NegativeControlCount(#[label] Span),
163+
#[error("non-void def should always return")]
164+
#[diagnostic(code("Qasm.Lowerer.NonVoidDefShouldAlwaysReturn"))]
165+
NonVoidDefShouldAlwaysReturn(#[label] Span),
163166
#[error("{0} are not supported")]
164167
#[diagnostic(code("Qasm.Lowerer.NotSupported"))]
165168
NotSupported(String, #[label] Span),

compiler/qsc_qasm/src/semantic/lowerer.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,10 @@ impl Lowerer {
13201320
// Pop the scope where the def lives.
13211321
self.symbols.pop_scope();
13221322

1323+
if let Some(return_ty) = &stmt.return_type {
1324+
self.check_that_def_returns_in_all_code_paths(&body, return_ty.span);
1325+
}
1326+
13231327
semantic::StmtKind::Def(semantic::DefStmt {
13241328
span: stmt.span,
13251329
symbol_id,
@@ -1330,6 +1334,53 @@ impl Lowerer {
13301334
})
13311335
}
13321336

1337+
fn check_that_def_returns_in_all_code_paths(&mut self, body: &semantic::Block, span: Span) {
1338+
if !Self::block_always_returns(&body.stmts) {
1339+
self.push_semantic_error(SemanticErrorKind::NonVoidDefShouldAlwaysReturn(span));
1340+
}
1341+
}
1342+
1343+
fn block_always_returns<'a>(stmts: impl IntoIterator<Item = &'a Box<semantic::Stmt>>) -> bool {
1344+
for stmt in stmts {
1345+
if Self::stmt_always_returns(stmt) {
1346+
return true;
1347+
}
1348+
}
1349+
false
1350+
}
1351+
1352+
fn stmt_always_returns(stmt: &semantic::Stmt) -> bool {
1353+
match &*stmt.kind {
1354+
semantic::StmtKind::Block(block) => Self::block_always_returns(&block.stmts),
1355+
semantic::StmtKind::Box(stmt) => Self::block_always_returns(&stmt.body),
1356+
semantic::StmtKind::If(stmt) => {
1357+
if let Some(else_body) = &stmt.else_body {
1358+
Self::stmt_always_returns(&stmt.if_body) && Self::stmt_always_returns(else_body)
1359+
} else {
1360+
false
1361+
}
1362+
}
1363+
// We don't know if the user's switch is exhaustive.
1364+
// We take a best effort approach and check if all the branches always return.
1365+
semantic::StmtKind::Switch(stmt) => {
1366+
let mut all_cases_return = true;
1367+
for case in &stmt.cases {
1368+
all_cases_return &= Self::block_always_returns(&case.block.stmts);
1369+
}
1370+
if let Some(default_case) = &stmt.default {
1371+
all_cases_return &= Self::block_always_returns(&default_case.stmts);
1372+
}
1373+
all_cases_return
1374+
}
1375+
// We don't know if the iterable of the loop is empty at compiletime.
1376+
// We take a best effort approach and check if the body always returns.
1377+
semantic::StmtKind::For(stmt) => Self::stmt_always_returns(&stmt.body),
1378+
semantic::StmtKind::WhileLoop(stmt) => Self::stmt_always_returns(&stmt.body),
1379+
semantic::StmtKind::Return(..) | semantic::StmtKind::End(..) => true,
1380+
_ => false,
1381+
}
1382+
}
1383+
13331384
fn lower_typed_parameter(&mut self, typed_param: &syntax::TypedParameter) -> Symbol {
13341385
match typed_param {
13351386
syntax::TypedParameter::ArrayReference(param) => {
@@ -2233,7 +2284,6 @@ impl Lowerer {
22332284
self.push_semantic_error(
22342285
SemanticErrorKind::MissingTargetExpressionInReturnStmt(stmt.span),
22352286
);
2236-
return semantic::StmtKind::Err;
22372287
}
22382288
}
22392289
(Some(expr), Some(ty)) => {

0 commit comments

Comments
 (0)