Skip to content

Commit

Permalink
Support swapping of fields with explicit receivers
Browse files Browse the repository at this point in the history
This adds support for the expression `receiver.field := value`, in
addition to fixing a bug that would incorrectly allow recovering of
values returned by the swap operation. In addition, `inko fmt` now
formats field assignments/swaps when an explicit receiver is used.

This fixes #684 and fixes
#712.

Changelog: added
  • Loading branch information
yorickpeterse committed Aug 5, 2024
1 parent 7222fe4 commit 616c0e7
Show file tree
Hide file tree
Showing 12 changed files with 411 additions and 109 deletions.
16 changes: 16 additions & 0 deletions ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,20 @@ impl Node for AssignSetter {
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct ReplaceSetter {
pub receiver: Expression,
pub name: Identifier,
pub value: Expression,
pub location: SourceLocation,
}

impl Node for ReplaceSetter {
fn location(&self) -> &SourceLocation {
&self.location
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct BinaryAssignVariable {
pub operator: Operator,
Expand Down Expand Up @@ -712,6 +726,7 @@ pub enum Expression {
AssignField(Box<AssignField>),
ReplaceField(Box<ReplaceField>),
AssignSetter(Box<AssignSetter>),
ReplaceSetter(Box<ReplaceSetter>),
BinaryAssignVariable(Box<BinaryAssignVariable>),
BinaryAssignField(Box<BinaryAssignField>),
BinaryAssignSetter(Box<BinaryAssignSetter>),
Expand Down Expand Up @@ -788,6 +803,7 @@ impl Node for Expression {
Expression::AssignField(ref typ) => typ.location(),
Expression::ReplaceField(ref typ) => typ.location(),
Expression::AssignSetter(ref typ) => typ.location(),
Expression::ReplaceSetter(ref typ) => typ.location(),
Expression::AssignVariable(ref typ) => typ.location(),
Expression::ReplaceVariable(ref typ) => typ.location(),
Expression::Binary(ref typ) => typ.location(),
Expand Down
43 changes: 43 additions & 0 deletions ast/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,9 @@ impl Parser {
TokenKind::Assign => {
return self.assign_setter(receiver, name_token);
}
TokenKind::Replace => {
return self.replace_setter(receiver, name_token);
}
TokenKind::AddAssign => {
return self.binary_assign_setter(
receiver,
Expand Down Expand Up @@ -2147,6 +2150,27 @@ impl Parser {
})))
}

fn replace_setter(
&mut self,
receiver: Expression,
name_token: Token,
) -> Result<Expression, ParseError> {
self.next();

let name = Identifier::from(name_token);
let value_token = self.require()?;
let value = self.expression(value_token)?;
let location =
SourceLocation::start_end(receiver.location(), value.location());

Ok(Expression::ReplaceSetter(Box::new(ReplaceSetter {
receiver,
name,
value,
location,
})))
}

fn binary_assign_setter(
&mut self,
receiver: Expression,
Expand Down Expand Up @@ -7658,6 +7682,25 @@ mod tests {
}))
);

assert_eq!(
expr("10.foo := 20"),
Expression::ReplaceSetter(Box::new(ReplaceSetter {
receiver: Expression::Int(Box::new(IntLiteral {
value: "10".to_string(),
location: cols(1, 2)
})),
name: Identifier {
name: "foo".to_string(),
location: cols(4, 6)
},
value: Expression::Int(Box::new(IntLiteral {
value: "20".to_string(),
location: cols(11, 12)
})),
location: cols(1, 12)
}))
);

assert_eq!(
expr("10.foo += 20"),
Expression::BinaryAssignSetter(Box::new(BinaryAssignSetter {
Expand Down
53 changes: 36 additions & 17 deletions compiler/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,24 @@ impl Diagnostics {
);
}

pub(crate) fn immutable_receiver_for_assignment(
&mut self,
name: &str,
file: PathBuf,
location: SourceLocation,
) {
self.error(
DiagnosticId::InvalidAssign,
format!(
"can't assign a new value to field '{}', as its receiver \
is immutable",
name,
),
file,
location,
);
}

pub(crate) fn public_field_private_class(
&mut self,
file: PathBuf,
Expand Down Expand Up @@ -633,6 +651,24 @@ impl Diagnostics {
);
}

pub(crate) fn unsendable_old_value(
&mut self,
name: &str,
file: PathBuf,
location: SourceLocation,
) {
self.error(
DiagnosticId::InvalidAssign,
format!(
"the value of '{}' can't be replaced inside a 'recover', \
as the old value isn't sendable",
name
),
file,
location,
);
}

pub(crate) fn unsendable_async_type(
&mut self,
name: String,
Expand Down Expand Up @@ -781,23 +817,6 @@ impl Diagnostics {
);
}

pub(crate) fn cant_assign_type(
&mut self,
name: &str,
file: PathBuf,
location: SourceLocation,
) {
self.error(
DiagnosticId::InvalidType,
format!(
"values of type '{}' can't be assigned to variables or fields",
name
),
file,
location,
)
}

pub(crate) fn string_literal_too_large(
&mut self,
limit: usize,
Expand Down
59 changes: 47 additions & 12 deletions compiler/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,7 @@ impl Document {
Expression::AssignField(n) => self.assign_field(n),
Expression::ReplaceField(n) => self.replace_field(n),
Expression::AssignSetter(n) => self.assign_setter(n),
Expression::ReplaceSetter(n) => self.replace_setter(n),
Expression::BinaryAssignVariable(n) => {
self.binary_assign_variable(n)
}
Expand Down Expand Up @@ -1630,13 +1631,21 @@ impl Document {
}

fn assign_setter(&mut self, node: &nodes::AssignSetter) -> Node {
Node::Nodes(vec![
self.expression(&node.receiver),
Node::text("."),
Node::text(&node.name.name),
Node::text(" = "),
self.expression(&node.value),
])
self.field_with_receiver(
&node.receiver,
&node.name.name,
"=",
&node.value,
)
}

fn replace_setter(&mut self, node: &nodes::ReplaceSetter) -> Node {
self.field_with_receiver(
&node.receiver,
&node.name.name,
":=",
&node.value,
)
}

fn binary_assign_setter(
Expand All @@ -1645,12 +1654,38 @@ impl Document {
) -> Node {
let op = Operator::from_ast(node.operator.kind).method_name();

self.field_with_receiver(
&node.receiver,
&node.name.name,
&format!("{}=", op),
&node.value,
)
}

fn field_with_receiver(
&mut self,
receiver: &Expression,
name: &str,
operator: &str,
value: &Expression,
) -> Node {
let hid = self.new_group_id();
let head = vec![
self.expression(receiver),
Node::Line,
Node::Indent(vec![Node::text("."), Node::text(name)]),
];

let val = self.expression(value);

Node::Nodes(vec![
self.expression(&node.receiver),
Node::text("."),
Node::text(&node.name.name),
Node::text(&format!(" {}= ", op)),
self.expression(&node.value),
Node::Group(hid, head),
Node::Text(format!(" {} ", operator)),
Node::IfWrap(
hid,
Box::new(Node::IndentNext(vec![val.clone()])),
Box::new(val),
),
])
}

Expand Down
29 changes: 29 additions & 0 deletions compiler/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ pub(crate) struct AssignSetter {
pub(crate) expected_type: types::TypeRef,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ReplaceSetter {
pub(crate) field_id: Option<types::FieldId>,
pub(crate) receiver: Expression,
pub(crate) name: Identifier,
pub(crate) value: Expression,
pub(crate) location: SourceLocation,
pub(crate) resolved_type: types::TypeRef,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ImportSymbol {
pub(crate) name: Identifier,
Expand Down Expand Up @@ -501,6 +511,7 @@ pub(crate) enum Expression {
AssignField(Box<AssignField>),
ReplaceField(Box<ReplaceField>),
AssignSetter(Box<AssignSetter>),
ReplaceSetter(Box<ReplaceSetter>),
AssignVariable(Box<AssignVariable>),
ReplaceVariable(Box<ReplaceVariable>),
Break(Box<Break>),
Expand Down Expand Up @@ -541,6 +552,7 @@ impl Expression {
Expression::AssignField(ref n) => &n.location,
Expression::ReplaceField(ref n) => &n.location,
Expression::AssignSetter(ref n) => &n.location,
Expression::ReplaceSetter(ref n) => &n.location,
Expression::AssignVariable(ref n) => &n.location,
Expression::ReplaceVariable(ref n) => &n.location,
Expression::Break(ref n) => &n.location,
Expand Down Expand Up @@ -2297,6 +2309,9 @@ impl<'a> LowerToHir<'a> {
ast::Expression::AssignSetter(node) => {
Expression::AssignSetter(self.assign_setter(*node))
}
ast::Expression::ReplaceSetter(node) => {
Expression::ReplaceSetter(self.replace_setter(*node))
}
ast::Expression::BinaryAssignVariable(node) => {
Expression::AssignVariable(self.binary_assign_variable(*node))
}
Expand Down Expand Up @@ -2653,6 +2668,20 @@ impl<'a> LowerToHir<'a> {
})
}

fn replace_setter(
&mut self,
node: ast::ReplaceSetter,
) -> Box<ReplaceSetter> {
Box::new(ReplaceSetter {
field_id: None,
resolved_type: types::TypeRef::Unknown,
receiver: self.expression(node.receiver),
name: self.identifier(node.name),
value: self.expression(node.value),
location: node.location,
})
}

fn binary_assign_setter(
&mut self,
node: ast::BinaryAssignSetter,
Expand Down
17 changes: 15 additions & 2 deletions compiler/src/mir/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,7 @@ impl<'a> LowerMethod<'a> {
hir::Expression::AssignField(n) => self.assign_field(*n),
hir::Expression::ReplaceField(n) => self.replace_field(*n),
hir::Expression::AssignSetter(n) => self.assign_setter(*n),
hir::Expression::ReplaceSetter(n) => self.replace_setter(*n),
hir::Expression::AssignVariable(n) => self.assign_variable(*n),
hir::Expression::ReplaceVariable(n) => self.replace_variable(*n),
hir::Expression::Break(n) => self.break_expression(*n),
Expand Down Expand Up @@ -2163,6 +2164,20 @@ impl<'a> LowerMethod<'a> {
reg
}

fn replace_setter(&mut self, node: hir::ReplaceSetter) -> RegisterId {
let id = node.field_id.unwrap();
let loc = self.add_location(node.location);
let exp = node.resolved_type;
let new_val = self.input_expression(node.value, Some(exp));
let old_val = self.new_register(exp);
let rec = self.expression(node.receiver);
let class = self.register_type(rec).class_id(self.db()).unwrap();

self.current_block_mut().get_field(old_val, rec, class, id, loc);
self.current_block_mut().set_field(rec, class, id, new_val, loc);
old_val
}

fn assign_variable(&mut self, node: hir::AssignVariable) -> RegisterId {
let id = node.variable_id.unwrap();
let exp = id.value_type(self.db());
Expand Down Expand Up @@ -2283,13 +2298,11 @@ impl<'a> LowerMethod<'a> {
let exp = node.resolved_type;
let new_val = self.input_expression(node.value, Some(exp));
let old_val = self.new_register(exp);

let (rec, check_reg) = if let Some(&reg) = self.field_mapping.get(&id) {
(self.surrounding_type_register, reg)
} else {
(self.self_register, self.self_register)
};

let class = self.register_type(rec).class_id(self.db()).unwrap();

self.check_if_moved(check_reg, &node.field.name, &node.field.location);
Expand Down
Loading

0 comments on commit 616c0e7

Please sign in to comment.