Skip to content

Commit

Permalink
Automatically clone references to value types
Browse files Browse the repository at this point in the history
Then references to value types are passed to owned values, they're now
cloned automatically. This makes it much easier to pass value types
around, regardless of whether they're owned values or references.

This in turn means methods no longer have to decide if they want to take
e.g. a `ref Int` or `Int`, as they can just specify `Int` as the type
and any `ref Int` passed is automatically cloned into a new `Int`.

This fixes https://gitlab.com/inko-lang/inko/-/issues/280

Changelog: added
  • Loading branch information
yorickpeterse committed Oct 9, 2022
1 parent 09d8178 commit 28706b5
Show file tree
Hide file tree
Showing 30 changed files with 239 additions and 173 deletions.
67 changes: 51 additions & 16 deletions compiler/src/mir/passes.rs
Expand Up @@ -1214,7 +1214,7 @@ impl<'a> LowerMethod<'a> {
let loc = self.add_location(node.location().clone());
let rets = node.returns_value();
let ret = if rets && !ignore_ret {
self.output_expression(node, false)
self.output_expression(node)
} else {
self.expression(node)
};
Expand Down Expand Up @@ -1334,7 +1334,7 @@ impl<'a> LowerMethod<'a> {
// this case we need to clone the value type, as the original
// value may still be in use after the body, hence the clone
// argument is set to `true`.
self.output_expression(n, true)
self.output_expression(n)
} else {
self.expression(n)
};
Expand Down Expand Up @@ -2228,7 +2228,7 @@ impl<'a> LowerMethod<'a> {
fn return_expression(&mut self, node: hir::Return) -> RegisterId {
let loc = self.add_location(node.location);
let reg = if let Some(value) = node.value {
self.output_expression(value, false)
self.output_expression(value)
} else {
self.get_nil(loc)
};
Expand All @@ -2242,7 +2242,7 @@ impl<'a> LowerMethod<'a> {

fn throw_expression(&mut self, node: hir::Throw) -> RegisterId {
let loc = self.add_location(node.location);
let reg = self.output_expression(node.value, false);
let reg = self.output_expression(node.value);

self.mark_register_as_moved(reg);
self.drop_all_registers();
Expand Down Expand Up @@ -2324,10 +2324,18 @@ impl<'a> LowerMethod<'a> {
) -> RegisterId {
let loc = self.add_location(location);
let val = self.expression(value);
let reg = self.new_register(value_type);

self.current_block_mut().increment(reg, val, loc);
reg
if value_type.is_value_type(self.db()) {
let reg = self.clone_value_type(val, value_type, false, loc);

self.mark_register_as_available(reg);
reg
} else {
let reg = self.new_register(value_type);

self.current_block_mut().increment(reg, val, loc);
reg
}
}

fn recover_expression(&mut self, node: hir::Recover) -> RegisterId {
Expand Down Expand Up @@ -2657,6 +2665,10 @@ impl<'a> LowerMethod<'a> {

self.mark_register_as_moved(reg);

if self.register_type(reg).is_permanent(self.db()) {
continue;
}

// If the value matched against is owned, it's destructured
// as part of the match. This means any fields ignored need
// to be dropped. If the match input is a reference no
Expand Down Expand Up @@ -2741,6 +2753,10 @@ impl<'a> LowerMethod<'a> {

self.mark_register_as_moved(reg);

if self.register_type(reg).is_permanent(self.db()) {
continue;
}

// If the register is the match input register, we always need to
// drop it. If the input is a reference, we don't want to drop
// unbound intermediate registers, because they were never
Expand Down Expand Up @@ -3424,20 +3440,18 @@ impl<'a> LowerMethod<'a> {

/// Returns the register to use for an output expression (`return` or
/// `throw`).
fn output_expression(
&mut self,
node: hir::Expression,
clone_value_type: bool,
) -> RegisterId {
fn output_expression(&mut self, node: hir::Expression) -> RegisterId {
let loc = self.add_location(node.location().clone());
let reg = self.expression(node);

self.check_field_move(reg, loc);

let typ = self.register_type(reg);

if clone_value_type && typ.is_value_type(self.db()) {
return self.clone_value_type(reg, typ, loc);
if typ.is_value_type(self.db()) {
let force_clone = !typ.is_owned_or_uni(self.db());

return self.clone_value_type(reg, typ, force_clone, loc);
}

if typ.is_owned_or_uni(self.db()) {
Expand Down Expand Up @@ -3564,7 +3578,7 @@ impl<'a> LowerMethod<'a> {
let typ = self.register_type(register);

if typ.is_value_type(self.db()) {
return self.clone_value_type(register, typ, location);
return self.clone_value_type(register, typ, false, location);
}

self.check_field_move(register, location);
Expand Down Expand Up @@ -3628,6 +3642,7 @@ impl<'a> LowerMethod<'a> {
return self.clone_value_type(
register,
register_type,
false,
location,
);
}
Expand All @@ -3643,6 +3658,25 @@ impl<'a> LowerMethod<'a> {
return register;
}

// References of value types passed to owned values should be cloned.
// This allows passing e.g. `ref Int` to something that expects `Int`,
// without the need for an explicit clone.
if register_type.is_value_type(self.db())
&& expected.map_or(false, |v| v.is_owned_or_uni(self.db()))
{
// In this case we force cloning, so expressions such as
// `foo(vals[0])` pass a clone instead of passing the returned ref
// directly. If we were to pass the ref directly, `foo` might drop
// it thinking its an owned value, then fail because a ref still
// exists.
return self.clone_value_type(
register,
register_type,
true,
location,
);
}

// For reference types we only need to increment if they originate from
// a variable or field, as regular registers can't be referred to more
// than once.
Expand Down Expand Up @@ -3676,9 +3710,10 @@ impl<'a> LowerMethod<'a> {
&mut self,
source: RegisterId,
typ: types::TypeRef,
force_clone: bool,
location: LocationId,
) -> RegisterId {
if self.register_kind(source).is_regular() {
if self.register_kind(source).is_regular() && !force_clone {
self.mark_register_as_moved(source);

// Value types not bound to any variables/fields don't need to be
Expand Down
23 changes: 19 additions & 4 deletions compiler/src/type_check/expressions.rs
Expand Up @@ -1279,7 +1279,10 @@ impl<'a> CheckMethodBody<'a> {
nodes: &mut [hir::Expression],
scope: &mut LexicalScope,
) -> TypeRef {
self.expressions(nodes, scope).pop().unwrap_or_else(TypeRef::nil)
self.expressions(nodes, scope)
.pop()
.unwrap_or_else(TypeRef::nil)
.value_type_as_owned(self.db())
}

fn expressions_with_return(
Expand Down Expand Up @@ -3225,7 +3228,12 @@ impl<'a> CheckMethodBody<'a> {
);
}

node.resolved_type = expr.as_ref(self.db());
node.resolved_type = if expr.is_value_type(self.db()) {
expr
} else {
expr.as_ref(self.db())
};

node.resolved_type
}

Expand All @@ -3250,7 +3258,12 @@ impl<'a> CheckMethodBody<'a> {
return TypeRef::Error;
}

node.resolved_type = expr.as_mut(self.db());
node.resolved_type = if expr.is_value_type(self.db()) {
expr
} else {
expr.as_mut(self.db())
};

node.resolved_type
}

Expand Down Expand Up @@ -3637,7 +3650,7 @@ impl<'a> CheckMethodBody<'a> {
ins.type_arguments(db)
.copy_into(&mut ctx.type_arguments);

let returns = if raw_typ.is_owned_or_uni(db) {
let mut returns = if raw_typ.is_owned_or_uni(db) {
let typ = raw_typ.inferred(db, &mut ctx, false);

if receiver.is_ref(db) {
Expand All @@ -3649,6 +3662,8 @@ impl<'a> CheckMethodBody<'a> {
raw_typ.inferred(db, &mut ctx, receiver.is_ref(db))
};

returns = returns.value_type_as_owned(self.db());

if receiver.require_sendable_arguments(self.db())
&& !returns.is_sendable(self.db())
{
Expand Down
8 changes: 4 additions & 4 deletions libstd/src/std/byte_array.inko
Expand Up @@ -280,7 +280,7 @@ impl Drop for ByteArray {
}
}

impl Index[ref Int, Int] for ByteArray {
impl Index[Int, Int] for ByteArray {
# Returns the byte at the given index.
#
# # Examples
Expand All @@ -294,13 +294,13 @@ impl Index[ref Int, Int] for ByteArray {
# # Panics
#
# This method panics if the index is out of bounds.
fn pub index(index: ref Int) -> Int {
fn pub index(index: Int) -> Int {
bounds_check(index, length)
_INKO.byte_array_get(self, index)
}
}

impl SetIndex[ref Int, Int] for ByteArray {
impl SetIndex[Int, Int] for ByteArray {
# Stores a byte at the given index, then returns it.
#
# # Examples
Expand All @@ -315,7 +315,7 @@ impl SetIndex[ref Int, Int] for ByteArray {
# # Panics
#
# This method panics if the index is out of bounds.
fn pub mut set_index(index: ref Int, value: Int) {
fn pub mut set_index(index: Int, value: Int) {
bounds_check(index, length)
_INKO.byte_array_set(self, index, value)
_INKO.moved(value)
Expand Down
2 changes: 1 addition & 1 deletion libstd/src/std/debug.inko
Expand Up @@ -23,7 +23,7 @@ class pub StackFrame {

impl Clone for StackFrame {
fn pub clone -> Self {
Self { @path = @path.clone, @name = @name.clone, @line = @line.clone }
Self { @path = @path.clone, @name = @name, @line = @line }
}
}

Expand Down
2 changes: 1 addition & 1 deletion libstd/src/std/env.inko
Expand Up @@ -118,7 +118,7 @@ fn pub working_directory !! Error -> Path {
# import std::env
#
# try! env.working_directory = '..'
fn pub working_directory=(directory: ref String) !! Error {
fn pub working_directory=(directory: String) !! Error {
try {
_INKO.env_set_working_directory(directory)
} else (e) {
Expand Down
42 changes: 14 additions & 28 deletions libstd/src/std/ffi.inko
Expand Up @@ -258,12 +258,12 @@ class pub Struct {

# Returns the size in bytes of this `Struct`.
fn pub size -> Int {
@layout.size.clone
@layout.size
}

# Returns the alignment of the members in this `Struct`.
fn pub alignment -> Int {
@layout.alignment.clone
@layout.alignment
}
}

Expand All @@ -274,7 +274,7 @@ impl Index[String, Any] for Struct {
fn pub index(index: String) -> Any {
let member = @layout.members[index]

@pointer.read(type: member.type.clone, offset: member.offset.clone)
@pointer.read(type: member.type.clone, offset: member.offset)
}
}

Expand All @@ -283,7 +283,7 @@ impl SetIndex[String, Any] for Struct {
fn pub mut set_index(index: String, value: Any) {
let member = @layout.members[index]
let type = member.type.clone
let offset = member.offset.clone
let offset = member.offset

@pointer.write(type, offset, value)
}
Expand Down Expand Up @@ -336,7 +336,7 @@ class pub LayoutBuilder {
@alignment = value.alignment
}

@existing.insert(name.clone)
@existing.insert(name)
@members.push(name)
@types.push(value)
}
Expand All @@ -354,7 +354,7 @@ class pub LayoutBuilder {
let members = Map.new
let mut size = 0
let mut offset = 0
let mut remaining_in_hole = @alignment.clone
let mut remaining_in_hole = @alignment
let mut index = 0

while index < @members.length {
Expand All @@ -370,30 +370,23 @@ class pub LayoutBuilder {
size += padding
offset += padding

remaining_in_hole = @alignment.clone
remaining_in_hole = @alignment
}

members[name.clone] = Member {
@name = name.clone,
@type = type.clone,
@offset = offset.clone
}
members[name] =
Member { @name = name, @type = type.clone, @offset = offset }

remaining_in_hole -= type_align

size += type_align
offset += type_align

if remaining_in_hole == 0 { remaining_in_hole = @alignment.clone }
if remaining_in_hole == 0 { remaining_in_hole = @alignment }

index += 1
}

Layout {
@members = members,
@alignment = @alignment.clone,
@size = size
}
Layout { @members = members, @alignment = @alignment, @size = size }
}

fn move into_layout_without_padding -> Layout {
Expand All @@ -405,21 +398,14 @@ class pub LayoutBuilder {
let name = @members[index]
let type = @types[index]

members[name.clone] = Member {
@name = name.clone,
@type = type.clone,
@offset = offset.clone
}
members[name] =
Member { @name = name, @type = type.clone, @offset = offset }

offset += type.alignment
index += 1
}

Layout {
@members = members,
@alignment = @alignment.clone,
@size = offset
}
Layout { @members = members, @alignment = @alignment, @size = offset }
}
}

Expand Down
2 changes: 1 addition & 1 deletion libstd/src/std/float.inko
Expand Up @@ -60,7 +60,7 @@ class builtin Float {
# Parsing a `Float` with an exponent:
#
# Float.parse('1.2e1') # => Option.Some(12.0)
fn pub static parse(string: ref String) -> Option[Self] {
fn pub static parse(string: String) -> Option[Self] {
let res = _INKO.string_to_float(string, -1, -1)

if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) }
Expand Down

0 comments on commit 28706b5

Please sign in to comment.