Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions compiler/noirc_frontend/src/elaborator/comptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use iter_extended::vecmap;
use noirc_errors::Location;

use crate::{
Type, TypeBindings, UnificationError,
Type, TypeBindings,
ast::{Documented, Expression, ExpressionKind},
hir::{
comptime::{Interpreter, InterpreterError, Value},
Expand Down Expand Up @@ -244,8 +244,7 @@ impl<'context> Elaborator<'context> {
function,
arguments,
location,
)
.map_err(CompilationError::from)?;
)?;

arguments.insert(0, (item, location));

Expand All @@ -271,7 +270,7 @@ impl<'context> Elaborator<'context> {
function: FuncId,
arguments: Vec<Expression>,
location: Location,
) -> Result<Vec<(Value, Location)>, InterpreterError> {
) -> Result<Vec<(Value, Location)>, CompilationError> {
let meta = interpreter.elaborator.interner.function_meta(&function);

let mut parameters = vecmap(&meta.parameters.0, |(_, typ, _)| typ.clone());
Expand All @@ -281,7 +280,8 @@ impl<'context> Elaborator<'context> {
expected: 0,
actual: arguments.len() + 1,
location,
});
}
.into());
}

let expected_type = item.get_type();
Expand All @@ -292,7 +292,8 @@ impl<'context> Elaborator<'context> {
expected: parameters[0].clone(),
actual: expected_type.clone(),
location,
});
}
.into());
}

// Remove the initial parameter for the comptime item since that is not included
Expand Down Expand Up @@ -336,13 +337,26 @@ impl<'context> Elaborator<'context> {
push_arg(Value::TraitDefinition(trait_id));
} else {
let (expr_id, expr_type) = interpreter.elaborator.elaborate_expression(arg);
if let Err(UnificationError) = expr_type.unify(param_type) {
return Err(InterpreterError::TypeMismatch {
expected: param_type.clone(),
actual: expr_type,
location: arg_location,
});
let mut errors = Vec::new();
expr_type.clone().unify_with_coercions(
param_type,
expr_id,
arg_location,
interpreter.elaborator.interner,
&mut errors,
|| {
CompilationError::InterpreterError(InterpreterError::TypeMismatch {
expected: param_type.clone(),
actual: expr_type,
location: arg_location,
})
},
);

if !errors.is_empty() {
return Err(errors.swap_remove(0));
}

push_arg(interpreter.evaluate(expr_id)?);
};
}
Expand Down
10 changes: 6 additions & 4 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,10 +963,12 @@ impl Elaborator<'_> {
expected_type,
resolved,
field_location,
|| TypeCheckError::TypeMismatch {
expected_typ: expected_type.to_string(),
expr_typ: field_type.to_string(),
expr_location: field_location,
|| {
CompilationError::TypeError(TypeCheckError::TypeMismatch {
expected_typ: expected_type.to_string(),
expr_typ: field_type.to_string(),
expr_location: field_location,
})
},
);
} else if seen_fields.contains(&field_name) {
Expand Down
9 changes: 5 additions & 4 deletions compiler/noirc_frontend/src/elaborator/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
ItemVisibility, LValue, LetStatement, Statement, StatementKind, WhileStatement,
},
hir::{
def_collector::dc_crate::CompilationError,
resolution::{
errors::ResolverError, import::PathResolutionError,
visibility::struct_member_is_visible,
Expand Down Expand Up @@ -119,11 +120,11 @@ impl Elaborator<'_> {
// Now check if LHS is the same type as the RHS
// Importantly, we do not coerce any types implicitly
self.unify_with_coercions(&expr_type, &annotated_type, expression, expr_location, || {
TypeCheckError::TypeMismatch {
CompilationError::TypeError(TypeCheckError::TypeMismatch {
expected_typ: annotated_type.to_string(),
expr_typ: expr_type.to_string(),
expr_location,
}
})
});

let warn_if_unused =
Expand Down Expand Up @@ -162,12 +163,12 @@ impl Elaborator<'_> {
}

self.unify_with_coercions(&expr_type, &lvalue_type, expression, expr_location, || {
TypeCheckError::TypeMismatchWithSource {
CompilationError::TypeError(TypeCheckError::TypeMismatchWithSource {
actual: expr_type.clone(),
expected: lvalue_type.clone(),
location: expr_location,
source: Source::Assignment,
}
})
});

let assign = HirAssignStatement { lvalue, expression };
Expand Down
10 changes: 5 additions & 5 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@
expected: &Type,
expression: ExprId,
location: Location,
make_error: impl FnOnce() -> TypeCheckError,
make_error: impl FnOnce() -> CompilationError,
) {
let mut errors = Vec::new();
actual.unify_with_coercions(
Expand All @@ -943,7 +943,7 @@
&mut errors,
make_error,
);
self.push_errors(errors.into_iter().map(|error| error.into()));
self.push_errors(errors);
}

/// Return a fresh integer or field type variable and log it
Expand Down Expand Up @@ -1045,11 +1045,11 @@

for (param, (arg, arg_expr_id, arg_location)) in fn_params.iter().zip(callsite_args) {
self.unify_with_coercions(arg, param, *arg_expr_id, *arg_location, || {
TypeCheckError::TypeMismatch {
CompilationError::TypeError(TypeCheckError::TypeMismatch {
expected_typ: param.to_string(),
expr_typ: arg.to_string(),
expr_location: *arg_location,
}
})
});
}

Expand Down Expand Up @@ -2113,7 +2113,7 @@
"implicitly returns `()` as its body has no tail or `return` expression",
);
}
error
CompilationError::TypeError(error)
},
);
}
Expand Down Expand Up @@ -2175,7 +2175,7 @@
let existing = existing.follow_bindings();
let new = binding.2.follow_bindings();

