Skip to content

Commit

Permalink
Implements deref in reassignement of projections.
Browse files Browse the repository at this point in the history
Implements dereferencing as LHS in reassignments for projections.

With this PR we will support:
 - `(*array)[0] = 1`
 - `(*tuple).0 = 1`
 - `(*struct).a = 1`

This allow includes support for nested references.

Fixes #6397
  • Loading branch information
esdrubal committed Feb 19, 2025
1 parent d8854fa commit b72e89b
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 135 deletions.
11 changes: 11 additions & 0 deletions sway-ast/src/assignable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ pub enum ElementAccess {
field: BigUint,
field_span: Span,
},
Deref {
target: Box<ElementAccess>,
star_token: StarToken,
/// Multiple Derefs can be nested, this is true for the outer most Deref.
is_root_element: bool,
},
}

impl Spanned for Assignable {
Expand All @@ -64,6 +70,11 @@ impl Spanned for ElementAccess {
ElementAccess::TupleFieldProjection {
target, field_span, ..
} => Span::join(target.span(), field_span),
ElementAccess::Deref {
target,
star_token,
is_root_element: _,
} => Span::join(target.span(), &star_token.span()),
}
}
}
41 changes: 35 additions & 6 deletions sway-ast/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,17 +473,22 @@ impl Expr {
if let Expr::Deref { star_token, expr } = self {
Ok(Assignable::Deref { star_token, expr })
} else {
Ok(Assignable::ElementAccess(self.try_into_element_access()?))
Ok(Assignable::ElementAccess(
self.try_into_element_access(false)?,
))
}
}

