Skip to content

Commit 3cb8666

Browse files
committed
Move into lints.rs
1 parent 00c186b commit 3cb8666

File tree

2 files changed

+90
-92
lines changed

2 files changed

+90
-92
lines changed

compiler/noirc_frontend/src/elaborator/lints.rs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use crate::{
66
type_check::TypeCheckError,
77
},
88
hir_def::{
9-
expr::{HirExpression, HirIdent, HirLiteral},
9+
expr::{HirBlockExpression, HirExpression, HirIdent, HirLiteral},
1010
function::FuncMeta,
11+
stmt::HirStatement,
12+
},
13+
node_interner::{
14+
DefinitionId, DefinitionKind, ExprId, FuncId, FunctionModifiers, NodeInterner,
1115
},
12-
node_interner::NodeInterner,
13-
node_interner::{DefinitionKind, ExprId, FuncId, FunctionModifiers},
1416
Type,
1517
};
1618

@@ -264,3 +266,77 @@ pub(crate) fn overflowing_int(
264266
fn func_meta_name_ident(func: &FuncMeta, modifiers: &FunctionModifiers) -> Ident {
265267
Ident(Spanned::from(func.name.location.span, modifiers.name.clone()))
266268
}
269+
270+
/// Check that a recursive function *can* return without endlessly calling itself.
271+
pub(crate) fn unbounded_recursion<'a>(
272+
interner: &'a NodeInterner,
273+
func_id: FuncId,
274+
func_name: impl FnOnce() -> &'a str,
275+
func_span: Span,
276+
body_id: ExprId,
277+
) -> Option<ResolverError> {
278+
if !can_return_without_recursing(interner, func_id, body_id) {
279+
Some(ResolverError::UnconditionalRecursion {
280+
name: func_name().to_string(),
281+
span: func_span,
282+
})
283+
} else {
284+
None
285+
}
286+
}
287+
288+
/// Check if an expression will end up calling a specific function.
289+
fn can_return_without_recursing(interner: &NodeInterner, func_id: FuncId, expr_id: ExprId) -> bool {
290+
let check = |e| can_return_without_recursing(interner, func_id, e);
291+
292+
let check_block = |block: HirBlockExpression| {
293+
block.statements.iter().all(|stmt_id| match interner.statement(stmt_id) {
294+
HirStatement::Let(s) => check(s.expression),
295+
HirStatement::Assign(s) => check(s.expression),
296+
HirStatement::Expression(e) => check(e),
297+
HirStatement::Semi(e) => check(e),
298+
// Rust doesn't seem to check the for loop body (it's bounds might mean it's never called).
299+
HirStatement::For(e) => check(e.start_range) && check(e.end_range),
300+
HirStatement::Constrain(_)
301+
| HirStatement::Comptime(_)
302+
| HirStatement::Break
303+
| HirStatement::Continue
304+
| HirStatement::Error => true,
305+
})
306+
};
307+
308+
match interner.expression(&expr_id) {
309+
HirExpression::Ident(ident, _) => {
310+
if ident.id == DefinitionId::dummy_id() {
311+
return true;
312+
}
313+
let definition = interner.definition(ident.id);
314+
if let DefinitionKind::Function(id) = definition.kind {
315+
func_id != id
316+
} else {
317+
true
318+
}
319+
}
320+
HirExpression::Block(b) => check_block(b),
321+
HirExpression::Prefix(e) => check(e.rhs),
322+
HirExpression::Infix(e) => check(e.lhs) && check(e.rhs),
323+
HirExpression::Index(e) => check(e.collection) && check(e.index),
324+
HirExpression::MemberAccess(e) => check(e.lhs),
325+
HirExpression::Call(e) => check(e.func) && e.arguments.iter().cloned().all(check),
326+
HirExpression::MethodCall(e) => check(e.object) && e.arguments.iter().cloned().all(check),
327+
HirExpression::Cast(e) => check(e.lhs),
328+
HirExpression::If(e) => {
329+
check(e.condition) && (check(e.consequence) || e.alternative.map(check).unwrap_or(true))
330+
}
331+
HirExpression::Tuple(e) => e.iter().cloned().all(check),
332+
HirExpression::Unsafe(b) => check_block(b),
333+
// Rust doesn't check the lambda body (it might not be called).
334+
HirExpression::Lambda(_)
335+
| HirExpression::Literal(_)
336+
| HirExpression::Constructor(_)
337+
| HirExpression::Quote(_)
338+
| HirExpression::Unquote(_)
339+
| HirExpression::Comptime(_)
340+
| HirExpression::Error => true,
341+
}
342+
}

compiler/noirc_frontend/src/elaborator/mod.rs

Lines changed: 11 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@ use std::{
44
};
55

