diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 1f43bdf3ecd..58d019c86aa 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -1328,22 +1328,20 @@ impl<'context> Elaborator<'context> { if crossing_runtime_boundary { if !self.in_unsafe_block { self.push_err(TypeCheckError::Unsafe { span }); - return Type::Error; } - let called_func_id = self - .interner - .lookup_function_from_expr(&call.func) - .expect("Called function should exist"); - self.run_lint(|elaborator| { - lints::oracle_called_from_constrained_function( - elaborator.interner, - &called_func_id, - is_current_func_constrained, - span, - ) - .map(Into::into) - }); + if let Some(called_func_id) = self.interner.lookup_function_from_expr(&call.func) { + self.run_lint(|elaborator| { + lints::oracle_called_from_constrained_function( + elaborator.interner, + &called_func_id, + is_current_func_constrained, + span, + ) + .map(Into::into) + }); + } + let errors = lints::unconstrained_function_args(&args); for error in errors { self.push_err(error); diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 9cffebb0cee..e9d1dbb67f8 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -951,12 +951,12 @@ impl NodeInterner { /// Returns the [`FuncId`] corresponding to the function referred to by `expr_id` pub fn lookup_function_from_expr(&self, expr: &ExprId) -> Option { if let HirExpression::Ident(HirIdent { id, .. }, _) = self.expression(expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.try_definition(id).map(|def| &def.kind) - { - Some(*func_id) - } else { - None + match self.try_definition(id).map(|def| &def.kind) { + Some(DefinitionKind::Function(func_id)) => Some(*func_id), + Some(DefinitionKind::Local(Some(expr_id))) => { + self.lookup_function_from_expr(expr_id) + } + _ => None, } } else { None diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index ba39980a0ef..26a82216eee 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -2482,12 +2482,63 @@ fn cannot_call_unconstrained_first_class_function_outside_of_unsafe() { let src = r#" fn main() { let func = foo; + // Warning should trigger here func(); + inner(func); + } + + fn inner(x: unconstrained fn() -> ()) { + // Warning should trigger here + x(); } unconstrained fn foo() {} "#; let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + for error in &errors { + let CompilationError::TypeError(TypeCheckError::Unsafe { .. }) = &error.0 else { + panic!("Expected an 'unsafe' error, got {:?}", errors[0].0); + }; + } +} + +#[test] +fn missing_unsafe_block_when_needing_type_annotations() { + // This test is a regression check that even when an unsafe block is missing + // that we still appropriately continue type checking and infer type annotations. + let src = r#" + fn main() { + let z = BigNum { limbs: [2, 0, 0] }; + assert(z.__is_zero() == false); + } + + struct BigNum { + limbs: [u64; N], + } + + impl BigNum { + unconstrained fn __is_zero_impl(self) -> bool { + let mut result: bool = true; + for i in 0..N { + result = result & (self.limbs[i] == 0); + } + result + } + } + + trait BigNumTrait { + fn __is_zero(self) -> bool; + } + + impl BigNumTrait for BigNum { + fn __is_zero(self) -> bool { + self.__is_zero_impl() + } + } + "#; + let errors = get_program_errors(src); assert_eq!(errors.len(), 1); let CompilationError::TypeError(TypeCheckError::Unsafe { .. }) = &errors[0].0 else {