Skip to content

Commit

Permalink
Allow try/throw in closures without return types
Browse files Browse the repository at this point in the history
When a closure doesn't specify an explicit return type (i.e. almost all
of them), we now allow the use of `try` and `throw`, instead of
producing a type error.

This fixes #552.

Changelog: added
  • Loading branch information
yorickpeterse committed Jun 5, 2023
1 parent 77800fa commit 3de43cf
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
2 changes: 1 addition & 1 deletion compiler/src/mir/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,6 @@ impl<'a> LowerMethod<'a> {
self.current_block_mut().get_field(tag_reg, reg, class, tag_field, loc);

let out_reg = match node.kind {
types::ThrowKind::Unknown => unreachable!(),
types::ThrowKind::Option(typ) => {
let some_id = class
.variant(self.db(), OPTION_SOME)
Expand Down Expand Up @@ -2265,6 +2264,7 @@ impl<'a> LowerMethod<'a> {
self.current_block_mut().return_value(ret_reg, loc);
ok_reg
}
_ => unreachable!(),
};

self.current_block = after_block;
Expand Down
35 changes: 26 additions & 9 deletions compiler/src/type_check/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3014,23 +3014,28 @@ impl<'a> CheckMethodBody<'a> {
}

let ret_type = scope.return_type;
let throw_type = if scope.in_recover() && expr.is_owned(self.db()) {
expr.as_uni(self.db())
} else {
expr
};

node.return_type = ret_type;
node.resolved_type = throw_type;

match ret_type.throw_kind(self.db()) {
ThrowKind::Unknown | ThrowKind::Option(_) => self
.state
.diagnostics
.throw_not_available(self.file(), node.location.clone()),
ThrowKind::Result(ret_ok, ret_err) => {
node.resolved_type =
if scope.in_recover() && expr.is_owned(self.db()) {
expr.as_uni(self.db())
} else {
expr
};
ThrowKind::Infer(pid) => {
let var = TypeRef::placeholder(self.db_mut(), None);
let typ = TypeRef::result_type(self.db_mut(), var, expr);

if !TypeChecker::check(self.db(), expr, ret_err) {
pid.assign(self.db(), typ);
}
ThrowKind::Result(ret_ok, ret_err) => {
if !TypeChecker::check(self.db(), throw_type, ret_err) {
self.state.diagnostics.invalid_throw(
ThrowKind::Result(ret_ok, expr)
.throw_type_name(self.db(), ret_ok),
Expand Down Expand Up @@ -3857,6 +3862,18 @@ impl<'a> CheckMethodBody<'a> {
// no type-checking is necessary in this case.
return some;
}
(ThrowKind::Option(some), ThrowKind::Infer(pid)) => {
let inferred = TypeRef::option_type(self.db_mut(), some);

pid.assign(self.db(), inferred);
return some;
}
(ThrowKind::Result(ok, err), ThrowKind::Infer(pid)) => {
let inferred = TypeRef::result_type(self.db_mut(), ok, err);

pid.assign(self.db(), inferred);
return ok;
}
(
ThrowKind::Result(ok, expr_err),
ThrowKind::Result(ret_ok, ret_err),
Expand All @@ -3872,7 +3889,7 @@ impl<'a> CheckMethodBody<'a> {
node.location.clone(),
);
}
(ThrowKind::Unknown, _) => {
(ThrowKind::Unknown | ThrowKind::Infer(_), _) => {
self.state.diagnostics.invalid_try(
format_type(self.db(), expr),
self.file(),
Expand Down
36 changes: 33 additions & 3 deletions types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl TypePlaceholderId {
self.get(db).required
}

fn assign(self, db: &Database, value: TypeRef) {
pub fn assign(self, db: &Database, value: TypeRef) {
// Assigning placeholders to themselves isn't useful and results in
// resolve() getting stuck.
if let TypeRef::Placeholder(id) = value {
Expand Down Expand Up @@ -2864,14 +2864,14 @@ pub enum ConstantPatternKind {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ThrowKind {
Unknown,
Infer(TypePlaceholderId),
Option(TypeRef),
Result(TypeRef, TypeRef),
}

impl ThrowKind {
pub fn throw_type_name(self, db: &Database, ok: TypeRef) -> String {
match self {
ThrowKind::Unknown => "?".to_string(),
ThrowKind::Option(_) => {
format!("Option[{}]", format::format_type(db, ok))
}
Expand All @@ -2882,6 +2882,7 @@ impl ThrowKind {
format::format_type(db, err)
)
}
_ => "?".to_string(),
}
}

Expand Down Expand Up @@ -4075,12 +4076,41 @@ impl TypeRef {
}
}
TypeRef::Placeholder(p) => {
p.value(db).map_or(ThrowKind::Unknown, |v| v.throw_kind(db))
p.value(db).map_or(ThrowKind::Infer(p), |v| v.throw_kind(db))
}
_ => ThrowKind::Unknown,
}
}

pub fn result_type(
db: &mut Database,
ok: TypeRef,
error: TypeRef,
) -> TypeRef {
let class = db.class_in_module(RESULT_MODULE, RESULT_CLASS);
let params = class.type_parameters(db);
let mut args = TypeArguments::new();

args.assign(params[0], ok);
args.assign(params[1], error);

TypeRef::Owned(TypeId::ClassInstance(ClassInstance::generic(
db, class, args,
)))
}

pub fn option_type(db: &mut Database, some: TypeRef) -> TypeRef {
let class = db.class_in_module(OPTION_MODULE, OPTION_CLASS);
let params = class.type_parameters(db);
let mut args = TypeArguments::new();

args.assign(params[0], some);

TypeRef::Owned(TypeId::ClassInstance(ClassInstance::generic(
db, class, args,
)))
}

fn is_instance_of(self, db: &Database, id: ClassId) -> bool {
self.class_id(db) == Some(id)
}
Expand Down

0 comments on commit 3de43cf

Please sign in to comment.