Implement return keyword

This commit is contained in:
Sam Mohr 2024-10-20 04:50:12 -07:00
parent 20a539a96d
commit b3e60f9d3a
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
39 changed files with 594 additions and 80 deletions

View file

@ -446,6 +446,7 @@ fn defn_help(
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: ret_var, return_type: ret_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -96,6 +96,7 @@ impl Constraints {
Category::List, Category::List,
Category::Str, Category::Str,
Category::Character, Category::Character,
Category::Return,
]); ]);
pattern_categories.extend([ pattern_categories.extend([
@ -149,6 +150,7 @@ impl Constraints {
pub const CATEGORY_LIST: Index<Category> = Index::new(11); pub const CATEGORY_LIST: Index<Category> = Index::new(11);
pub const CATEGORY_STR: Index<Category> = Index::new(12); pub const CATEGORY_STR: Index<Category> = Index::new(12);
pub const CATEGORY_CHARACTER: Index<Category> = Index::new(13); pub const CATEGORY_CHARACTER: Index<Category> = Index::new(13);
pub const CATEGORY_RETURN: Index<Category> = Index::new(14);
pub const PCATEGORY_RECORD: Index<PatternCategory> = Index::new(0); pub const PCATEGORY_RECORD: Index<PatternCategory> = Index::new(0);
pub const PCATEGORY_EMPTYRECORD: Index<PatternCategory> = Index::new(1); pub const PCATEGORY_EMPTYRECORD: Index<PatternCategory> = Index::new(1);

View file

@ -455,6 +455,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type, function_type,
closure_type, closure_type,
return_type, return_type,
early_returns,
name, name,
captured_symbols, captured_symbols,
recursive, recursive,
@ -464,6 +465,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type: sub!(*function_type), function_type: sub!(*function_type),
closure_type: sub!(*closure_type), closure_type: sub!(*closure_type),
return_type: sub!(*return_type), return_type: sub!(*return_type),
early_returns: early_returns
.iter()
.map(|(var, region)| (sub!(*var), *region))
.collect(),
name: *name, name: *name,
captured_symbols: captured_symbols captured_symbols: captured_symbols
.iter() .iter()
@ -687,6 +692,14 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
lookups_in_cond: lookups_in_cond.to_vec(), lookups_in_cond: lookups_in_cond.to_vec(),
}, },
Return {
return_value,
return_var,
} => Return {
return_value: Box::new(return_value.map(|e| go_help!(e))),
return_var: sub!(*return_var),
},
Dbg { Dbg {
source_location, source_location,
source, source,

View file

@ -449,6 +449,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
Dbg { .. } => todo!(), Dbg { .. } => todo!(),
Expect { .. } => todo!(), Expect { .. } => todo!(),
ExpectFx { .. } => todo!(), ExpectFx { .. } => todo!(),
Return { .. } => todo!(),
TypedHole(_) => todo!(), TypedHole(_) => todo!(),
RuntimeError(_) => todo!(), RuntimeError(_) => todo!(),
} }

View file

@ -134,31 +134,32 @@ impl Annotation {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct CanDefs { pub(crate) struct CanDefs {
defs: Vec<Option<Def>>, defs: Vec<Option<Def>>,
dbgs: ExpectsOrDbgs, dbgs: OrderDependentStatements,
expects: ExpectsOrDbgs, expects: OrderDependentStatements,
expects_fx: ExpectsOrDbgs, expects_fx: OrderDependentStatements,
returns: OrderDependentStatements,
def_ordering: DefOrdering, def_ordering: DefOrdering,
aliases: VecMap<Symbol, Alias>, aliases: VecMap<Symbol, Alias>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ExpectsOrDbgs { pub struct OrderDependentStatements {
pub conditions: Vec<Expr>, pub expressions: Vec<Expr>,
pub regions: Vec<Region>, pub regions: Vec<Region>,
pub preceding_comment: Vec<Region>, pub preceding_comment: Vec<Region>,
} }
impl ExpectsOrDbgs { impl OrderDependentStatements {
fn with_capacity(capacity: usize) -> Self { fn with_capacity(capacity: usize) -> Self {
Self { Self {
conditions: Vec::with_capacity(capacity), expressions: Vec::with_capacity(capacity),
regions: Vec::with_capacity(capacity), regions: Vec::with_capacity(capacity),
preceding_comment: Vec::with_capacity(capacity), preceding_comment: Vec::with_capacity(capacity),
} }
} }
fn push(&mut self, loc_can_condition: Loc<Expr>, preceding_comment: Region) { fn push(&mut self, loc_can_condition: Loc<Expr>, preceding_comment: Region) {
self.conditions.push(loc_can_condition.value); self.expressions.push(loc_can_condition.value);
self.regions.push(loc_can_condition.region); self.regions.push(loc_can_condition.region);
self.preceding_comment.push(preceding_comment); self.preceding_comment.push(preceding_comment);
} }
@ -303,9 +304,10 @@ impl PendingTypeDef<'_> {
pub enum Declaration { pub enum Declaration {
Declare(Def), Declare(Def),
DeclareRec(Vec<Def>, IllegalCycleMark), DeclareRec(Vec<Def>, IllegalCycleMark),
Return(Expr, Region),
Builtin(Def), Builtin(Def),
Expects(ExpectsOrDbgs), Expects(OrderDependentStatements),
ExpectsFx(ExpectsOrDbgs), ExpectsFx(OrderDependentStatements),
/// If we know a cycle is illegal during canonicalization. /// If we know a cycle is illegal during canonicalization.
/// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`]. /// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`].
InvalidCycle(Vec<CycleEntry>), InvalidCycle(Vec<CycleEntry>),
@ -317,6 +319,7 @@ impl Declaration {
match self { match self {
Declare(_) => 1, Declare(_) => 1,
DeclareRec(defs, _) => defs.len(), DeclareRec(defs, _) => defs.len(),
Return(_, _) => 0,
InvalidCycle { .. } => 0, InvalidCycle { .. } => 0,
Builtin(_) => 0, Builtin(_) => 0,
Expects(_) => 0, Expects(_) => 0,
@ -340,6 +343,7 @@ impl Declaration {
expects.regions.first().unwrap(), expects.regions.first().unwrap(),
expects.regions.last().unwrap(), expects.regions.last().unwrap(),
), ),
Declaration::Return(_return_expr, return_region) => *return_region,
} }
} }
} }
@ -1137,6 +1141,7 @@ fn canonicalize_value_defs<'a>(
let mut pending_dbgs = Vec::with_capacity(value_defs.len()); let mut pending_dbgs = Vec::with_capacity(value_defs.len());
let mut pending_expects = Vec::with_capacity(value_defs.len()); let mut pending_expects = Vec::with_capacity(value_defs.len());
let mut pending_expect_fx = Vec::with_capacity(value_defs.len()); let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
let mut pending_returns = Vec::with_capacity(value_defs.len());
let mut imports_introduced = Vec::with_capacity(value_defs.len()); let mut imports_introduced = Vec::with_capacity(value_defs.len());
@ -1159,6 +1164,9 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => { PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect); pending_expect_fx.push(pending_expect);
} }
PendingValue::Return(pending_return) => {
pending_returns.push(pending_return);
}
PendingValue::ModuleImport(PendingModuleImport { PendingValue::ModuleImport(PendingModuleImport {
module_id, module_id,
region, region,
@ -1235,9 +1243,10 @@ fn canonicalize_value_defs<'a>(
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
} }
let mut dbgs = ExpectsOrDbgs::with_capacity(pending_dbgs.len()); let mut dbgs = OrderDependentStatements::with_capacity(pending_dbgs.len());
let mut expects = ExpectsOrDbgs::with_capacity(pending_expects.len()); let mut expects = OrderDependentStatements::with_capacity(pending_expects.len());
let mut expects_fx = ExpectsOrDbgs::with_capacity(pending_expects.len()); let mut expects_fx = OrderDependentStatements::with_capacity(pending_expects.len());
let mut returns = OrderDependentStatements::with_capacity(pending_returns.len());
for pending in pending_dbgs { for pending in pending_dbgs {
let (loc_can_condition, can_output) = canonicalize_expr( let (loc_can_condition, can_output) = canonicalize_expr(
@ -1281,11 +1290,21 @@ fn canonicalize_value_defs<'a>(
output.union(can_output); output.union(can_output);
} }
for pending in pending_returns {
let (loc_return_expr, can_output) =
canonicalize_expr(env, var_store, scope, pending.region, &pending.value);
returns.push(loc_return_expr, Region::zero());
output.union(can_output);
}
let can_defs = CanDefs { let can_defs = CanDefs {
defs, defs,
dbgs, dbgs,
expects, expects,
expects_fx, expects_fx,
returns,
def_ordering, def_ordering,
aliases, aliases,
}; };
@ -1681,7 +1700,7 @@ impl DefOrdering {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn sort_can_defs_new( pub(crate) fn sort_top_level_can_defs(
env: &mut Env<'_>, env: &mut Env<'_>,
scope: &mut Scope, scope: &mut Scope,
var_store: &mut VarStore, var_store: &mut VarStore,
@ -1694,10 +1713,17 @@ pub(crate) fn sort_can_defs_new(
dbgs: _, dbgs: _,
expects, expects,
expects_fx, expects_fx,
returns,
def_ordering, def_ordering,
aliases, aliases,
} = defs; } = defs;
for return_region in returns.regions {
env.problem(Problem::ReturnOutsideOfFunction {
region: return_region,
});
}
// TODO: inefficient, but I want to make this what CanDefs contains in the future // TODO: inefficient, but I want to make this what CanDefs contains in the future
let mut defs: Vec<_> = defs.into_iter().map(|x| x.unwrap()).collect(); let mut defs: Vec<_> = defs.into_iter().map(|x| x.unwrap()).collect();
@ -1712,7 +1738,7 @@ pub(crate) fn sort_can_defs_new(
// because of the ordering of declarations, expects should come first because they are // because of the ordering of declarations, expects should come first because they are
// independent, but can rely on all other top-level symbols in the module // independent, but can rely on all other top-level symbols in the module
let it = expects let it = expects
.conditions .expressions
.into_iter() .into_iter()
.zip(expects.regions) .zip(expects.regions)
.zip(expects.preceding_comment); .zip(expects.preceding_comment);
@ -1725,7 +1751,7 @@ pub(crate) fn sort_can_defs_new(
} }
let it = expects_fx let it = expects_fx
.conditions .expressions
.into_iter() .into_iter()
.zip(expects_fx.regions) .zip(expects_fx.regions)
.zip(expects_fx.preceding_comment); .zip(expects_fx.preceding_comment);
@ -1829,6 +1855,7 @@ pub(crate) fn sort_can_defs_new(
None, None,
); );
} }
// TODO: do I need to handle returns here?
_ => { _ => {
declarations.push_value_def( declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_pattern.region, symbol),
@ -1975,6 +2002,7 @@ pub(crate) fn sort_can_defs(
dbgs, dbgs,
expects, expects,
expects_fx, expects_fx,
returns,
def_ordering, def_ordering,
aliases, aliases,
} = defs; } = defs;
@ -2100,18 +2128,24 @@ pub(crate) fn sort_can_defs(
} }
} }
if !dbgs.conditions.is_empty() { if !dbgs.expressions.is_empty() {
declarations.push(Declaration::Expects(dbgs)); declarations.push(Declaration::Expects(dbgs));
} }
if !expects.conditions.is_empty() { if !expects.expressions.is_empty() {
declarations.push(Declaration::Expects(expects)); declarations.push(Declaration::Expects(expects));
} }
if !expects_fx.conditions.is_empty() { if !expects_fx.expressions.is_empty() {
declarations.push(Declaration::ExpectsFx(expects_fx)); declarations.push(Declaration::ExpectsFx(expects_fx));
} }
if !returns.expressions.is_empty() {
for (return_expr, return_region) in returns.expressions.into_iter().zip(returns.regions) {
declarations.push(Declaration::Return(return_expr, return_region));
}
}
(declarations, output) (declarations, output)
} }
@ -2354,6 +2388,7 @@ fn canonicalize_pending_value_def<'a>(
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: var_store.fresh(),
early_returns: scope.early_returns.clone(),
name: symbol, name: symbol,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -2571,6 +2606,8 @@ fn canonicalize_pending_body<'a>(
loc_value = value; loc_value = value;
} }
let expr_var = var_store.fresh();
// We treat closure definitions `foo = \a, b -> ...` differently from other body expressions, // We treat closure definitions `foo = \a, b -> ...` differently from other body expressions,
// because they need more bookkeeping (for tail calls, closure captures, etc.) // because they need more bookkeeping (for tail calls, closure captures, etc.)
// //
@ -2664,7 +2701,6 @@ fn canonicalize_pending_body<'a>(
} }
}; };
let expr_var = var_store.fresh();
let mut vars_by_symbol = SendMap::default(); let mut vars_by_symbol = SendMap::default();
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
@ -2731,7 +2767,7 @@ pub fn can_defs_with_return<'a>(
let mut loc_expr: Loc<Expr> = ret_expr; let mut loc_expr: Loc<Expr> = ret_expr;
for declaration in declarations.into_iter().rev() { for declaration in declarations.into_iter().rev() {
loc_expr = decl_to_let(declaration, loc_expr); loc_expr = decl_to_let_or_return(declaration, loc_expr, var_store);
} }
(loc_expr.value, output) (loc_expr.value, output)
@ -2758,7 +2794,11 @@ pub fn report_unused_imports(
} }
} }
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> { fn decl_to_let_or_return<'a>(
decl: Declaration,
loc_ret: Loc<Expr>,
var_store: &mut VarStore,
) -> Loc<Expr> {
match decl { match decl {
Declaration::Declare(def) => { Declaration::Declare(def) => {
let region = Region::span_across(&def.loc_pattern.region, &loc_ret.region); let region = Region::span_across(&def.loc_pattern.region, &loc_ret.region);
@ -2770,6 +2810,17 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
let expr = Expr::LetRec(defs, Box::new(loc_ret), cycle_mark); let expr = Expr::LetRec(defs, Box::new(loc_ret), cycle_mark);
Loc::at(region, expr) Loc::at(region, expr)
} }
Declaration::Return(return_expr, return_region) => {
let region = Region::span_across(&return_region, &loc_ret.region);
let return_var = var_store.fresh();
let expr = Expr::Return {
return_value: Box::new(Loc::at(return_region, return_expr)),
return_var,
};
Loc::at(region, expr)
}
Declaration::InvalidCycle(entries) => { Declaration::InvalidCycle(entries) => {
Loc::at_zero(Expr::RuntimeError(RuntimeError::CircularDef(entries))) Loc::at_zero(Expr::RuntimeError(RuntimeError::CircularDef(entries)))
} }
@ -2780,7 +2831,7 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
Declaration::Expects(expects) => { Declaration::Expects(expects) => {
let mut loc_ret = loc_ret; let mut loc_ret = loc_ret;
let conditions = expects.conditions.into_iter().rev(); let conditions = expects.expressions.into_iter().rev();
let condition_regions = expects.regions.into_iter().rev(); let condition_regions = expects.regions.into_iter().rev();
let expect_regions = expects.preceding_comment.into_iter().rev(); let expect_regions = expects.preceding_comment.into_iter().rev();
@ -3005,6 +3056,7 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>), Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>), Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>), ExpectFx(PendingExpectOrDbg<'a>),
Return(&'a Loc<ast::Expr<'a>>),
ModuleImport(PendingModuleImport<'a>), ModuleImport(PendingModuleImport<'a>),
SignatureDefMismatch, SignatureDefMismatch,
InvalidIngestedFile, InvalidIngestedFile,
@ -3154,6 +3206,8 @@ fn to_pending_value_def<'a>(
preceding_comment: *preceding_comment, preceding_comment: *preceding_comment,
}), }),
Return(return_expr) => PendingValue::Return(return_expr),
ModuleImport(module_import) => { ModuleImport(module_import) => {
let qualified_module_name: QualifiedModuleName = module_import.name.value.into(); let qualified_module_name: QualifiedModuleName = module_import.name.value.into();
let module_name = qualified_module_name.module.clone(); let module_name = qualified_module_name.module.clone();

View file

@ -177,6 +177,12 @@ fn desugar_value_def<'a>(
body_expr: desugar_expr(env, scope, stmt_expr), body_expr: desugar_expr(env, scope, stmt_expr),
} }
} }
Return(return_expr) => {
let desugared_return_expr = &*env.arena.alloc(desugar_expr(env, scope, return_expr));
Return(&desugared_return_expr)
}
} }
} }
@ -315,9 +321,15 @@ pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>)
} }
}, },
// TODO support desugaring of Dbg, Expect, and ExpectFx // TODO support desugaring of Dbg and ExpectFx
Dbg { .. } | ExpectFx { .. } => value_def, Dbg { .. } | ExpectFx { .. } => value_def,
ModuleImport { .. } | IngestedFileImport(_) => value_def, ModuleImport { .. } | IngestedFileImport(_) => value_def,
Return(ret_expr) => match unwrap_suffixed_expression(arena, ret_expr, None) {
Ok(new_ret_expr) => ValueDef::Return(new_ret_expr),
Err(..) => {
internal_error!("Unable to desugar the suffix inside a Return value def");
}
},
Stmt(..) => { Stmt(..) => {
internal_error!( internal_error!(
@ -1008,6 +1020,7 @@ pub fn desugar_expr<'a>(
Expect(condition, continuation) => { Expect(condition, continuation) => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation)); let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
env.arena.alloc(Loc { env.arena.alloc(Loc {
value: Expect(desugared_condition, desugared_continuation), value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region, region: loc_expr.region,
@ -1019,7 +1032,6 @@ pub fn desugar_expr<'a>(
} }
DbgStmt(condition, continuation) => { DbgStmt(condition, continuation) => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation)); let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
env.arena.alloc(Loc { env.arena.alloc(Loc {
@ -1027,6 +1039,16 @@ pub fn desugar_expr<'a>(
region: loc_expr.region, region: loc_expr.region,
}) })
} }
Return(return_value, after_return) => {
let desugared_return_value = &*env.arena.alloc(desugar_expr(env, scope, return_value));
let desugared_after_return =
after_return.map(|ar| *env.arena.alloc(desugar_expr(env, scope, ar)));
env.arena.alloc(Loc {
value: Return(desugared_return_value, desugared_after_return),
region: loc_expr.region,
})
}
// note this only exists after desugaring // note this only exists after desugaring
LowLevelDbg(_, _, _) => loc_expr, LowLevelDbg(_, _, _) => loc_expr,

View file

@ -286,6 +286,11 @@ pub enum Expr {
symbol: Symbol, symbol: Symbol,
}, },
Return {
return_value: Box<Loc<Expr>>,
return_var: Variable,
},
/// Rendered as empty box in editor /// Rendered as empty box in editor
TypedHole(Variable), TypedHole(Variable),
@ -360,6 +365,7 @@ impl Expr {
Self::Expect { .. } => Category::Expect, Self::Expect { .. } => Category::Expect,
Self::ExpectFx { .. } => Category::Expect, Self::ExpectFx { .. } => Category::Expect,
Self::Crash { .. } => Category::Crash, Self::Crash { .. } => Category::Crash,
Self::Return { .. } => Category::Return,
Self::Dbg { .. } => Category::Expect, Self::Dbg { .. } => Category::Expect,
@ -400,6 +406,7 @@ pub struct ClosureData {
pub function_type: Variable, pub function_type: Variable,
pub closure_type: Variable, pub closure_type: Variable,
pub return_type: Variable, pub return_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub name: Symbol, pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>, pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive, pub recursive: Recursive,
@ -476,6 +483,7 @@ impl StructAccessorData {
function_type: function_var, function_type: function_var,
closure_type: closure_var, closure_type: closure_var,
return_type: field_var, return_type: field_var,
early_returns: vec![],
name, name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -549,6 +557,7 @@ impl OpaqueWrapFunctionData {
function_type: function_var, function_type: function_var,
closure_type: closure_var, closure_type: closure_var,
return_type: opaque_var, return_type: opaque_var,
early_returns: vec![],
name: function_name, name: function_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -1008,7 +1017,7 @@ pub fn canonicalize_expr<'a>(
} }
ast::Expr::Defs(loc_defs, loc_ret) => { ast::Expr::Defs(loc_defs, loc_ret) => {
// The body expression gets a new scope for canonicalization, // The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| { scope.inner_scope(false, |inner_scope| {
let defs: Defs = (*loc_defs).clone(); let defs: Defs = (*loc_defs).clone();
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret) can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
}) })
@ -1040,7 +1049,8 @@ pub fn canonicalize_expr<'a>(
let mut can_branches = Vec::with_capacity(branches.len()); let mut can_branches = Vec::with_capacity(branches.len());
for branch in branches.iter() { for branch in branches.iter() {
let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| { let (can_when_branch, branch_references) =
scope.inner_scope(false, |inner_scope| {
canonicalize_when_branch( canonicalize_when_branch(
env, env,
var_store, var_store,
@ -1258,6 +1268,37 @@ pub fn canonicalize_expr<'a>(
output, output,
) )
} }
ast::Expr::Return(return_expr, after_return) => {
let mut output = Output::default();
let (loc_return_expr, output1) = canonicalize_expr(
env,
var_store,
scope,
return_expr.region,
&return_expr.value,
);
if let Some(after_return) = after_return {
env.problem(Problem::StatementsAfterReturn {
region: after_return.region,
});
}
output.union(output1);
let return_var = var_store.fresh();
scope.early_returns.push((return_var, return_expr.region));
(
Return {
return_value: Box::new(loc_return_expr),
return_var,
},
output,
)
}
ast::Expr::If { ast::Expr::If {
if_thens, if_thens,
final_else: final_else_branch, final_else: final_else_branch,
@ -1493,7 +1534,7 @@ pub fn canonicalize_closure<'a>(
loc_body_expr: &'a Loc<ast::Expr<'a>>, loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>, opt_def_name: Option<Symbol>,
) -> (ClosureData, Output) { ) -> (ClosureData, Output) {
scope.inner_scope(|inner_scope| { scope.inner_scope(true, |inner_scope| {
canonicalize_closure_body( canonicalize_closure_body(
env, env,
var_store, var_store,
@ -1621,10 +1662,13 @@ fn canonicalize_closure_body<'a>(
output.non_closures.insert(symbol); output.non_closures.insert(symbol);
} }
let return_type_var = var_store.fresh();
let closure_data = ClosureData { let closure_data = ClosureData {
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: return_type_var,
early_returns: scope.early_returns.clone(),
name: symbol, name: symbol,
captured_symbols, captured_symbols,
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -2027,7 +2071,8 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ TypedHole { .. } | other @ TypedHole { .. }
| other @ ForeignCall { .. } | other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) | other @ OpaqueWrapFunction(_)
| other @ Crash { .. } => other, | other @ Crash { .. }
| other @ Return { .. } => other,
List { List {
elem_var, elem_var,
@ -2253,6 +2298,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type, function_type,
closure_type, closure_type,
return_type, return_type,
early_returns,
recursive, recursive,
name, name,
captured_symbols, captured_symbols,
@ -2269,6 +2315,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type, function_type,
closure_type, closure_type,
return_type, return_type,
early_returns,
recursive, recursive,
name, name,
captured_symbols, captured_symbols,
@ -2495,6 +2542,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
ast::Expr::DbgStmt(_, _) ast::Expr::DbgStmt(_, _)
| ast::Expr::LowLevelDbg(_, _, _) | ast::Expr::LowLevelDbg(_, _, _)
| ast::Expr::Expect(_, _) | ast::Expr::Expect(_, _)
| ast::Expr::Return(_, _)
| ast::Expr::When(_, _) | ast::Expr::When(_, _)
| ast::Expr::Backpassing(_, _, _) | ast::Expr::Backpassing(_, _, _)
| ast::Expr::SpaceBefore(_, _) | ast::Expr::SpaceBefore(_, _)
@ -2795,6 +2843,7 @@ impl Declarations {
pub fn new() -> Self { pub fn new() -> Self {
Self::with_capacity(0) Self::with_capacity(0)
} }
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
Self { Self {
declarations: Vec::with_capacity(capacity), declarations: Vec::with_capacity(capacity),
@ -2841,6 +2890,7 @@ impl Declarations {
let function_def = FunctionDef { let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type, closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type, return_type: loc_closure_data.value.return_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols, captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments, arguments: loc_closure_data.value.arguments,
}; };
@ -2892,6 +2942,7 @@ impl Declarations {
let function_def = FunctionDef { let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type, closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type, return_type: loc_closure_data.value.return_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols, captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments, arguments: loc_closure_data.value.arguments,
}; };
@ -3072,6 +3123,7 @@ impl Declarations {
let function_def = FunctionDef { let function_def = FunctionDef {
closure_type: closure_data.closure_type, closure_type: closure_data.closure_type,
return_type: closure_data.return_type, return_type: closure_data.return_type,
early_returns: closure_data.early_returns,
captured_symbols: closure_data.captured_symbols, captured_symbols: closure_data.captured_symbols,
arguments: closure_data.arguments, arguments: closure_data.arguments,
}; };
@ -3112,6 +3164,7 @@ impl Declarations {
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: var_store.fresh(),
early_returns: vec![],
name: self.symbols[index].value, name: self.symbols[index].value,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -3124,6 +3177,7 @@ impl Declarations {
let function_def = FunctionDef { let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type, closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type, return_type: loc_closure_data.value.return_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols, captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments, arguments: loc_closure_data.value.arguments,
}; };
@ -3258,6 +3312,7 @@ impl DeclarationTag {
pub struct FunctionDef { pub struct FunctionDef {
pub closure_type: Variable, pub closure_type: Variable,
pub return_type: Variable, pub return_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub captured_symbols: Vec<(Symbol, Variable)>, pub captured_symbols: Vec<(Symbol, Variable)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>, pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
} }
@ -3402,6 +3457,9 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
// Intentionally ignore the lookups in the nested `expect` condition itself, // Intentionally ignore the lookups in the nested `expect` condition itself,
// because they couldn't possibly influence the outcome of this `expect`! // because they couldn't possibly influence the outcome of this `expect`!
} }
Expr::Return { return_value, .. } => {
stack.push(&return_value.value);
}
Expr::Crash { msg, .. } => stack.push(&msg.value), Expr::Crash { msg, .. } => stack.push(&msg.value),
Expr::Num(_, _, _, _) Expr::Num(_, _, _, _)
| Expr::Float(_, _, _, _, _) | Expr::Float(_, _, _, _, _)

View file

@ -369,6 +369,12 @@ pub fn canonicalize_module_defs<'a>(
PatternType::TopLevelDef, PatternType::TopLevelDef,
); );
for (_early_return_var, early_return_region) in &scope.early_returns {
env.problem(Problem::ReturnOutsideOfFunction {
region: *early_return_region,
});
}
let pending_derives = output.pending_derives; let pending_derives = output.pending_derives;
// See if any of the new idents we defined went unused. // See if any of the new idents we defined went unused.
@ -425,7 +431,7 @@ pub fn canonicalize_module_defs<'a>(
..Default::default() ..Default::default()
}; };
let (mut declarations, mut output) = crate::def::sort_can_defs_new( let (mut declarations, mut output) = crate::def::sort_top_level_can_defs(
&mut env, &mut env,
&mut scope, &mut scope,
var_store, var_store,
@ -969,6 +975,14 @@ fn fix_values_captured_in_closure_expr(
); );
} }
Return { return_value, .. } => {
fix_values_captured_in_closure_expr(
&mut return_value.value,
no_capture_symbols,
closure_captures,
);
}
Crash { msg, ret_var: _ } => { Crash { msg, ret_var: _ } => {
fix_values_captured_in_closure_expr( fix_values_captured_in_closure_expr(
&mut msg.value, &mut msg.value,

View file

@ -48,6 +48,8 @@ pub struct Scope {
/// Ignored variables (variables that start with an underscore). /// Ignored variables (variables that start with an underscore).
/// We won't intern them because they're only used during canonicalization for error reporting. /// We won't intern them because they're only used during canonicalization for error reporting.
ignored_locals: VecMap<String, Region>, ignored_locals: VecMap<String, Region>,
pub early_returns: Vec<(Variable, Region)>,
} }
impl Scope { impl Scope {
@ -73,6 +75,7 @@ impl Scope {
modules: ScopeModules::new(home, module_name), modules: ScopeModules::new(home, module_name),
imported_symbols: default_imports, imported_symbols: default_imports,
ignored_locals: VecMap::default(), ignored_locals: VecMap::default(),
early_returns: Vec::default(),
} }
} }
@ -429,7 +432,7 @@ impl Scope {
self.aliases.contains_key(&name) self.aliases.contains_key(&name)
} }
pub fn inner_scope<F, T>(&mut self, f: F) -> T pub fn inner_scope<F, T>(&mut self, entering_function: bool, f: F) -> T
where where
F: FnOnce(&mut Scope) -> T, F: FnOnce(&mut Scope) -> T,
{ {
@ -446,6 +449,11 @@ impl Scope {
let locals_snapshot = self.locals.in_scope.len(); let locals_snapshot = self.locals.in_scope.len();
let imported_symbols_snapshot = self.imported_symbols.len(); let imported_symbols_snapshot = self.imported_symbols.len();
let imported_modules_snapshot = self.modules.len(); let imported_modules_snapshot = self.modules.len();
let early_returns_snapshot = if entering_function {
std::mem::replace(&mut self.early_returns, Vec::new())
} else {
Vec::new()
};
let result = f(self); let result = f(self);
@ -453,6 +461,9 @@ impl Scope {
self.ignored_locals.truncate(ignored_locals_count); self.ignored_locals.truncate(ignored_locals_count);
self.imported_symbols.truncate(imported_symbols_snapshot); self.imported_symbols.truncate(imported_symbols_snapshot);
self.modules.truncate(imported_modules_snapshot); self.modules.truncate(imported_modules_snapshot);
if entering_function {
self.early_returns = early_returns_snapshot;
}
// anything added in the inner scope is no longer in scope now // anything added in the inner scope is no longer in scope now
for i in locals_snapshot..self.locals.in_scope.len() { for i in locals_snapshot..self.locals.in_scope.len() {
@ -882,7 +893,7 @@ mod test {
assert!(scope.lookup(&ident, region).is_err()); assert!(scope.lookup(&ident, region).is_err());
scope.inner_scope(|inner| { scope.inner_scope(false, |inner| {
assert!(inner.introduce(ident.clone(), region).is_ok()); assert!(inner.introduce(ident.clone(), region).is_ok());
}); });
@ -943,7 +954,7 @@ mod test {
&[ident1.clone(), ident2.clone(), ident3.clone(),] &[ident1.clone(), ident2.clone(), ident3.clone(),]
); );
scope.inner_scope(|inner| { scope.inner_scope(false, |inner| {
let ident4 = Ident::from("Ångström"); let ident4 = Ident::from("Ångström");
let ident5 = Ident::from("Sirály"); let ident5 = Ident::from("Sirály");

View file

@ -680,7 +680,7 @@ pub fn unwrap_suffixed_expression_defs_help<'a>(
}; };
let maybe_suffixed_value_def = match current_value_def { let maybe_suffixed_value_def = match current_value_def {
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None, Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Return(_) | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None,
AnnotatedBody { body_pattern, body_expr, ann_type, ann_pattern, .. } => Some((body_pattern, body_expr, Some((ann_pattern, ann_type)))), AnnotatedBody { body_pattern, body_expr, ann_type, ann_pattern, .. } => Some((body_pattern, body_expr, Some((ann_pattern, ann_type)))),
Body (def_pattern, def_expr) => Some((def_pattern, def_expr, None)), Body (def_pattern, def_expr) => Some((def_pattern, def_expr, None)),
}; };

View file

@ -72,6 +72,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: var_store.fresh(),
early_returns: vec![],
name: task_closure_symbol, name: task_closure_symbol,
captured_symbols, captured_symbols,
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -98,6 +99,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: var_store.fresh(),
early_returns: vec![],
name: symbol, name: symbol,
captured_symbols: std::vec::Vec::new(), captured_symbols: std::vec::Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -126,6 +128,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
return_type: var_store.fresh(), return_type: var_store.fresh(),
early_returns: vec![],
name: task_closure_symbol, name: task_closure_symbol,
captured_symbols, captured_symbols,
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -22,6 +22,10 @@ pub enum DeclarationInfo<'a> {
pattern: Pattern, pattern: Pattern,
annotation: Option<&'a Annotation>, annotation: Option<&'a Annotation>,
}, },
Return {
loc_expr: &'a Loc<Expr>,
expr_var: Variable,
},
Expectation { Expectation {
loc_condition: &'a Loc<Expr>, loc_condition: &'a Loc<Expr>,
}, },
@ -50,6 +54,7 @@ impl<'a> DeclarationInfo<'a> {
loc_expr, loc_expr,
.. ..
} => Region::span_across(&loc_symbol.region, &loc_expr.region), } => Region::span_across(&loc_symbol.region, &loc_expr.region),
Return { loc_expr, .. } => loc_expr.region,
Expectation { loc_condition } => loc_condition.region, Expectation { loc_condition } => loc_condition.region,
Function { Function {
loc_symbol, loc_symbol,
@ -67,6 +72,7 @@ impl<'a> DeclarationInfo<'a> {
fn var(&self) -> Variable { fn var(&self) -> Variable {
match self { match self {
DeclarationInfo::Value { expr_var, .. } => *expr_var, DeclarationInfo::Value { expr_var, .. } => *expr_var,
DeclarationInfo::Return { expr_var, .. } => *expr_var,
DeclarationInfo::Expectation { .. } => Variable::BOOL, DeclarationInfo::Expectation { .. } => Variable::BOOL,
DeclarationInfo::Function { expr_var, .. } => *expr_var, DeclarationInfo::Function { expr_var, .. } => *expr_var,
DeclarationInfo::Destructure { expr_var, .. } => *expr_var, DeclarationInfo::Destructure { expr_var, .. } => *expr_var,
@ -185,6 +191,9 @@ pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
Expectation { loc_condition } => { Expectation { loc_condition } => {
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL); visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
} }
Return { loc_expr, expr_var } => {
visitor.visit_expr(&loc_expr.value, loc_expr.region, expr_var);
}
Function { Function {
loc_symbol, loc_symbol,
loc_body, loc_body,
@ -403,6 +412,12 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
Variable::NULL, Variable::NULL,
); );
} }
Expr::Return {
return_value,
return_var,
} => {
visitor.visit_expr(&return_value.value, return_value.region, *return_var);
}
Expr::TypedHole(_) => { /* terminal */ } Expr::TypedHole(_) => { /* terminal */ }
Expr::RuntimeError(..) => { /* terminal */ } Expr::RuntimeError(..) => { /* terminal */ }
} }

View file

@ -113,6 +113,7 @@ fn constrain_untyped_closure(
fn_var: Variable, fn_var: Variable,
closure_var: Variable, closure_var: Variable,
ret_var: Variable, ret_var: Variable,
early_returns: &[(Variable, Region)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)], arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
loc_body_expr: &Loc<Expr>, loc_body_expr: &Loc<Expr>,
captured_symbols: &[(Symbol, Variable)], captured_symbols: &[(Symbol, Variable)],
@ -134,7 +135,12 @@ fn constrain_untyped_closure(
vars.push(closure_var); vars.push(closure_var);
vars.push(fn_var); vars.push(fn_var);
let body_type = constraints.push_expected_type(NoExpectation(return_type_index)); let body_type = constraints.push_expected_type(ForReason(
Reason::Return,
return_type_index,
loc_body_expr.region,
));
let ret_constraint = constrain_expr( let ret_constraint = constrain_expr(
types, types,
constraints, constraints,
@ -144,6 +150,21 @@ fn constrain_untyped_closure(
body_type, body_type,
); );
let mut early_return_constraints = Vec::with_capacity(early_returns.len());
for (early_return_variable, early_return_region) in early_returns {
let early_return_var = constraints.push_variable(*early_return_variable);
let early_return_con = constraints.equal_types(
early_return_var,
body_type,
Category::Return,
*early_return_region,
);
early_return_constraints.push(early_return_con);
}
let early_returns_constraint = constraints.and_constraint(early_return_constraints);
// make sure the captured symbols are sorted! // make sure the captured symbols are sorted!
debug_assert_eq!(captured_symbols.to_vec(), { debug_assert_eq!(captured_symbols.to_vec(), {
let mut copy = captured_symbols.to_vec(); let mut copy = captured_symbols.to_vec();
@ -185,6 +206,7 @@ fn constrain_untyped_closure(
region, region,
fn_var, fn_var,
), ),
early_returns_constraint,
closure_constraint, closure_constraint,
]; ];
@ -624,6 +646,7 @@ pub fn constrain_expr(
function_type: fn_var, function_type: fn_var,
closure_type: closure_var, closure_type: closure_var,
return_type: ret_var, return_type: ret_var,
early_returns,
arguments, arguments,
loc_body: boxed, loc_body: boxed,
captured_symbols, captured_symbols,
@ -640,6 +663,7 @@ pub fn constrain_expr(
*fn_var, *fn_var,
*closure_var, *closure_var,
*ret_var, *ret_var,
early_returns,
arguments, arguments,
boxed, boxed,
captured_symbols, captured_symbols,
@ -1378,6 +1402,29 @@ pub fn constrain_expr(
body_con body_con
} }
Return {
return_value,
return_var,
} => {
let return_type_index = constraints.push_variable(*return_var);
let expected_return_value = constraints.push_expected_type(ForReason(
Reason::Return,
return_type_index,
return_value.region,
));
let return_con = constrain_expr(
types,
constraints,
env,
return_value.region,
&return_value.value,
expected_return_value,
);
constraints.exists([*return_var], return_con)
}
Tag { Tag {
tag_union_var: variant_var, tag_union_var: variant_var,
ext_var, ext_var,
@ -1870,6 +1917,7 @@ fn constrain_function_def(
expr_var, expr_var,
function_def.closure_type, function_def.closure_type,
function_def.return_type, function_def.return_type,
&function_def.early_returns,
&function_def.arguments, &function_def.arguments,
loc_body_expr, loc_body_expr,
&function_def.captured_symbols, &function_def.captured_symbols,
@ -2071,6 +2119,7 @@ fn constrain_function_def(
expr_var, expr_var,
function_def.closure_type, function_def.closure_type,
function_def.return_type, function_def.return_type,
&function_def.early_returns,
&function_def.arguments, &function_def.arguments,
loc_expr, loc_expr,
&function_def.captured_symbols, &function_def.captured_symbols,
@ -3651,6 +3700,7 @@ fn constraint_recursive_function(
expr_var, expr_var,
function_def.closure_type, function_def.closure_type,
function_def.return_type, function_def.return_type,
&function_def.early_returns,
&function_def.arguments, &function_def.arguments,
loc_expr, loc_expr,
&function_def.captured_symbols, &function_def.captured_symbols,
@ -4133,6 +4183,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
| Expect { .. } | Expect { .. }
| ExpectFx { .. } | ExpectFx { .. }
| Dbg { .. } | Dbg { .. }
| Return { .. }
| TypedHole(_) | TypedHole(_)
| RuntimeError(..) | RuntimeError(..)
| ZeroArgumentTag { .. } | ZeroArgumentTag { .. }

View file

@ -147,6 +147,7 @@ fn wrap_in_decode_custom_decode_with(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: decode_with_result_var, return_type: decode_with_result_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: sorted_inner_decoder_captures, captured_symbols: sorted_inner_decoder_captures,
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -350,6 +350,7 @@ pub(super) fn step_field(
function_type, function_type,
closure_type, closure_type,
return_type: keep_or_skip_var, return_type: keep_or_skip_var,
early_returns: vec![],
name: step_field_closure, name: step_field_closure,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -586,6 +587,7 @@ fn custom_decoder_lambda(env: &mut Env<'_>, args: DecodingFieldArgs) -> (Variabl
function_type: this_custom_callback_var, function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var, closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var, return_type: custom_callback_ret_var,
early_returns: vec![],
name: custom_closure_symbol, name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)], captured_symbols: vec![(state_arg_symbol, state_record_var)],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -993,6 +995,7 @@ pub(super) fn finalizer(
function_type: function_var, function_type: function_var,
closure_type, closure_type,
return_type: return_type_var, return_type: return_type_var,
early_returns: vec![],
name: function_symbol, name: function_symbol,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -556,6 +556,7 @@ fn step_elem(
function_type: this_custom_callback_var, function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var, closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var, return_type: custom_callback_ret_var,
early_returns: vec![],
name: custom_closure_symbol, name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)], captured_symbols: vec![(state_arg_symbol, state_record_var)],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -710,6 +711,7 @@ fn step_elem(
function_type, function_type,
closure_type, closure_type,
return_type: keep_or_skip_var, return_type: keep_or_skip_var,
early_returns: vec![],
name: step_elem_closure, name: step_elem_closure,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -896,6 +898,7 @@ fn finalizer(
function_type: function_var, function_type: function_var,
closure_type, closure_type,
return_type: return_type_var, return_type: return_type_var,
early_returns: vec![],
name: function_symbol, name: function_symbol,
captured_symbols: Vec::new(), captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -188,6 +188,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: to_elem_encoder_fn_var, function_type: to_elem_encoder_fn_var,
closure_type: to_elem_encoder_lset, closure_type: to_elem_encoder_lset,
return_type: elem_encoder_var, return_type: elem_encoder_var,
early_returns: vec![],
name: to_elem_encoder_sym, name: to_elem_encoder_sym,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -281,6 +282,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_encoder_var, return_type: this_encoder_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -490,6 +492,7 @@ fn to_encoder_record(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_encoder_var, return_type: this_encoder_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -672,6 +675,7 @@ fn to_encoder_tuple(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_encoder_var, return_type: this_encoder_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -914,6 +918,7 @@ fn to_encoder_tag_union(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_encoder_var, return_type: this_encoder_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -1025,6 +1030,7 @@ fn wrap_in_encode_custom(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: Variable::LIST_U8, return_type: Variable::LIST_U8,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![(captured_symbol, captured_var)], captured_symbols: vec![(captured_symbol, captured_var)],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -542,6 +542,7 @@ fn build_outer_derived_closure(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: body_var, return_type: body_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -194,6 +194,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: to_elem_inspector_fn_var, function_type: to_elem_inspector_fn_var,
closure_type: to_elem_inspector_lset, closure_type: to_elem_inspector_lset,
return_type: elem_inspector_var, return_type: elem_inspector_var,
early_returns: vec![],
name: to_elem_inspector_sym, name: to_elem_inspector_sym,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -292,6 +293,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_inspector_var, return_type: this_inspector_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -502,6 +504,7 @@ fn to_inspector_record(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_inspector_var, return_type: this_inspector_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -685,6 +688,7 @@ fn to_inspector_tuple(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_inspector_var, return_type: this_inspector_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -931,6 +935,7 @@ fn to_inspector_tag_union(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: this_inspector_var, return_type: this_inspector_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![], captured_symbols: vec![],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,
@ -1029,6 +1034,7 @@ fn wrap_in_inspect_custom(
function_type: fn_var, function_type: fn_var,
closure_type: fn_clos_var, closure_type: fn_clos_var,
return_type: fmt_var, return_type: fmt_var,
early_returns: vec![],
name: fn_name, name: fn_name,
captured_symbols: vec![(captured_symbol, captured_var)], captured_symbols: vec![(captured_symbol, captured_var)],
recursive: Recursive::NotRecursive, recursive: Recursive::NotRecursive,

View file

@ -423,6 +423,7 @@ impl<'a> Formattable for ValueDef<'a> {
ModuleImport(module_import) => module_import.is_multiline(), ModuleImport(module_import) => module_import.is_multiline(),
IngestedFileImport(ingested_file_import) => ingested_file_import.is_multiline(), IngestedFileImport(ingested_file_import) => ingested_file_import.is_multiline(),
Stmt(loc_expr) => loc_expr.is_multiline(), Stmt(loc_expr) => loc_expr.is_multiline(),
Return(loc_expr) => loc_expr.is_multiline(),
} }
} }
@ -464,6 +465,7 @@ impl<'a> Formattable for ValueDef<'a> {
ModuleImport(module_import) => module_import.format(buf, indent), ModuleImport(module_import) => module_import.format(buf, indent),
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent), IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent), Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent),
Return(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent),
} }
} }
} }

View file

@ -14,6 +14,7 @@ use roc_parse::ast::{
}; };
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor; use roc_parse::ident::Accessor;
use roc_parse::keyword;
use roc_region::all::Loc; use roc_region::all::Loc;
impl<'a> Formattable for Expr<'a> { impl<'a> Formattable for Expr<'a> {
@ -70,6 +71,9 @@ impl<'a> Formattable for Expr<'a> {
LowLevelDbg(_, _, _) => unreachable!( LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting" "LowLevelDbg should only exist after desugaring, not during formatting"
), ),
Return(return_value, after_return) => {
return_value.is_multiline() || after_return.is_some()
}
If { If {
if_thens: branches, if_thens: branches,
@ -452,6 +456,9 @@ impl<'a> Formattable for Expr<'a> {
LowLevelDbg(_, _, _) => unreachable!( LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting" "LowLevelDbg should only exist after desugaring, not during formatting"
), ),
Return(return_value, after_return) => {
fmt_return(buf, return_value, &after_return, parens, newlines, indent);
}
If { If {
if_thens: branches, if_thens: branches,
final_else, final_else,
@ -1064,6 +1071,27 @@ fn fmt_expect<'a>(
continuation.format(buf, indent); continuation.format(buf, indent);
} }
fn fmt_return<'a>(
buf: &mut Buf,
return_value: &'a Loc<Expr<'a>>,
after_return: &Option<&'a Loc<Expr<'a>>>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str(keyword::RETURN);
buf.spaces(1);
return_value.format(buf, indent);
if let Some(after_return) = after_return {
after_return.format_with_options(buf, parens, newlines, indent);
}
}
fn fmt_if<'a>( fn fmt_if<'a>(
buf: &mut Buf, buf: &mut Buf,
branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)], branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],

View file

@ -281,6 +281,9 @@ fn generate_entry_docs(
ValueDef::IngestedFileImport { .. } => { ValueDef::IngestedFileImport { .. } => {
// Don't generate docs for ingested file imports // Don't generate docs for ingested file imports
} }
ValueDef::Return { .. } => {
// Don't generate docs for `return`s
}
ValueDef::Stmt(loc_expr) => { ValueDef::Stmt(loc_expr) => {
if let roc_parse::ast::Expr::Var { if let roc_parse::ast::Expr::Var {

View file

@ -220,6 +220,7 @@ impl<'a> LowerParams<'a> {
function_type: _, function_type: _,
closure_type: _, closure_type: _,
return_type: _, return_type: _,
early_returns: _,
recursive: _, recursive: _,
arguments: _, arguments: _,
}) => { }) => {
@ -380,6 +381,12 @@ impl<'a> LowerParams<'a> {
expr_stack.push(&mut loc_message.value); expr_stack.push(&mut loc_message.value);
expr_stack.push(&mut loc_continuation.value); expr_stack.push(&mut loc_continuation.value);
} }
Return {
return_value,
return_var: _,
} => {
expr_stack.push(&mut return_value.value);
}
RecordAccessor(_) RecordAccessor(_)
| ImportParams(_, _, None) | ImportParams(_, _, None)
| ZeroArgumentTag { | ZeroArgumentTag {
@ -532,6 +539,7 @@ impl<'a> LowerParams<'a> {
function_type: self.var_store.fresh(), function_type: self.var_store.fresh(),
closure_type: self.var_store.fresh(), closure_type: self.var_store.fresh(),
return_type: self.var_store.fresh(), return_type: self.var_store.fresh(),
early_returns: vec![],
name: self.unique_symbol(), name: self.unique_symbol(),
captured_symbols, captured_symbols,
recursive: roc_can::expr::Recursive::NotRecursive, recursive: roc_can::expr::Recursive::NotRecursive,

View file

@ -181,7 +181,8 @@ fn remove_for_reason(
def_region: _, def_region: _,
} }
| Reason::CrashArg | Reason::CrashArg
| Reason::ImportParams(_) => {} | Reason::ImportParams(_)
| Reason::Return => {}
} }
} }

View file

@ -5878,6 +5878,28 @@ pub fn with_hole<'a>(
} }
} }
} }
Return {
return_value,
return_var,
} => {
let return_symbol = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&return_value.value,
return_var,
);
assign_to_symbol(
env,
procs,
layout_cache,
return_var,
*return_value,
return_symbol,
Stmt::Ret(return_symbol),
)
}
TypedHole(_) => runtime_error(env, "Hit a blank"), TypedHole(_) => runtime_error(env, "Hit a blank"),
RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())), RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())),
Crash { msg, ret_var: _ } => { Crash { msg, ret_var: _ } => {

View file

@ -519,6 +519,13 @@ pub enum Expr<'a> {
&'a [&'a WhenBranch<'a>], &'a [&'a WhenBranch<'a>],
), ),
Return(
/// The return value
&'a Loc<Expr<'a>>,
/// The unused code after the return statement
Option<&'a Loc<Expr<'a>>>,
),
// Blank Space (e.g. comments, spaces, newlines) before or after an expression. // Blank Space (e.g. comments, spaces, newlines) before or after an expression.
// We preserve this for the formatter; canonicalization ignores it. // We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a Expr<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a Expr<'a>, &'a [CommentOrNewline<'a>]),
@ -668,6 +675,9 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::When(cond, branches) => { Expr::When(cond, branches) => {
is_expr_suffixed(&cond.value) || branches.iter().any(|x| is_when_branch_suffixed(x)) is_expr_suffixed(&cond.value) || branches.iter().any(|x| is_when_branch_suffixed(x))
} }
Expr::Return(a, b) => {
is_expr_suffixed(&a.value) || b.is_some_and(|loc_b| is_expr_suffixed(&loc_b.value))
}
Expr::SpaceBefore(a, _) => is_expr_suffixed(a), Expr::SpaceBefore(a, _) => is_expr_suffixed(a),
Expr::SpaceAfter(a, _) => is_expr_suffixed(a), Expr::SpaceAfter(a, _) => is_expr_suffixed(a),
Expr::MalformedIdent(_, _) => false, Expr::MalformedIdent(_, _) => false,
@ -826,6 +836,8 @@ pub enum ValueDef<'a> {
IngestedFileImport(IngestedFileImport<'a>), IngestedFileImport(IngestedFileImport<'a>),
Stmt(&'a Loc<Expr<'a>>), Stmt(&'a Loc<Expr<'a>>),
Return(&'a Loc<Expr<'a>>),
} }
impl<'a> ValueDef<'a> { impl<'a> ValueDef<'a> {
@ -937,6 +949,16 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&condition.value); expr_stack.push(&condition.value);
expr_stack.push(&cont.value); expr_stack.push(&cont.value);
} }
Return(return_value, after_return) => {
if let Some(after_return) = after_return {
expr_stack.reserve(2);
expr_stack.push(&return_value.value);
expr_stack.push(&after_return.value);
} else {
expr_stack.reserve(1);
expr_stack.push(&return_value.value);
}
}
Apply(fun, args, _) => { Apply(fun, args, _) => {
expr_stack.reserve(args.len() + 1); expr_stack.reserve(args.len() + 1);
expr_stack.push(&fun.value); expr_stack.push(&fun.value);
@ -1068,6 +1090,7 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
} }
} }
ValueDef::Stmt(loc_expr) => self.push_pending_from_expr(&loc_expr.value), ValueDef::Stmt(loc_expr) => self.push_pending_from_expr(&loc_expr.value),
ValueDef::Return(loc_expr) => self.push_pending_from_expr(&loc_expr.value),
ValueDef::Annotation(_, _) | ValueDef::IngestedFileImport(_) => {} ValueDef::Annotation(_, _) | ValueDef::IngestedFileImport(_) => {}
} }
@ -2463,6 +2486,7 @@ impl<'a> Malformed for Expr<'a> {
Dbg => false, Dbg => false,
DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(), DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(), LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(), BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),
UnaryOp(expr, _) => expr.is_malformed(), UnaryOp(expr, _) => expr.is_malformed(),
@ -2713,6 +2737,7 @@ impl<'a> Malformed for ValueDef<'a> {
annotation, annotation,
}) => path.is_malformed() || annotation.is_malformed(), }) => path.is_malformed() || annotation.is_malformed(),
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(), ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
ValueDef::Return(loc_expr) => loc_expr.is_malformed(),
} }
} }
} }

View file

@ -19,7 +19,7 @@ use crate::parser::{
map_with_arena, optional, reset_min_indent, sep_by1, sep_by1_e, set_min_indent, skip_first, map_with_arena, optional, reset_min_indent, sep_by1, sep_by1_e, set_min_indent, skip_first,
skip_second, specialize_err, specialize_err_ref, then, two_bytes, zero_or_more, EClosure, skip_second, specialize_err, specialize_err_ref, then, two_bytes, zero_or_more, EClosure,
EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber, EPattern, ERecord, EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber, EPattern, ERecord,
EString, EType, EWhen, Either, ParseResult, Parser, SpaceProblem, EReturn, EString, EType, EWhen, Either, ParseResult, Parser, SpaceProblem,
}; };
use crate::pattern::closure_param; use crate::pattern::closure_param;
use crate::state::State; use crate::state::State;
@ -546,6 +546,7 @@ fn stmt_start<'a>(
EExpr::Dbg, EExpr::Dbg,
dbg_stmt_help(options, preceding_comment) dbg_stmt_help(options, preceding_comment)
)), )),
loc(specialize_err(EExpr::Return, return_help(options))),
loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))), loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))),
map( map(
loc(specialize_err(EExpr::Closure, closure_help(options))), loc(specialize_err(EExpr::Closure, closure_help(options))),
@ -1443,6 +1444,7 @@ fn parse_stmt_operator<'a>(
let op_start = loc_op.region.start(); let op_start = loc_op.region.start();
let op_end = loc_op.region.end(); let op_end = loc_op.region.end();
let new_start = state.pos(); let new_start = state.pos();
match op { match op {
OperatorOrDef::BinOp(BinOp::Minus) if expr_state.end != op_start && op_end == new_start => { OperatorOrDef::BinOp(BinOp::Minus) if expr_state.end != op_start && op_end == new_start => {
parse_negated_term( parse_negated_term(
@ -2172,6 +2174,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::Dbg | Expr::Dbg
| Expr::DbgStmt(_, _) | Expr::DbgStmt(_, _)
| Expr::LowLevelDbg(_, _, _) | Expr::LowLevelDbg(_, _, _)
| Expr::Return(_, _)
| Expr::MalformedClosure | Expr::MalformedClosure
| Expr::MalformedSuffixed(..) | Expr::MalformedSuffixed(..)
| Expr::PrecedenceConflict { .. } | Expr::PrecedenceConflict { .. }
@ -2644,6 +2647,32 @@ fn expect_help<'a>(
} }
} }
fn return_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Stmt<'a>, EReturn<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent| {
let (_, return_kw, state) = loc(parser::keyword(keyword::RETURN, EReturn::Return))
.parse(arena, state, min_indent)?;
let (_, return_value, state) = parse_block(
options,
arena,
state,
true,
EReturn::IndentReturnValue,
EReturn::ReturnValue,
)
.map_err(|(_, f)| (MadeProgress, f))?;
let region = Region::span_across(&return_kw.region, &return_value.region);
let stmt = Stmt::ValueDef(ValueDef::Return(
arena.alloc(Loc::at(region, return_value.value)),
));
Ok((MadeProgress, stmt, state))
})
.trace("return_help")
}
fn dbg_stmt_help<'a>( fn dbg_stmt_help<'a>(
options: ExprParseOptions, options: ExprParseOptions,
preceding_comment: Region, preceding_comment: Region,
@ -3028,6 +3057,7 @@ fn stmts_to_expr<'a>(
CalledVia::Space, CalledVia::Space,
) )
} }
Stmt::ValueDef(ValueDef::Return(return_value)) => Expr::Return(return_value, None),
Stmt::ValueDef(ValueDef::Expect { .. }) => { Stmt::ValueDef(ValueDef::Expect { .. }) => {
return Err(EExpr::Expect( return Err(EExpr::Expect(
EExpect::Continuation( EExpect::Continuation(
@ -3082,6 +3112,20 @@ fn stmts_to_defs<'a>(
last_expr = Some(sp_stmt.item.with_value(e)); last_expr = Some(sp_stmt.item.with_value(e));
} }
} }
Stmt::ValueDef(ValueDef::Return(return_value)) => {
if i == stmts.len() - 1 {
last_expr = Some(Loc::at_zero(Expr::Return(return_value, None)));
} else {
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
last_expr = Some(Loc::at_zero(Expr::Return(
return_value,
Some(arena.alloc(rest)),
)));
}
// don't re-process the rest of the statements, they got consumed by the early return
break;
}
Stmt::Backpassing(pats, call) => { Stmt::Backpassing(pats, call) => {
if last_expr.is_some() { if last_expr.is_some() {
return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start())); return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start()));

View file

@ -9,6 +9,7 @@ pub const DBG: &str = "dbg";
pub const IMPORT: &str = "import"; pub const IMPORT: &str = "import";
pub const EXPECT: &str = "expect"; pub const EXPECT: &str = "expect";
pub const EXPECT_FX: &str = "expect-fx"; pub const EXPECT_FX: &str = "expect-fx";
pub const RETURN: &str = "return";
pub const CRASH: &str = "crash"; pub const CRASH: &str = "crash";
// These keywords are valid in imports // These keywords are valid in imports
@ -21,6 +22,6 @@ pub const WHERE: &str = "where";
// These keywords are valid in headers // These keywords are valid in headers
pub const PLATFORM: &str = "platform"; pub const PLATFORM: &str = "platform";
pub const KEYWORDS: [&str; 11] = [ pub const KEYWORDS: [&str; 12] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, EXPECT_FX, CRASH, IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, EXPECT_FX, RETURN, CRASH,
]; ];

View file

@ -16,7 +16,6 @@ pub mod keyword;
pub mod normalize; pub mod normalize;
pub mod number_literal; pub mod number_literal;
pub mod pattern; pub mod pattern;
pub mod problems;
pub mod src64; pub mod src64;
pub mod state; pub mod state;
pub mod string_literal; pub mod string_literal;

View file

@ -3,6 +3,7 @@ use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp}; use roc_module::called_via::{BinOp, UnaryOp};
use roc_region::all::{Loc, Position, Region}; use roc_region::all::{Loc, Position, Region};
use crate::parser::EReturn;
use crate::{ use crate::{
ast::{ ast::{
AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header, AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header,
@ -439,6 +440,7 @@ impl<'a> Normalize<'a> for ValueDef<'a> {
IngestedFileImport(ingested_file_import.normalize(arena)) IngestedFileImport(ingested_file_import.normalize(arena))
} }
Stmt(loc_expr) => Stmt(arena.alloc(loc_expr.normalize(arena))), Stmt(loc_expr) => Stmt(arena.alloc(loc_expr.normalize(arena))),
Return(loc_expr) => Return(arena.alloc(loc_expr.normalize(arena))),
} }
} }
} }
@ -756,6 +758,10 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(a.normalize(arena)), arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)), arena.alloc(b.normalize(arena)),
), ),
Expr::Return(a, b) => Expr::Return(
arena.alloc(a.normalize(arena)),
b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))),
),
Expr::Apply(a, b, c) => { Expr::Apply(a, b, c) => {
Expr::Apply(arena.alloc(a.normalize(arena)), b.normalize(arena), c) Expr::Apply(arena.alloc(a.normalize(arena)), b.normalize(arena), c)
} }
@ -1038,6 +1044,9 @@ impl<'a> Normalize<'a> for EExpr<'a> {
EExpr::Expect(inner_err, _pos) => { EExpr::Expect(inner_err, _pos) => {
EExpr::Expect(inner_err.normalize(arena), Position::zero()) EExpr::Expect(inner_err.normalize(arena), Position::zero())
} }
EExpr::Return(inner_err, _pos) => {
EExpr::Return(inner_err.normalize(arena), Position::zero())
}
EExpr::Dbg(inner_err, _pos) => EExpr::Dbg(inner_err.normalize(arena), Position::zero()), EExpr::Dbg(inner_err, _pos) => EExpr::Dbg(inner_err.normalize(arena), Position::zero()),
EExpr::Import(inner_err, _pos) => { EExpr::Import(inner_err, _pos) => {
EExpr::Import(inner_err.normalize(arena), Position::zero()) EExpr::Import(inner_err.normalize(arena), Position::zero())
@ -1472,6 +1481,20 @@ impl<'a> Normalize<'a> for EExpect<'a> {
} }
} }
} }
impl<'a> Normalize<'a> for EReturn<'a> {
fn normalize(&self, arena: &'a Bump) -> Self {
match self {
EReturn::Space(inner_err, _) => EReturn::Space(*inner_err, Position::zero()),
EReturn::Return(_) => EReturn::Return(Position::zero()),
EReturn::ReturnValue(inner_err, _) => {
EReturn::ReturnValue(arena.alloc(inner_err.normalize(arena)), Position::zero())
}
EReturn::IndentReturnValue(_) => EReturn::IndentReturnValue(Position::zero()),
}
}
}
impl<'a> Normalize<'a> for EIf<'a> { impl<'a> Normalize<'a> for EIf<'a> {
fn normalize(&self, arena: &'a Bump) -> Self { fn normalize(&self, arena: &'a Bump) -> Self {
match self { match self {

View file

@ -97,6 +97,7 @@ impl_space_problem! {
EPattern<'a>, EPattern<'a>,
EProvides<'a>, EProvides<'a>,
ERecord<'a>, ERecord<'a>,
EReturn<'a>,
ERequires<'a>, ERequires<'a>,
EString<'a>, EString<'a>,
EType<'a>, EType<'a>,
@ -337,6 +338,7 @@ pub enum EExpr<'a> {
Expect(EExpect<'a>, Position), Expect(EExpect<'a>, Position),
Dbg(EExpect<'a>, Position), Dbg(EExpect<'a>, Position),
Import(EImport<'a>, Position), Import(EImport<'a>, Position),
Return(EReturn<'a>, Position),
Closure(EClosure<'a>, Position), Closure(EClosure<'a>, Position),
Underscore(Position), Underscore(Position),
@ -513,6 +515,14 @@ pub enum EExpect<'a> {
IndentCondition(Position), IndentCondition(Position),
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EReturn<'a> {
Space(BadInputError, Position),
Return(Position),
ReturnValue(&'a EExpr<'a>, Position),
IndentReturnValue(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EImport<'a> { pub enum EImport<'a> {
Import(Position), Import(Position),

View file

@ -1,25 +0,0 @@
use roc_region::all::Loc;
pub type Problems = Vec<Loc<Problem>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Problem {
// UNICODE CODE POINT
/// TODO Invalid hex code - Unicode code points must be specified using hexadecimal characters (the numbers 0-9 and letters A-F)
NonHexCharsInUnicodeCodePt,
/// TODO Invalid Unicode code point. It must be no more than \\u{10FFFF}.
UnicodeCodePtTooLarge,
InvalidUnicodeCodePt,
MalformedEscapedUnicode,
NoUnicodeDigits,
// STRING LITERAL
NewlineInLiteral,
Tab,
CarriageReturn,
NullChar,
UnsupportedEscapedChar,
// NUMBER LITERAL
OutsideSupportedRange,
}

View file

@ -242,6 +242,12 @@ pub enum Problem {
one_occurrence: Region, one_occurrence: Region,
kind: AliasKind, kind: AliasKind,
}, },
ReturnOutsideOfFunction {
region: Region,
},
StatementsAfterReturn {
region: Region,
},
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -323,6 +329,8 @@ impl Problem {
Problem::OverAppliedDbg { .. } => RuntimeError, Problem::OverAppliedDbg { .. } => RuntimeError,
Problem::DefsOnlyUsedInRecursion(_, _) => Warning, Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
Problem::FileProblem { .. } => Fatal, Problem::FileProblem { .. } => Fatal,
Problem::ReturnOutsideOfFunction { .. } => Warning,
Problem::StatementsAfterReturn { .. } => Warning,
} }
} }
@ -434,6 +442,7 @@ impl Problem {
field: region, field: region,
}) })
| Problem::RuntimeError(RuntimeError::ReadIngestedFileError { region, .. }) | Problem::RuntimeError(RuntimeError::ReadIngestedFileError { region, .. })
| Problem::RuntimeError(RuntimeError::ReturnOutsideOfFunction(region))
| Problem::InvalidAliasRigid { region, .. } | Problem::InvalidAliasRigid { region, .. }
| Problem::InvalidInterpolation(region) | Problem::InvalidInterpolation(region)
| Problem::InvalidHexadecimal(region) | Problem::InvalidHexadecimal(region)
@ -485,7 +494,9 @@ impl Problem {
| Problem::UnappliedCrash { region } | Problem::UnappliedCrash { region }
| Problem::OverAppliedDbg { region } | Problem::OverAppliedDbg { region }
| Problem::UnappliedDbg { region } | Problem::UnappliedDbg { region }
| Problem::DefsOnlyUsedInRecursion(_, region) => Some(*region), | Problem::DefsOnlyUsedInRecursion(_, region)
| Problem::ReturnOutsideOfFunction { region }
| Problem::StatementsAfterReturn { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries)) Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => { | Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region) cycle_entries.first().map(|entry| entry.expr_region)
@ -692,6 +703,8 @@ pub enum RuntimeError {
}, },
MalformedSuffixed(Region), MalformedSuffixed(Region),
ReturnOutsideOfFunction(Region),
} }
impl RuntimeError { impl RuntimeError {
@ -740,7 +753,8 @@ impl RuntimeError {
record: _, record: _,
field: region, field: region,
} }
| RuntimeError::ReadIngestedFileError { region, .. } => *region, | RuntimeError::ReadIngestedFileError { region, .. }
| RuntimeError::ReturnOutsideOfFunction(region) => *region,
RuntimeError::InvalidUnicodeCodePt(region) => *region, RuntimeError::InvalidUnicodeCodePt(region) => *region,
RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(), RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(),
RuntimeError::LookupNotInScope { loc_name, .. } => loc_name.region, RuntimeError::LookupNotInScope { loc_name, .. } => loc_name.region,

View file

@ -3425,6 +3425,7 @@ pub enum Reason {
}, },
CrashArg, CrashArg,
ImportParams(ModuleId), ImportParams(ModuleId),
Return,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@ -3474,6 +3475,7 @@ pub enum Category {
Expect, Expect,
Dbg, Dbg,
Return,
Unknown, Unknown,
} }

View file

@ -214,6 +214,7 @@ impl CompletionVisitor<'_> {
DeclarationInfo::Value { DeclarationInfo::Value {
expr_var, pattern, .. expr_var, pattern, ..
} => self.patterns(pattern, expr_var), } => self.patterns(pattern, expr_var),
DeclarationInfo::Return { .. } => vec![],
DeclarationInfo::Function { DeclarationInfo::Function {
expr_var, expr_var,
pattern, pattern,

View file

@ -641,6 +641,7 @@ impl IterTokens for ValueDef<'_> {
onetoken(Token::Import, import.name.item.region, arena) onetoken(Token::Import, import.name.item.region, arena)
} }
ValueDef::Stmt(loc_expr) => loc_expr.iter_tokens(arena), ValueDef::Stmt(loc_expr) => loc_expr.iter_tokens(arena),
ValueDef::Return(loc_expr) => loc_expr.iter_tokens(arena),
} }
} }
} }
@ -718,6 +719,11 @@ impl IterTokens for Loc<Expr<'_>> {
Expr::When(e, branches) => (e.iter_tokens(arena).into_iter()) Expr::When(e, branches) => (e.iter_tokens(arena).into_iter())
.chain(branches.iter_tokens(arena)) .chain(branches.iter_tokens(arena))
.collect_in(arena), .collect_in(arena),
Expr::Return(ret_expr, after_ret) => ret_expr
.iter_tokens(arena)
.into_iter()
.chain(after_ret.iter_tokens(arena))
.collect_in(arena),
Expr::SpaceBefore(e, _) | Expr::SpaceAfter(e, _) => { Expr::SpaceBefore(e, _) | Expr::SpaceAfter(e, _) => {
Loc::at(region, *e).iter_tokens(arena) Loc::at(region, *e).iter_tokens(arena)
} }