66
use crate::{
7-
ast::ItemVisibility,
8-
hir_def::{
9-
expr::{HirBlockExpression, HirExpression},
10-
stmt::HirStatement,
11-
traits::ResolvedTraitBound,
12-
},
13-
node_interner::DefinitionId,
14-
StructField, StructType, TypeBindings,
7+
ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, StructField, StructType, TypeBindings,
158
};
169
use crate::{
1710
ast::{
@@ -479,12 +472,16 @@ impl<'context> Elaborator<'context> {
479472

480473
// Check that the body can return without calling the function.
481474
if let FunctionKind::Normal | FunctionKind::Recursive = kind {
482-
self.check_for_unbounded_recursion(
483-
id,
484-
self.interner.definition_name(func_meta.name.id).to_string(),
485-
func_meta.name.location.span,
486-
hir_func.as_expr(),
487-
);
475+
self.run_lint(|elaborator| {
476+
lints::unbounded_recursion(
477+
elaborator.interner,
478+
id,
479+
|| elaborator.interner.definition_name(func_meta.name.id),
480+
func_meta.name.location.span,
481+
hir_func.as_expr(),
482+
)
483+
.map(Into::into)
484+
});
488485
}
489486

490487
let meta = self
@@ -1709,79 +1706,4 @@ impl<'context> Elaborator<'context> {
17091706
_ => true,
17101707
})
17111708
}
1712-
1713-
/// Check that a recursive function *can* return without endlessly calling itself.
1714-
fn check_for_unbounded_recursion(
1715-
&mut self,
1716-
func_id: FuncId,
1717-
func_name: String,
1718-
func_span: Span,
1719-
body_id: ExprId,
1720-
) {
1721-
if !self.can_return_without_recursing(func_id, body_id) {
1722-
self.push_err(CompilationError::ResolverError(ResolverError::UnconditionalRecursion {
1723-
name: func_name,
1724-
span: func_span,
1725-
}));
1726-
}
1727-
}
1728-
1729-
/// Check if an expression will end up calling a specific function.
1730-
fn can_return_without_recursing(&self, func_id: FuncId, expr_id: ExprId) -> bool {
1731-
let check = |e| self.can_return_without_recursing(func_id, e);
1732-
1733-
let check_block = |block: HirBlockExpression| {
1734-
block.statements.iter().all(|stmt_id| match self.interner.statement(stmt_id) {
1735-
HirStatement::Let(s) => check(s.expression),
1736-
HirStatement::Assign(s) => check(s.expression),
1737-
HirStatement::Expression(e) => check(e),
1738-
HirStatement::Semi(e) => check(e),
1739-
// Rust doesn't seem to check the for loop body (it's bounds might mean it's never called).
1740-
HirStatement::For(e) => check(e.start_range) && check(e.end_range),
1741-
HirStatement::Constrain(_)
1742-
| HirStatement::Comptime(_)
1743-
| HirStatement::Break
1744-
| HirStatement::Continue
1745-
| HirStatement::Error => true,
1746-
})
1747-
};
1748-
1749-
match self.interner.expression(&expr_id) {
1750-
HirExpression::Ident(ident, _) => {
1751-
if ident.id == DefinitionId::dummy_id() {
1752-
return true;
1753-
}
1754-
let definition = self.interner.definition(ident.id);
1755-
if let DefinitionKind::Function(id) = definition.kind {
1756-
func_id != id
1757-
} else {
1758-
true
1759-
}
1760-
}
1761-
HirExpression::Block(b) => check_block(b),
1762-
HirExpression::Prefix(e) => check(e.rhs),
1763-
HirExpression::Infix(e) => check(e.lhs) && check(e.rhs),
1764-
HirExpression::Index(e) => check(e.collection) && check(e.index),
1765-
HirExpression::MemberAccess(e) => check(e.lhs),
1766-
HirExpression::Call(e) => check(e.func) && e.arguments.iter().cloned().all(check),
1767-
HirExpression::MethodCall(e) => {
1768-
check(e.object) && e.arguments.iter().cloned().all(check)
1769-
}
1770-
HirExpression::Cast(e) => check(e.lhs),
1771-
HirExpression::If(e) => {
1772-
check(e.condition)
1773-
&& (check(e.consequence) || e.alternative.map(check).unwrap_or(true))
1774-
}
1775-
HirExpression::Tuple(e) => e.iter().cloned().all(check),
1776-
HirExpression::Unsafe(b) => check_block(b),
1777-
// Rust doesn't check the lambda body (it might not be called).
1778-
HirExpression::Lambda(_)
1779-
| HirExpression::Literal(_)
1780-
| HirExpression::Constructor(_)
1781-
| HirExpression::Quote(_)
1782-
| HirExpression::Unquote(_)
1783-
| HirExpression::Comptime(_)
1784-
| HirExpression::Error => true,
1785-
}
1786-
}
17871709
}

0 commit comments

Comments
 (0)