Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(frontend): Resolve object types from method calls a single time #5131

Merged
merged 10 commits into from
May 29, 2024
Merged
118 changes: 72 additions & 46 deletions compiler/noirc_frontend/src/hir/type_check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use iter_extended::vecmap;
use noirc_errors::Span;

use crate::ast::{BinaryOpKind, IntegerBitSize, UnaryOp};
use crate::hir_def::expr::HirCallExpression;
use crate::macros_api::Signedness;
use crate::{
hir::{resolution::resolver::verify_mutable_reference, type_check::errors::Source},
Expand Down Expand Up @@ -176,56 +177,20 @@ impl<'interner> TypeChecker<'interner> {
}
HirExpression::Index(index_expr) => self.check_index_expression(expr_id, index_expr),
HirExpression::Call(call_expr) => {
// Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression
// These flags are later used to type check calls to unconstrained functions from constrained functions
let current_func = self.current_function;
let func_mod = current_func.map(|func| self.interner.function_modifiers(&func));
let is_current_func_constrained =
func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained);
let is_unconstrained_call = self.is_unconstrained_call(&call_expr.func);

self.check_if_deprecated(&call_expr.func);

let function = self.check_expression(&call_expr.func);

let args = vecmap(&call_expr.arguments, |arg| {
let typ = self.check_expression(arg);
(typ, *arg, self.interner.expr_span(arg))
});

// Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime
if is_current_func_constrained && is_unconstrained_call {
for (typ, _, _) in args.iter() {
if matches!(&typ.follow_bindings(), Type::MutableReference(_)) {
self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained {
span: self.interner.expr_span(expr_id),
});
return Type::Error;
}
}
}

let span = self.interner.expr_span(expr_id);
let return_type = self.bind_function_type(function, args, span);

// Check that we are not passing a slice from an unconstrained runtime to a constrained runtime
if is_current_func_constrained && is_unconstrained_call {
if return_type.contains_slice() {
self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained {
span: self.interner.expr_span(expr_id),
});
return Type::Error;
} else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) {
self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained {
span: self.interner.expr_span(expr_id),
});
return Type::Error;
}
};

return_type
self.check_call(&call_expr, function, args, span)
}
HirExpression::MethodCall(mut method_call) => {
let method_call_span = self.interner.expr_span(expr_id);
let object = method_call.object;
let object_span = self.interner.expr_span(&method_call.object);
let mut object_type = self.check_expression(&method_call.object).follow_bindings();
let method_name = method_call.method.0.contents.as_str();
match self.lookup_method(&object_type, method_name, expr_id) {
Expand Down Expand Up @@ -259,19 +224,42 @@ impl<'interner> TypeChecker<'interner> {
);
}

// These arguments will be given to the desugared function call.
// Compared to the method arguments, they also contain the object.
let mut function_args = Vec::with_capacity(method_call.arguments.len() + 1);

function_args.push((object_type.clone(), object, object_span));

for arg in method_call.arguments.iter() {
let span = self.interner.expr_span(arg);
let typ = self.check_expression(arg);
function_args.push((typ, *arg, span));
}

// TODO: update object_type here?
let (_, function_call) = method_call.into_function_call(
let ((function_id, _), function_call) = method_call.into_function_call(
&method_ref,
object_type,
location,
self.interner,
);

self.interner.replace_expr(expr_id, HirExpression::Call(function_call));
let func_type = self.check_expression(&function_id);

// Type check the new call now that it has been changed from a method call
// to a function call. This way we avoid duplicating code.
self.check_expression(expr_id)
// We call `check_call` rather than `check_expression` directly as we want to avoid
// resolving the object type again once it is part of the arguments.
let typ = self.check_call(
&function_call,
func_type,
function_args,
method_call_span,
);

self.interner.replace_expr(expr_id, HirExpression::Call(function_call));

typ
}
None => Type::Error,
}
Expand Down Expand Up @@ -333,6 +321,45 @@ impl<'interner> TypeChecker<'interner> {
typ
}