// Exact equality on types is intential here, we never want to

Check warning on line 2178 in compiler/noirc_frontend/src/elaborator/types.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (intential)
// overwrite even type variables but should probably avoid a panic if
// the types are exactly the same.
if existing != new {
Expand Down
66 changes: 51 additions & 15 deletions compiler/noirc_frontend/src/hir_def/types/unification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::borrow::Cow;
use noirc_errors::Location;

use crate::{
BinaryTypeOperator, Kind, Type, TypeBinding, TypeBindings, TypeVariable,
hir::type_check::TypeCheckError,
BinaryTypeOperator, Kind, QuotedType, Type, TypeBinding, TypeBindings, TypeVariable,
hir::{def_collector::dc_crate::CompilationError, type_check::TypeCheckError},
hir_def::{
expr::{HirCallExpression, HirExpression, HirIdent},
types,
Expand Down Expand Up @@ -504,8 +504,8 @@ impl Type {
expression: ExprId,
location: Location,
interner: &mut NodeInterner,
errors: &mut Vec<TypeCheckError>,
make_error: impl FnOnce() -> TypeCheckError,
errors: &mut Vec<CompilationError>,
make_error: impl FnOnce() -> CompilationError,
) {
let mut bindings = TypeBindings::default();

Expand All @@ -518,6 +518,10 @@ impl Type {
return;
}

if self.try_string_to_ctstring_coercion(expected, expression, interner) {
return;
}

if self.try_reference_coercion(expected) {
return;
}
Expand All @@ -531,7 +535,7 @@ impl Type {
);
}
FunctionCoercionResult::UnconstrainedMismatch(coerced_self) => {
errors.push(TypeCheckError::UnsafeFn { location });
errors.push(CompilationError::TypeError(TypeCheckError::UnsafeFn { location }));

coerced_self.unify_with_coercions(
expected, expression, location, interner, errors, make_error,
Expand Down Expand Up @@ -581,7 +585,7 @@ impl Type {
// Don't need to issue an error here if not, it will be done in unify_with_coercions
let mut bindings = TypeBindings::default();
if element1.try_unify(element2, &mut bindings).is_ok() {
convert_array_expression_to_slice(expression, this, target, as_slice, interner);
invoke_function_on_expression(expression, this, target, as_slice, interner);
Self::apply_type_bindings(bindings);
return true;
}
Expand All @@ -590,8 +594,40 @@ impl Type {
false
}

fn try_string_to_ctstring_coercion(
&self,
target: &Type,
expression: ExprId,
interner: &mut NodeInterner,
) -> bool {
let this = self.follow_bindings();
let target = target.follow_bindings();

let Type::Quoted(QuotedType::CtString) = &target else {
return false;
};

match &this {
Type::String(..) | Type::FmtString(..) => {
// as_ctstring is defined as a trait method
for (func_id, trait_id) in interner.lookup_trait_methods(&this, "as_ctstring", true)
{
// Look up the one that's in the standard library.
let trait_ = interner.get_trait(trait_id);
if trait_.crate_id.is_stdlib() && trait_.name.as_str() == "AsCtString" {
invoke_function_on_expression(expression, this, target, func_id, interner);
return true;
}
}
}
_ => (),
}

false
}

/// Attempt to coerce `&mut T` to `&T`, returning true if this is possible.
pub fn try_reference_coercion(&self, target: &Type) -> bool {
pub(crate) fn try_reference_coercion(&self, target: &Type) -> bool {
let this = self.follow_bindings();
let target = target.follow_bindings();

Expand All @@ -610,24 +646,24 @@ impl Type {
}
}

/// Wraps a given `expression` in `expression.as_slice()`
fn convert_array_expression_to_slice(
/// Wraps a given `expression` in `expression.method()`
fn invoke_function_on_expression(
expression: ExprId,
array_type: Type,
expression_type: Type,
target_type: Type,
as_slice_method: crate::node_interner::FuncId,
method: crate::node_interner::FuncId,
interner: &mut NodeInterner,
) {
let as_slice_id = interner.function_definition_id(as_slice_method);
let method_id = interner.function_definition_id(method);
let location = interner.expr_location(&expression);
let as_slice = HirExpression::Ident(HirIdent::non_trait_method(as_slice_id, location), None);
let as_slice = HirExpression::Ident(HirIdent::non_trait_method(method_id, location), None);
let func = interner.push_expr(as_slice);

// Copy the expression and give it a new ExprId. The old one
// will be mutated in place into a Call expression.
let argument = interner.expression(&expression);
let argument = interner.push_expr(argument);
interner.push_expr_type(argument, array_type.clone());
interner.push_expr_type(argument, expression_type.clone());
interner.push_expr_location(argument, location);

let arguments = vec![argument];
Expand All @@ -639,7 +675,7 @@ fn convert_array_expression_to_slice(
interner.push_expr_type(expression, target_type.clone());

let func_type =
Type::Function(vec![array_type], Box::new(target_type), Box::new(Type::Unit), false);
Type::Function(vec![expression_type], Box::new(target_type), Box::new(Type::Unit), false);
interner.push_expr_type(func, func_type);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "comptime_attribute_coercions"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#[foo([1, 2, 3], "hello", f"world")]
comptime fn foo(_: FunctionDefinition, slice: [u8], _str1: CtString, _str2: CtString) -> Quoted {
quote {
fn bar() -> [u8] {
$slice
}
}
}

// Make sure coercion also works in varargs
#[baz("hello", "world")]
#[varargs]
comptime fn baz(_f: FunctionDefinition, args: [CtString]) {
assert_eq(args.len(), 2);
}

fn main() {
assert_eq(bar(), &[1, 2, 3])
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading