Skip to content

Commit

Permalink
Make 'ref Any' a valid type
Browse files Browse the repository at this point in the history
This is needed for the FFI, as calling functions requires a value of
type Any but without taking over ownership, as doing so could leak
values.

Changelog: added
  • Loading branch information
yorickpeterse committed Sep 26, 2022
1 parent ecafac6 commit 9698cda
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 27 deletions.
9 changes: 9 additions & 0 deletions compiler/src/mir/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3603,6 +3603,15 @@ impl<'a> LowerMethod<'a> {
return register;
}

// 'ref Any' is used to pass values through the FFI without giving up
// ownership. Because the value could be anything, we don't increment
// ref counts.
if let Some(exp) = expected {
if exp.is_ref_any(self.db()) {
return register;
}
}

if register_type.is_owned_or_uni(self.db()) {
match expected {
// Owned values passed to references are implicitly passed as
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,20 @@ impl<'a> DefineTypeSignature<'a> {
RefKind::Uni => TypeRef::UniSelf,
_ => TypeRef::RefSelf,
},
"Any" => {
if kind == RefKind::Owned {
TypeRef::Any
} else {
"Any" => match kind {
RefKind::Owned => TypeRef::Any,
RefKind::Ref | RefKind::Mut => TypeRef::RefAny,
RefKind::Uni => {
self.state.diagnostics.error(
DiagnosticId::InvalidType,
"'Any' can't be used as a reference",
"'uni Any' isn't a valid type",
self.file(),
node.location.clone(),
);

return TypeRef::Error;
}
}
},
_ => {
self.state.diagnostics.undefined_symbol(
name,
Expand Down
4 changes: 3 additions & 1 deletion docs/source/getting-started/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,9 @@ trait refers to the class that implements the trait.
managed by the Inko runtime such as a pointer to a C structure. Types can't be
cast _to_ an `Any` (nor are they compatible with `Any`), but you _can_ cast
`Any` to any other type. This is useful when working with Inko's FFI, but you
should avoid `Any` anywhere else.
should avoid `Any` anywhere else. `ref Any` is a variation of this type that
doesn't take ownership of a value. This type is used in the FFI for function
arguments.

`Never` is a type that indicates something never happens. When used in a return
or throw type it means a method never returns or throws. If a method doesn't
Expand Down
21 changes: 15 additions & 6 deletions libstd/src/std/ffi.inko
Original file line number Diff line number Diff line change
Expand Up @@ -166,33 +166,42 @@ class pub Function {
}

# Calls the function with a single argument.
fn pub call1(arg0: Any) -> Any {
fn pub call1(arg0: ref Any) -> Any {
_INKO.ffi_function_call(@func, arg0)
}

# Calls the function with two arguments.
fn pub call2(arg1: Any, arg2: Any) -> Any {
fn pub call2(arg1: ref Any, arg2: ref Any) -> Any {
_INKO.ffi_function_call(@func, arg1, arg2)
}

# Calls the function with three arguments.
fn pub call3(arg1: Any, arg2: Any, arg3: Any) -> Any {
fn pub call3(arg1: ref Any, arg2: ref Any, arg3: ref Any) -> Any {
_INKO.ffi_function_call(@func, arg1, arg2, arg3)
}

# Calls the function with four arguments.
fn pub call4(arg1: Any, arg2: Any, arg3: Any, arg4: Any) -> Any {
fn pub call4(
arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any
) -> Any {
_INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4)
}

# Calls the function with five arguments.
fn pub call5(arg1: Any, arg2: Any, arg3: Any, arg4: Any, arg5: Any) -> Any {
fn pub call5(
arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any, arg5: ref Any
) -> Any {
_INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5)
}

# Calls the function with six arguments.
fn pub call6(
arg1: Any, arg2: Any, arg3: Any, arg4: Any, arg5: Any, arg6: Any
arg1: ref Any,
arg2: ref Any,
arg3: ref Any,
arg4: ref Any,
arg5: ref Any,
arg6: ref Any,
) -> Any {
_INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5, arg6)
}
Expand Down
45 changes: 31 additions & 14 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3249,6 +3249,10 @@ pub enum TypeRef {
/// library isn't allowed.
Any,

/// A value that could be anything but shouldn't have its ownership
/// transferred.
RefAny,

/// The `Self` type.
OwnedSelf,

Expand Down Expand Up @@ -3415,14 +3419,24 @@ impl TypeRef {

pub fn is_any(self, db: &Database) -> bool {
match self {
TypeRef::Any => true,
TypeRef::Any | TypeRef::RefAny => true,
TypeRef::Placeholder(id) => {
id.value(db).map_or(false, |v| v.is_any(db))
}
_ => false,
}
}

pub fn is_ref_any(self, db: &Database) -> bool {
match self {
TypeRef::RefAny => true,
TypeRef::Placeholder(id) => {
id.value(db).map_or(false, |v| v.is_ref_any(db))
}
_ => false,
}
}

pub fn is_error(self, db: &Database) -> bool {
match self {
TypeRef::Error => true,
Expand Down Expand Up @@ -4037,18 +4051,12 @@ impl TypeRef {
with: TypeRef,
context: &mut TypeContext,
) -> bool {
// `Any` can be cast to anything, which although unsafe is needed to
// make parts of the standard library work.
if self == TypeRef::Any {
// Casting to/from Any is dangerous but necessary to make the standard
// library work.
if self == TypeRef::Any || with == TypeRef::Any {
return true;
}

// Explicitly casting to `Any` would allow one to then cast the result
// to any other type, which is very unsafe.
if with == TypeRef::Any {
return false;
}

self.type_check_directly(db, with, context, true)
}

Expand Down Expand Up @@ -4128,6 +4136,7 @@ impl TypeRef {
TypeRef::Owned(TypeId::Class(_)) => true,
TypeRef::Never => true,
TypeRef::Any => true,
TypeRef::RefAny => true,
TypeRef::Placeholder(id) => {
id.value(db).map_or(true, |v| v.is_permanent(db))
}
Expand Down Expand Up @@ -4332,7 +4341,7 @@ impl TypeRef {
TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => {
our_id.type_check(db, their_id, context, subtyping)
}
TypeRef::Any | TypeRef::Error => true,
TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true,
TypeRef::OwnedSelf => {
our_id.type_check(db, context.self_type, context, subtyping)
}
Expand All @@ -4344,7 +4353,7 @@ impl TypeRef {
| TypeRef::Uni(their_id) => {
our_id.type_check(db, their_id, context, subtyping)
}
TypeRef::Any | TypeRef::Error => true,
TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true,
TypeRef::UniSelf => {
our_id.type_check(db, context.self_type, context, subtyping)
}
Expand Down Expand Up @@ -4408,7 +4417,10 @@ impl TypeRef {
TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => context
.self_type
.type_check(db, their_id, context, subtyping),
TypeRef::Any | TypeRef::Error | TypeRef::OwnedSelf => true,
TypeRef::Any
| TypeRef::RefAny
| TypeRef::Error
| TypeRef::OwnedSelf => true,
_ => false,
},
TypeRef::RefSelf => match with {
Expand All @@ -4432,6 +4444,7 @@ impl TypeRef {
context.self_type.type_check(db, their_id, context, false)
}
TypeRef::Any
| TypeRef::RefAny
| TypeRef::Error
| TypeRef::UniSelf
| TypeRef::OwnedSelf => true,
Expand All @@ -4440,7 +4453,10 @@ impl TypeRef {
// Type errors are compatible with all other types to prevent a
// cascade of type errors.
TypeRef::Error => true,
TypeRef::Any => matches!(with, TypeRef::Any | TypeRef::Error),
TypeRef::Any => {
matches!(with, TypeRef::Any | TypeRef::RefAny | TypeRef::Error)
}
TypeRef::RefAny => matches!(with, TypeRef::RefAny | TypeRef::Error),
TypeRef::Placeholder(id) => {
if let Some(assigned) = id.value(db) {
return assigned.type_check(db, with, context, subtyping);
Expand Down Expand Up @@ -4608,6 +4624,7 @@ impl FormatType for TypeRef {
}
TypeRef::Never => buffer.write("Never"),
TypeRef::Any => buffer.write("Any"),
TypeRef::RefAny => buffer.write("ref Any"),
TypeRef::OwnedSelf => {
self.format_self_type(buffer);
}
Expand Down

0 comments on commit 9698cda

Please sign in to comment.