View file

@ -199,6 +199,9 @@ impl ReplState {
ValueDef::ExpectFx { .. } => { ValueDef::ExpectFx { .. } => {
todo!("handle receiving an `expect-fx` - what should the repl do for that?") todo!("handle receiving an `expect-fx` - what should the repl do for that?")
} }
ValueDef::Return(_) => {
todo!("handle receiving an `return` - what should the repl do for that?")
}
ValueDef::ModuleImport(import) => match import.name.value.package { ValueDef::ModuleImport(import) => match import.name.value.package {
Some(_) => { Some(_) => {
todo!("handle importing a module from a package") todo!("handle importing a module from a package")

View file

@ -1346,6 +1346,32 @@ pub fn can_problem<'b>(
doc = report.doc; doc = report.doc;
title = report.title; title = report.title;
} }
Problem::ReturnOutsideOfFunction { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "),
alloc.keyword("return"),
alloc.reflow(" statement doesn't belong to a function:"),
]),
alloc.region(lines.convert_region(region), severity),
]);
title = "RETURN OUTSIDE OF FUNCTION".to_string();
}
Problem::StatementsAfterReturn { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This code won't run because it follows a "),
alloc.keyword("return"),
alloc.reflow(" statement:"),
]),
alloc.region(lines.convert_region(region), severity),
]);
title = "UNREACHABLE CODE".to_string();
}
}; };
Report { Report {
@ -2522,6 +2548,18 @@ fn pretty_runtime_error<'b>(
title = "OPTIONAL FIELD IN RECORD BUILDER"; title = "OPTIONAL FIELD IN RECORD BUILDER";
} }
RuntimeError::ReturnOutsideOfFunction(region) => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("The "),
alloc.keyword("return"),
alloc.reflow(" keyword can only be used in functions."),
]),
alloc.region(lines.convert_region(region), severity),
]);
title = "RETURN OUTSIDE OF FUNCTION";
}
} }
(doc, title) (doc, title)

