Skip to content

Commit

Permalink
Allow passing uni ref T to ref T in certain cases
Browse files Browse the repository at this point in the history
This allows passing values of type `uni ref T` to arguments of type `ref
T`, provided no aliases to the unique reference/borrow can be created.
This makes it possible to recover values into `uni T` when using methods
that take immutable references to do so, something that was previously
not possible.

This fixes #570.

Changelog: added
  • Loading branch information
yorickpeterse committed Jun 7, 2023
1 parent c3f3385 commit be0d830
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 16 deletions.
27 changes: 16 additions & 11 deletions compiler/src/type_check/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ impl MethodCall {
) {
let given = argument.cast_according_to(expected, &state.db);

if self.require_sendable {
if self.require_sendable || given.is_uni_ref(&state.db) {
self.check_sendable.push((given, location.clone()));
}

Expand All @@ -463,7 +463,7 @@ impl MethodCall {
}

fn check_sendable(&mut self, state: &mut State, location: &SourceLocation) {
if !self.require_sendable {
if self.check_sendable.is_empty() {
return;
}

Expand All @@ -472,12 +472,12 @@ impl MethodCall {
// thus violating the uniqueness constraints.
let ref_safe = self.method.is_immutable(&state.db)
&& self.check_sendable.iter().all(|(typ, _)| {
typ.is_sendable(&state.db) || typ.is_ref(&state.db)
typ.is_sendable(&state.db) || typ.is_sendable_ref(&state.db)
});

for (given, loc) in &self.check_sendable {
if given.is_sendable(&state.db)
|| (given.is_ref(&state.db) && ref_safe)
|| (given.is_sendable_ref(&state.db) && ref_safe)
{
continue;
}
Expand Down Expand Up @@ -1471,13 +1471,18 @@ impl<'a> CheckMethodBody<'a> {

// Since other types aren't compatible with Any, we only need to check
// the first value's type.
if !types.is_empty() && types[0].is_any(self.db()) {
self.state.diagnostics.error(
DiagnosticId::InvalidType,
"Arrays can't store values of type 'Any'",
self.file(),
node.location.clone(),
);
if let Some(&first) = types.get(0) {
if !first.allow_in_array(self.db()) {
self.state.diagnostics.error(
DiagnosticId::InvalidType,
format!(
"Values of type '{}' can't be stored in an Array",
format_type(self.db(), first)
),
self.file(),
node.location.clone(),
);
}
}

let ins =
Expand Down
12 changes: 11 additions & 1 deletion types/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ impl<'a> TypeChecker<'a> {
_ => false,
},
TypeRef::UniRef(left_id) => match right {
TypeRef::UniRef(right_id) => {
TypeRef::UniRef(right_id) | TypeRef::Ref(right_id) => {
self.check_type_id(left_id, right_id, env, rules)
}
TypeRef::Error => true,
Expand Down Expand Up @@ -1526,6 +1526,11 @@ mod tests {
TypeRef::UniRef(instance(thing)),
TypeRef::UniRef(instance(thing)),
);
check_ok(
&db,
TypeRef::UniRef(instance(thing)),
TypeRef::Ref(instance(thing)),
);
check_ok(&db, TypeRef::UniRef(instance(thing)), TypeRef::Error);

check_err(
Expand Down Expand Up @@ -1555,6 +1560,11 @@ mod tests {
TypeRef::UniMut(instance(thing)),
TypeRef::UniRef(instance(thing)),
);
check_err(
&db,
TypeRef::UniMut(instance(thing)),
TypeRef::Mut(instance(thing)),
);
check_err(&db, TypeRef::UniMut(instance(thing)), placeholder(var));
check_err(&db, TypeRef::UniMut(instance(thing)), TypeRef::Any);
}
Expand Down
34 changes: 30 additions & 4 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3458,6 +3458,19 @@ impl TypeRef {
}
}

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

pub fn is_any(self, db: &Database) -> bool {
match self {
TypeRef::Any | TypeRef::RefAny => true,
Expand Down Expand Up @@ -3620,6 +3633,16 @@ impl TypeRef {
}
}

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

pub fn is_ref(self, db: &Database) -> bool {
match self {
TypeRef::Ref(_) => true,
Expand Down Expand Up @@ -3815,7 +3838,10 @@ impl TypeRef {
pub fn allow_as_ref(self, db: &Database) -> bool {
match self {
TypeRef::Any => true,
TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Ref(_) => true,
TypeRef::Owned(_)
| TypeRef::Mut(_)
| TypeRef::Ref(_)
| TypeRef::Uni(_) => true,
TypeRef::Placeholder(id) => {
id.value(db).map_or(false, |v| v.allow_as_ref(db))
}
Expand All @@ -3827,7 +3853,7 @@ impl TypeRef {
match self {
TypeRef::Any => true,
TypeRef::Owned(TypeId::RigidTypeParameter(id)) => id.is_mutable(db),
TypeRef::Owned(_) | TypeRef::Mut(_) => true,
TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Uni(_) => true,
TypeRef::Placeholder(id) => {
id.value(db).map_or(false, |v| v.allow_as_mut(db))
}
Expand Down Expand Up @@ -4969,7 +4995,7 @@ mod tests {
assert!(placeholder(var).allow_as_ref(&db));
assert!(owned(rigid(param)).allow_as_ref(&db));
assert!(TypeRef::Any.allow_as_ref(&db));
assert!(!uni(instance(int)).allow_as_ref(&db));
assert!(uni(instance(int)).allow_as_ref(&db));
}

#[test]
Expand All @@ -4990,7 +5016,7 @@ mod tests {
assert!(owned(rigid(param2)).allow_as_mut(&db));
assert!(!immutable(instance(int)).allow_as_mut(&db));
assert!(!owned(rigid(param1)).allow_as_mut(&db));
assert!(!uni(instance(int)).allow_as_mut(&db));
assert!(uni(instance(int)).allow_as_mut(&db));
}

#[test]
Expand Down

0 comments on commit be0d830

Please sign in to comment.