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

feat(experimental): Enable ownership syntax #7603

Merged
merged 4 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion compiler/noirc_driver/src/abi_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType {
| Type::Slice(_)
| Type::Function(_, _, _, _) => unreachable!("{typ} cannot be used in the abi"),
Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"),
Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"),
Type::Reference(..) => unreachable!("references cannot be used in the abi"),
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
unary.location,
))
}
UnaryOp::MutableReference => {
UnaryOp::Reference { mutable: _ } => {
Ok(self.codegen_reference(&unary.rhs)?.map(|rhs| {
match rhs {
value::Value::Normal(value) => {
Expand Down Expand Up @@ -527,7 +527,7 @@
/// loop_entry(i: Field):
/// v2 = lt i v1
/// brif v2, then: loop_body, else: loop_end
/// loop_body():

Check warning on line 530 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// v3 = ... codegen body ...
/// v4 = add 1, i
/// br loop_entry(v4)
Expand Down Expand Up @@ -671,7 +671,7 @@
/// ```text
/// v0 = ... codegen cond ...
/// brif v0, then: then_block, else: else_block
/// then_block():

Check warning on line 674 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// v1 = ... codegen a ...
/// br end_if(v1)
/// else_block():
Expand All @@ -686,7 +686,7 @@
/// ```text
/// v0 = ... codegen cond ...
/// brif v0, then: then_block, else: end_if
/// then_block:

Check warning on line 689 in compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (brif)
/// v1 = ... codegen a ...
/// br end_if()
/// end_if: // No block parameter is needed. Without an else, the unit value is always returned.
Expand Down
7 changes: 5 additions & 2 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ impl BinaryOpKind {
pub enum UnaryOp {
Minus,
Not,
MutableReference,
Reference {
mutable: bool,
},

/// If implicitly_added is true, this operation was implicitly added by the compiler for a
/// field dereference. The compiler may undo some of these implicitly added dereferences if
Expand Down Expand Up @@ -732,7 +734,8 @@ impl Display for UnaryOp {
match self {
UnaryOp::Minus => write!(f, "-"),
UnaryOp::Not => write!(f, "!"),
UnaryOp::MutableReference => write!(f, "&mut"),
UnaryOp::Reference { mutable } if *mutable => write!(f, "&mut"),
UnaryOp::Reference { .. } => write!(f, "&"),
UnaryOp::Dereference { .. } => write!(f, "*"),
}
}
Expand Down
11 changes: 6 additions & 5 deletions compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@
/// A Trait as return type or parameter of function, including its generics
TraitAsType(Path, GenericTypeArgs),

/// &mut T
MutableReference(Box<UnresolvedType>),
/// &T and &mut T
Reference(Box<UnresolvedType>, /*mutable*/ bool),

// Note: Tuples have no visibility, instead each of their elements may have one.
Tuple(Vec<UnresolvedType>),
Expand Down Expand Up @@ -311,7 +311,8 @@
other => write!(f, "fn[{other}]({args}) -> {ret}"),
}
}
MutableReference(element) => write!(f, "&mut {element}"),
Reference(element, false) => write!(f, "&{element}"),
Reference(element, true) => write!(f, "&mut {element}"),
Quoted(quoted) => write!(f, "{}", quoted),
Unit => write!(f, "()"),
Error => write!(f, "error"),
Expand Down Expand Up @@ -346,7 +347,7 @@
impl UnresolvedType {
pub fn is_synthesized(&self) -> bool {
match &self.typ {
UnresolvedTypeData::MutableReference(ty) => ty.is_synthesized(),
UnresolvedTypeData::Reference(ty, _) => ty.is_synthesized(),
UnresolvedTypeData::Named(_, _, synthesized) => *synthesized,
_ => false,
}
Expand Down Expand Up @@ -424,7 +425,7 @@
path_is_wildcard || an_arg_is_unresolved
}
UnresolvedTypeData::TraitAsType(_path, args) => args.contains_unspecified(),
UnresolvedTypeData::MutableReference(typ) => typ.contains_unspecified(),
UnresolvedTypeData::Reference(typ, _) => typ.contains_unspecified(),
UnresolvedTypeData::Tuple(args) => args.iter().any(|arg| arg.contains_unspecified()),
UnresolvedTypeData::Function(args, ret, env, _unconstrained) => {
let args_contains_unspecified = args.iter().any(|arg| arg.contains_unspecified());
Expand Down Expand Up @@ -604,7 +605,7 @@
Self::Public => write!(f, "pub"),
Self::Private => write!(f, "priv"),
Self::CallData(id) => write!(f, "calldata{id}"),
Self::ReturnData => write!(f, "returndata"),

Check warning on line 608 in compiler/noirc_frontend/src/ast/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (returndata)
}
}
}
6 changes: 3 additions & 3 deletions compiler/noirc_frontend/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ pub trait Visitor {
true
}

fn visit_mutable_reference_type(&mut self, _: &UnresolvedType, _: Span) -> bool {
fn visit_reference_type(&mut self, _: &UnresolvedType, _mutable: bool, _: Span) -> bool {
true
}

Expand Down Expand Up @@ -1382,8 +1382,8 @@ impl UnresolvedType {
generic_type_args.accept(visitor);
}
}
UnresolvedTypeData::MutableReference(unresolved_type) => {
if visitor.visit_mutable_reference_type(unresolved_type, self.location.span) {
UnresolvedTypeData::Reference(unresolved_type, mutable) => {
if visitor.visit_reference_type(unresolved_type, *mutable, self.location.span) {
unresolved_type.accept(visitor);
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/elaborator/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@
| Type::NamedGeneric(_, _)
| Type::CheckedCast { .. }
| Type::Function(_, _, _, _)
| Type::MutableReference(_)
| Type::Reference(..)
| Type::Forall(_, _)
| Type::Constant(_, _)
| Type::Quoted(_)
Expand Down Expand Up @@ -967,7 +967,7 @@

/// Compiles the cases and fallback cases for integer and range patterns.
///
/// Integers have an infinite number of constructors, so we specialise the

Check warning on line 970 in compiler/noirc_frontend/src/elaborator/enums.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (specialise)
/// compilation of integer and range patterns.
fn compile_int_cases(
&mut self,
Expand Down
10 changes: 7 additions & 3 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::{
token::{FmtStrFragment, Tokens},
};

use super::{Elaborator, LambdaContext, UnsafeBlockStatus};
use super::{Elaborator, LambdaContext, UnsafeBlockStatus, UnstableFeature};

impl Elaborator<'_> {
pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) {
Expand Down Expand Up @@ -344,8 +344,12 @@ impl Elaborator<'_> {

let operator = prefix.operator;

if let UnaryOp::MutableReference = operator {
self.check_can_mutate(rhs, rhs_location);
if let UnaryOp::Reference { mutable } = operator {
if mutable {
self.check_can_mutate(rhs, rhs_location);
} else {
self.use_unstable_feature(UnstableFeature::Ownership, location);
}
}

let expr =
Expand Down
15 changes: 10 additions & 5 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ impl<'context> Elaborator<'context> {
for (mut constraint, expr_id, select_impl) in context.trait_constraints {
let location = self.interner.expr_location(&expr_id);

if matches!(&constraint.typ, Type::MutableReference(_)) {
if matches!(&constraint.typ, Type::Reference(..)) {
let (_, dereferenced_typ) =
self.insert_auto_dereferences(expr_id, constraint.typ.clone());
constraint.typ = dereferenced_typ;
Expand Down Expand Up @@ -1078,7 +1078,7 @@ impl<'context> Elaborator<'context> {
self.mark_type_as_used(from);
self.mark_type_as_used(to);
}
Type::MutableReference(typ) => {
Type::Reference(typ, _) => {
self.mark_type_as_used(typ);
}
Type::InfixExpr(left, _op, right, _) => {
Expand Down Expand Up @@ -1461,8 +1461,8 @@ impl<'context> Elaborator<'context> {
self.self_type = Some(self_type.clone());
let self_type_location = trait_impl.object_type.location;

if matches!(self_type, Type::MutableReference(_)) {
self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl {
if matches!(self_type, Type::Reference(..)) {
self.push_err(DefCollectorErrorKind::ReferenceInTraitImpl {
location: self_type_location,
});
}
Expand Down Expand Up @@ -1755,7 +1755,7 @@ impl<'context> Elaborator<'context> {
);
self.check_type_is_not_more_private_then_item(name, visibility, env, location);
}
Type::MutableReference(typ) | Type::Array(_, typ) | Type::Slice(typ) => {
Type::Reference(typ, _) | Type::Array(_, typ) | Type::Slice(typ) => {
self.check_type_is_not_more_private_then_item(name, visibility, typ, location);
}
Type::InfixExpr(left, _op, right, _) => {
Expand Down Expand Up @@ -2168,6 +2168,11 @@ impl<'context> Elaborator<'context> {
}
}

/// Return true if the given unstable feature is enabled
fn unstable_feature_enabled(&self, feature: UnstableFeature) -> bool {
self.options.enabled_unstable_features.contains(&feature)
}

/// Run the given function using the resolver and return true if any errors (not warnings)
/// occurred while running it.
pub fn errors_occurred_in<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T) {
Expand Down
6 changes: 3 additions & 3 deletions compiler/noirc_frontend/src/elaborator/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use std::str::FromStr;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum UnstableFeature {
Enums,
ArrayOwnership,
Ownership,
}

impl std::fmt::Display for UnstableFeature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Enums => write!(f, "enums"),
Self::ArrayOwnership => write!(f, "array-ownership"),
Self::Ownership => write!(f, "ownership"),
}
}
}
Expand All @@ -21,7 +21,7 @@ impl FromStr for UnstableFeature {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"enums" => Ok(Self::Enums),
"array-ownership" => Ok(Self::ArrayOwnership),
"ownership" => Ok(Self::Ownership),
other => Err(format!("Unknown unstable feature '{other}'")),
}
}
Expand Down
15 changes: 8 additions & 7 deletions compiler/noirc_frontend/src/elaborator/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,8 @@ impl Elaborator<'_> {
let (mut lvalue, mut lvalue_type, mut mutable) = self.elaborate_lvalue(*array);

// Before we check that the lvalue is an array, try to dereference it as many times
// as needed to unwrap any &mut wrappers.
while let Type::MutableReference(element) = lvalue_type.follow_bindings() {
// as needed to unwrap any `&` or `&mut` wrappers.
while let Type::Reference(element, _) = lvalue_type.follow_bindings() {
let element_type = element.as_ref().clone();
lvalue =
HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location };
Expand Down Expand Up @@ -482,7 +482,9 @@ impl Elaborator<'_> {
let lvalue = Box::new(lvalue);

let element_type = Type::type_variable(self.interner.next_type_variable_id());
let expected_type = Type::MutableReference(Box::new(element_type.clone()));

// Always expect a mutable reference here since we're storing to it
let expected_type = Type::Reference(Box::new(element_type.clone()), true);

self.unify(&reference_type, &expected_type, || TypeCheckError::TypeMismatch {
expected_typ: expected_type.to_string(),
Expand Down Expand Up @@ -539,9 +541,8 @@ impl Elaborator<'_> {
}
}
}
// If the lhs is a mutable reference we automatically transform
// lhs.field into (*lhs).field
Type::MutableReference(element) => {
// If the lhs is a reference we automatically transform `lhs.field` into `(*lhs).field`
Type::Reference(element, mutable) => {
if let Some(mut dereference_lhs) = dereference_lhs {
dereference_lhs(self, lhs_type.clone(), element.as_ref().clone());
return self.check_field_access(
Expand All @@ -553,7 +554,7 @@ impl Elaborator<'_> {
} else {
let (element, index) =
self.check_field_access(element, field_name, location, dereference_lhs)?;
return Some((Type::MutableReference(Box::new(element)), index));
return Some((Type::Reference(Box::new(element), *mutable), index));
}
}
_ => (),
Expand Down
57 changes: 39 additions & 18 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
Signedness, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType,
UnresolvedTypeData, UnresolvedTypeExpression, WILDCARD_TYPE,
},
elaborator::UnstableFeature,
hir::{
def_collector::dc_crate::CompilationError,
def_map::{ModuleDefId, fully_qualified_module_path},
Expand Down Expand Up @@ -141,8 +142,11 @@ impl Elaborator<'_> {
}
}
}
MutableReference(element) => {
Type::MutableReference(Box::new(self.resolve_type_inner(*element, kind)))
Reference(element, mutable) => {
if !mutable {
self.use_unstable_feature(UnstableFeature::Ownership, location);
}
Type::Reference(Box::new(self.resolve_type_inner(*element, kind)), mutable)
}
Parenthesized(typ) => self.resolve_type_inner(*typ, kind),
Resolved(id) => self.interner.get_quoted_type(id).clone(),
Expand Down Expand Up @@ -845,7 +849,7 @@ impl Elaborator<'_> {
/// Insert as many dereference operations as necessary to automatically dereference a method
/// call object to its base value type T.
pub(super) fn insert_auto_dereferences(&mut self, object: ExprId, typ: Type) -> (ExprId, Type) {
if let Type::MutableReference(element) = typ.follow_bindings() {
if let Type::Reference(element, _mut) = typ.follow_bindings() {
let location = self.interner.id_location(object);

let object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression {
Expand Down Expand Up @@ -1304,17 +1308,34 @@ impl Elaborator<'_> {
_ => Ok((rhs_type.clone(), true)),
}
}
crate::ast::UnaryOp::MutableReference => {
Ok((Type::MutableReference(Box::new(rhs_type.follow_bindings())), false))
crate::ast::UnaryOp::Reference { mutable } => {
let typ = Type::Reference(Box::new(rhs_type.follow_bindings()), *mutable);
Ok((typ, false))
}
crate::ast::UnaryOp::Dereference { implicitly_added: _ } => {
let element_type = self.interner.next_type_variable();
let expected = Type::MutableReference(Box::new(element_type.clone()));
self.unify(rhs_type, &expected, || TypeCheckError::TypeMismatch {
expr_typ: rhs_type.to_string(),
expected_typ: expected.to_string(),
expr_location: location,
});
let make_expected =
|mutable| Type::Reference(Box::new(element_type.clone()), mutable);

let immutable = make_expected(false);
let ownership_enabled = self.unstable_feature_enabled(UnstableFeature::Ownership);

// Both `&mut T` and `&T` should coerce to an expected `&T`.
if !rhs_type.try_reference_coercion(&immutable) {
self.unify(rhs_type, &immutable, || {
let expected_typ = if ownership_enabled {
immutable.to_string()
} else {
make_expected(true).to_string()
};

TypeCheckError::TypeMismatch {
expr_typ: rhs_type.to_string(),
expected_typ,
expr_location: location,
}
});
}
Ok((element_type, false))
}
}
Expand Down Expand Up @@ -1437,9 +1458,9 @@ impl Elaborator<'_> {
Type::NamedGeneric(_, _) => {
self.lookup_method_in_trait_constraints(object_type, method_name, location)
}
// Mutable references to another type should resolve to methods of their element type.
// References to another type should resolve to methods of their element type.
// This may be a data type or a primitive type.
Type::MutableReference(element) => {
Type::Reference(element, _mutable) => {
self.lookup_method(&element, method_name, location, check_self_param)
}

Expand Down Expand Up @@ -1836,12 +1857,12 @@ impl Elaborator<'_> {
if let Some(expected_object_type) = expected_object_type {
let actual_type = object_type.follow_bindings();

if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) {
if !matches!(actual_type, Type::MutableReference(_)) {
if let Type::Reference(_, mutable) = expected_object_type.follow_bindings() {
if !matches!(actual_type, Type::Reference(..)) {
let location = self.interner.id_location(*object);
self.check_can_mutate(*object, location);

let new_type = Type::MutableReference(Box::new(actual_type));
let new_type = Type::Reference(Box::new(actual_type), mutable);
*object_type = new_type.clone();

// First try to remove a dereference operator that may have been implicitly
Expand All @@ -1852,7 +1873,7 @@ impl Elaborator<'_> {
*object = new_object.unwrap_or_else(|| {
let new_object =
self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression {
operator: UnaryOp::MutableReference,
operator: UnaryOp::Reference { mutable },
rhs: *object,
trait_method_id: None,
}));
Expand All @@ -1863,7 +1884,7 @@ impl Elaborator<'_> {
}
// Otherwise if the object type is a mutable reference and the method is not, insert as
// many dereferences as needed.
} else if matches!(actual_type, Type::MutableReference(_)) {
} else if matches!(actual_type, Type::Reference(..)) {
let (new_object, new_type) = self.insert_auto_dereferences(*object, actual_type);
*object_type = new_type;
*object = new_object;
Expand Down
Loading
Loading