fn try_into_element_access(self) -> Result<ElementAccess, Expr> {
match self {
fn try_into_element_access(
self,
accept_deref_without_parens: bool,
) -> Result<ElementAccess, Expr> {
match self.clone() {
Expr::Path(path_expr) => match path_expr.try_into_ident() {
Ok(name) => Ok(ElementAccess::Var(name)),
Err(path_expr) => Err(Expr::Path(path_expr)),
},
Expr::Index { target, arg } => match target.try_into_element_access() {
Expr::Index { target, arg } => match target.try_into_element_access(false) {
Ok(target) => Ok(ElementAccess::Index {
target: Box::new(target),
arg,
Expand All @@ -494,7 +499,7 @@ impl Expr {
target,
dot_token,
name,
} => match target.try_into_element_access() {
} => match target.try_into_element_access(false) {
Ok(target) => Ok(ElementAccess::FieldProjection {
target: Box::new(target),
dot_token,
Expand All @@ -507,7 +512,7 @@ impl Expr {
dot_token,
field,
field_span,
} => match target.try_into_element_access() {
} => match target.try_into_element_access(false) {
Ok(target) => Ok(ElementAccess::TupleFieldProjection {
target: Box::new(target),
dot_token,
Expand All @@ -516,6 +521,30 @@ impl Expr {
}),
error => error,
},
Expr::Parens(Parens { inner, .. }) => {
if let Expr::Deref { expr, star_token } = *inner {
match expr.try_into_element_access(true) {
Ok(target) => Ok(ElementAccess::Deref {
target: Box::new(target),
star_token,
is_root_element: true,
}),
error => error,
}
} else {
Err(self)
}
}
Expr::Deref { expr, star_token } if accept_deref_without_parens => {
match expr.try_into_element_access(true) {
Ok(target) => Ok(ElementAccess::Deref {
target: Box::new(target),
star_token,
is_root_element: false,
}),
error => error,
}
}
expr => Err(expr),
}
}
Expand Down
18 changes: 17 additions & 1 deletion sway-core/src/control_flow_analysis/dead_code_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2059,7 +2059,7 @@ fn connect_expression<'eng: 'cfg, 'cfg>(
}
}
}
ty::TyReassignmentTarget::Deref(exp) => {
ty::TyReassignmentTarget::DerefAccess { exp, indices } => {
connect_expression(
engines,
&exp.expression,
Expand All @@ -2071,6 +2071,22 @@ fn connect_expression<'eng: 'cfg, 'cfg>(
exp.span.clone(),
options,
)?;

for projection in indices {
if let ProjectionKind::ArrayIndex { index, index_span } = projection {
connect_expression(
engines,
&index.expression,
graph,
leaves,
exit_node,
"variable reassignment LHS array index",
tree_type,
index_span.clone(),
options,
)?;
}
}
}
};

Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ fn const_eval_typed_expr(
});
}
}
ty::TyReassignmentTarget::Deref(_) => {
ty::TyReassignmentTarget::DerefAccess { .. } => {
return Err(ConstEvalError::CannotBeEvaluatedToConst {
span: expr.span.clone(),
});
Expand Down
162 changes: 103 additions & 59 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3549,63 +3549,10 @@ impl<'eng> FnCompiler<'eng> {
// A non-aggregate; use a direct `store`.
lhs_val
} else {
// Create a GEP by following the chain of LHS indices. We use a scan which is
// essentially a map with context, which is the parent type id for the current field.
let mut gep_indices = Vec::<Value>::new();
let mut cur_type_id = *base_type;
// TODO-IG: Add support for projections being references themselves.
for idx_kind in indices.iter() {
let cur_type_info_arc = self.engines.te().get_unaliased(cur_type_id);
let cur_type_info = &*cur_type_info_arc;
match (idx_kind, cur_type_info) {
(
ProjectionKind::StructField { name: idx_name },
TypeInfo::Struct(decl_ref),
) => {
let struct_decl = self.engines.de().get_struct(decl_ref);

match struct_decl.get_field_index_and_type(idx_name) {
None => {
return Err(CompileError::InternalOwned(
format!(
"Unknown field name \"{idx_name}\" for struct \"{}\" \
in reassignment.",
struct_decl.call_path.suffix.as_str(),
),
idx_name.span(),
))
}
Some((field_idx, field_type_id)) => {
cur_type_id = field_type_id;
gep_indices
.push(Constant::get_uint(context, 64, field_idx));
}
}
}
(
ProjectionKind::TupleField { index, .. },
TypeInfo::Tuple(field_tys),
) => {
cur_type_id = field_tys[*index].type_id;
gep_indices.push(Constant::get_uint(context, 64, *index as u64));
}
(
ProjectionKind::ArrayIndex { index, .. },
TypeInfo::Array(elem_ty, _),
) => {
cur_type_id = elem_ty.type_id;
let val = return_on_termination_or_extract!(
self.compile_expression_to_value(context, md_mgr, index)?
);
gep_indices.push(val);
}
_ => {
return Err(CompileError::Internal(
"Unknown field in reassignment.",
idx_kind.span(),
))
}
}
let (terminator, gep_indices) =
self.compile_indices(context, md_mgr, *base_type, indices)?;
if let Some(terminator) = terminator {
return Ok(terminator);
}

// Using the type of the RHS for the GEP, rather than the final inner type of the
Expand All @@ -3625,7 +3572,10 @@ impl<'eng> FnCompiler<'eng> {
.add_metadatum(context, span_md_idx)
}
}
ty::TyReassignmentTarget::Deref(dereference_exp) => {
ty::TyReassignmentTarget::DerefAccess {
exp: dereference_exp,
indices,
} => {
let TyExpressionVariant::Deref(reference_exp) = &dereference_exp.expression else {
return Err(CompileError::Internal(
"Left-hand side of the reassignment must be dereferencing.",
Expand All @@ -3636,7 +3586,36 @@ impl<'eng> FnCompiler<'eng> {
let (ptr, _) =
self.compile_deref_up_to_ptr(context, md_mgr, reference_exp, span_md_idx)?;

return_on_termination_or_extract!(ptr)
if indices.is_empty() {
// A non-aggregate;
return_on_termination_or_extract!(ptr)
} else {
let (terminator, gep_indices) = self.compile_indices(
context,
md_mgr,
dereference_exp.return_type,
indices,
)?;
if let Some(terminator) = terminator {
return Ok(terminator);
}

// Using the type of the RHS for the GEP, rather than the final inner type of the
// aggregate, but getting the latter is a bit of a pain, though the `scan` above knew it.
// The program is type checked and the IR types on the LHS and RHS are the same.
let field_type = rhs.get_type(context).ok_or_else(|| {
CompileError::Internal(
"Failed to determine type of reassignment.",
dereference_exp.span.clone(),
)
})?;

// Create the GEP.
self.current_block
.append(context)
.get_elem_ptr(ptr.value, field_type, gep_indices)
.add_metadatum(context, span_md_idx)
}
}
};

Expand All @@ -3649,6 +3628,71 @@ impl<'eng> FnCompiler<'eng> {
Ok(TerminatorValue::new(val, context))
}

fn compile_indices(
&mut self,
context: &mut Context,
md_mgr: &mut MetadataManager,
base_type: TypeId,
indices: &[ProjectionKind],
) -> Result<(Option<TerminatorValue>, Vec<Value>), CompileError> {
// Create a GEP by following the chain of LHS indices. We use a scan which is
// essentially a map with context, which is the parent type id for the current field.
let mut gep_indices = Vec::<Value>::new();
let mut cur_type_id = base_type;
for idx_kind in indices.iter() {
while let TypeInfo::Ref {
referenced_type, ..
} = &*self.engines.te().get_unaliased(cur_type_id)
{
cur_type_id = referenced_type.type_id;
}
let cur_type_info_arc = self.engines.te().get_unaliased(cur_type_id);
let cur_type_info = &*cur_type_info_arc;
match (idx_kind, cur_type_info) {
(ProjectionKind::StructField { name: idx_name }, TypeInfo::Struct(decl_ref)) => {
let struct_decl = self.engines.de().get_struct(decl_ref);

match struct_decl.get_field_index_and_type(idx_name) {
None => {
return Err(CompileError::InternalOwned(
format!(
"Unknown field name \"{idx_name}\" for struct \"{}\" \
in reassignment.",
struct_decl.call_path.suffix.as_str(),
),
idx_name.span(),
))
}
Some((field_idx, field_type_id)) => {
cur_type_id = field_type_id;
gep_indices.push(Constant::get_uint(context, 64, field_idx));
}
}
}
(ProjectionKind::TupleField { index, .. }, TypeInfo::Tuple(field_tys)) => {
cur_type_id = field_tys[*index].type_id;
gep_indices.push(Constant::get_uint(context, 64, *index as u64));
}
(ProjectionKind::ArrayIndex { index, .. }, TypeInfo::Array(elem_ty, _)) => {
cur_type_id = elem_ty.type_id;
let val = self.compile_expression_to_value(context, md_mgr, index)?;
if val.is_terminator {
return Ok((Some(val), vec![]));
}
gep_indices.push(val.value);
}
_ => {
return Err(CompileError::Internal(
"Unknown field in reassignment.",
idx_kind.span(),
))
}
}
}

Ok((None, gep_indices))
}

fn compile_array_repeat_expr(
&mut self,
context: &mut Context,
Expand Down
20 changes: 19 additions & 1 deletion sway-core/src/language/ty/expression/expression_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1562,7 +1562,25 @@ impl DebugWithEngines for TyExpressionVariant {
TyExpressionVariant::Continue => "continue".to_string(),
TyExpressionVariant::Reassignment(reassignment) => {
let target = match &reassignment.lhs {
TyReassignmentTarget::Deref(exp) => format!("{:?}", engines.help_out(exp)),
TyReassignmentTarget::DerefAccess { exp, indices } => {
let mut target = format!("{:?}", engines.help_out(exp));
for index in indices {
match index {
ProjectionKind::StructField { name } => {
target.push('.');
target.push_str(name.as_str());
}
ProjectionKind::TupleField { index, .. } => {
target.push('.');
target.push_str(index.to_string().as_str());
}
ProjectionKind::ArrayIndex { index, .. } => {
write!(&mut target, "[{:?}]", engines.help_out(index)).unwrap();
}
}
}
target
}
TyReassignmentTarget::ElementAccess {
base_name,
base_type: _,
Expand Down
Loading

0 comments on commit b72e89b

Please sign in to comment.