View file

@ -918,7 +918,6 @@ fn to_expr_report<'b>(
alloc.reflow("But I need every "), alloc.reflow("But I need every "),
alloc.keyword("expect"), alloc.keyword("expect"),
alloc.reflow(" condition to evaluate to a "), alloc.reflow(" condition to evaluate to a "),
alloc.type_str("Bool"),
alloc.reflow("—either "), alloc.reflow("—either "),
alloc.tag("Bool.true".into()), alloc.tag("Bool.true".into()),
alloc.reflow(" or "), alloc.reflow(" or "),
@ -958,7 +957,6 @@ fn to_expr_report<'b>(
alloc.reflow("But I need every "), alloc.reflow("But I need every "),
alloc.keyword("if"), alloc.keyword("if"),
alloc.reflow(" condition to evaluate to a "), alloc.reflow(" condition to evaluate to a "),
alloc.type_str("Bool"),
alloc.reflow("—either "), alloc.reflow("—either "),
alloc.tag("Bool.true".into()), alloc.tag("Bool.true".into()),
alloc.reflow(" or "), alloc.reflow(" or "),
@ -997,7 +995,6 @@ fn to_expr_report<'b>(
alloc.reflow("But I need every "), alloc.reflow("But I need every "),
alloc.keyword("if"), alloc.keyword("if"),
alloc.reflow(" guard condition to evaluate to a "), alloc.reflow(" guard condition to evaluate to a "),
alloc.type_str("Bool"),
alloc.reflow("—either "), alloc.reflow("—either "),
alloc.tag("Bool.true".into()), alloc.tag("Bool.true".into()),
alloc.reflow(" or "), alloc.reflow(" or "),
@ -1645,6 +1642,44 @@ fn to_expr_report<'b>(
unimplemented!("record default field is not implemented yet") unimplemented!("record default field is not implemented yet")
} }
Reason::ImportParams(_) => unreachable!(), Reason::ImportParams(_) => unreachable!(),
Reason::Return => {
let problem = alloc.concat([
alloc.text("This "),
alloc.keyword("return"),
alloc.reflow(
" statement doesn't match the return type of its enclosing function:",
),
]);
let comparison = type_comparison(
alloc,
found,
expected_type,
ExpectationContext::Arbitrary,
add_category(alloc, alloc.text("It is"), &category),
alloc.concat([
alloc.reflow("But I need every "),
alloc.keyword("return"),
alloc.reflow(" statement in that function to return:"),
]),
None,
);
Report {
title: "TYPE MISMATCH".to_string(),
filename,
doc: alloc.stack([
problem,
alloc.region_with_subregion(
lines.convert_region(region),
lines.convert_region(expr_region),
severity,
),
comparison,
]),
severity,
}
}
}, },
} }
} }
@ -1983,6 +2018,15 @@ fn format_category<'b>(
alloc.concat([this_is, alloc.text(" a dbg statement")]), alloc.concat([this_is, alloc.text(" a dbg statement")]),
alloc.text(" of type:"), alloc.text(" of type:"),
), ),
Return => (
alloc.concat([
this_is,
alloc.reflow(" a "),
alloc.keyword("return"),
alloc.reflow(" statement"),
]),
alloc.text(" of type:"),
),
} }
} }