fn check_call(
&mut self,
call: &HirCallExpression,
func_type: Type,
args: Vec<(Type, ExprId, Span)>,
span: Span,
) -> Type {
// Need to setup these flags here as `self` is borrowed mutably to type check the rest of the call expression
// These flags are later used to type check calls to unconstrained functions from constrained functions
let func_mod = self.current_function.map(|func| self.interner.function_modifiers(&func));
let is_current_func_constrained =
func_mod.map_or(true, |func_mod| !func_mod.is_unconstrained);

let is_unconstrained_call = self.is_unconstrained_call(&call.func);
self.check_if_deprecated(&call.func);

// Check that we are not passing a mutable reference from a constrained runtime to an unconstrained runtime
if is_current_func_constrained && is_unconstrained_call {
for (typ, _, _) in args.iter() {
if matches!(&typ.follow_bindings(), Type::MutableReference(_)) {
self.errors.push(TypeCheckError::ConstrainedReferenceToUnconstrained { span });
}
}
}

let return_type = self.bind_function_type(func_type, args, span);

// Check that we are not passing a slice from an unconstrained runtime to a constrained runtime
if is_current_func_constrained && is_unconstrained_call {
if return_type.contains_slice() {
self.errors.push(TypeCheckError::UnconstrainedSliceReturnToConstrained { span });
} else if matches!(&return_type.follow_bindings(), Type::MutableReference(_)) {
self.errors.push(TypeCheckError::UnconstrainedReferenceToConstrained { span });
}
};

return_type
}

fn check_block(&mut self, block: HirBlockExpression) -> Type {
let mut block_type = Type::Unit;

Expand Down Expand Up @@ -416,9 +443,8 @@ impl<'interner> TypeChecker<'interner> {
// Push any trait constraints required by this definition to the context
// to be checked later when the type of this variable is further constrained.
if let Some(definition) = self.interner.try_definition(ident.id) {
if let DefinitionKind::Function(function) = definition.kind {
let function = self.interner.function_meta(&function);

if let DefinitionKind::Function(func_id) = definition.kind {
let function = self.interner.function_meta(&func_id);
for mut constraint in function.trait_constraints.clone() {
constraint.apply_bindings(&bindings);
self.trait_constraints.push((constraint, *expr_id));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "regression_5065_failure"
type = "bin"
authors = [""]
compiler_version = ">=0.30.0"

[dependencies]
40 changes: 40 additions & 0 deletions test_programs/compile_failure/regression_5065_failure/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
struct Wrapper<T> {
_value: T,
}

impl<T> Wrapper<T> {
fn new(value: T) -> Self {
Self { _value: value }
}

fn unwrap(self) -> T {
self._value
}
}

trait MyTrait {
fn new() -> Self;
}

struct MyType {}

impl MyTrait for MyType {
fn new() -> Self {
MyType {}
}
}

fn foo<T>() -> T where T: MyTrait {
MyTrait::new()
}

struct BadType {}

// Check that we get "No matching impl found for `BadType: MyTrait`"
fn concise_regression() -> BadType {
Wrapper::new(foo()).unwrap()
}

fn main() {
let _ = concise_regression();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "regression_5065"
type = "bin"
authors = [""]
compiler_version = ">=0.30.0"

[dependencies]
45 changes: 45 additions & 0 deletions test_programs/compile_success_empty/regression_5065/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
struct Wrapper<T> {
_value: T,
}

impl<T> Wrapper<T> {
fn new_wrapper(value: T) -> Self {
Self { _value: value }
}

fn unwrap(self) -> T {
self._value
}
}

trait MyTrait {
fn new() -> Self;
}

struct MyType {}

impl MyTrait for MyType {
fn new() -> Self {
MyType {}
}
}

fn foo<T>() -> T where T: MyTrait {
MyTrait::new()
}

// fn verbose_but_compiles() -> MyType {
// let a = Wrapper::new_wrapper(foo());
// a.unwrap()
// }

// Check that are able to infer the return type of the call to `foo`
fn concise_regression() -> MyType {
Wrapper::new_wrapper(foo()).unwrap()
// Wrapper::unwrap(Wrapper::new_wrapper(foo()))
}

fn main() {
// let _ = verbose_but_compiles();
let _ = concise_regression();
}
Loading