Merge remote-tracking branch 'remote/main' into rebuild-platform

This commit is contained in:
Luke Boswell 2024-11-04 13:57:51 +11:00
commit c00db6da37
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
100 changed files with 2554 additions and 279 deletions

View file

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

View file

@ -97,6 +97,7 @@ impl Constraints {
Category::List,
Category::Str,
Category::Character,
Category::Return,
]);
pattern_categories.extend([
@ -150,6 +151,7 @@ impl Constraints {
pub const CATEGORY_LIST: Index<Category> = Index::new(11);
pub const CATEGORY_STR: Index<Category> = Index::new(12);
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_EMPTYRECORD: Index<PatternCategory> = Index::new(1);

View file

@ -456,6 +456,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type,
closure_type,
return_type,
early_returns,
name,
captured_symbols,
recursive,
@ -465,6 +466,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type: sub!(*function_type),
closure_type: sub!(*closure_type),
return_type: sub!(*return_type),
early_returns: early_returns
.iter()
.map(|(var, region)| (sub!(*var), *region))
.collect(),
name: *name,
captured_symbols: captured_symbols
.iter()
@ -688,6 +693,14 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
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 {
source_location,
source,
@ -961,7 +974,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
// Everything else is a mechanical descent.
Structure(flat_type) => match flat_type {
EmptyRecord | EmptyTuple | EmptyTagUnion => Structure(flat_type),
EmptyRecord | EmptyTagUnion => Structure(flat_type),
Apply(symbol, arguments) => {
descend_slice!(arguments);

View file

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

View file

@ -1681,7 +1681,7 @@ impl DefOrdering {
}
#[inline(always)]
pub(crate) fn sort_can_defs_new(
pub(crate) fn sort_top_level_can_defs(
env: &mut Env<'_>,
scope: &mut Scope,
var_store: &mut VarStore,
@ -2354,6 +2354,7 @@ fn canonicalize_pending_value_def<'a>(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
early_returns: scope.early_returns.clone(),
name: symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
@ -2571,6 +2572,8 @@ fn canonicalize_pending_body<'a>(
loc_value = value;
}
let expr_var = var_store.fresh();
// We treat closure definitions `foo = \a, b -> ...` differently from other body expressions,
// because they need more bookkeeping (for tail calls, closure captures, etc.)
//
@ -2602,9 +2605,15 @@ fn canonicalize_pending_body<'a>(
env.tailcallable_symbol = outer_tailcallable;
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(tail_symbol) if tail_symbol == *defined_symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
let is_recursive = if !can_output.tail_calls.is_empty()
&& can_output
.tail_calls
.iter()
.all(|tail_symbol| tail_symbol == defined_symbol)
{
Recursive::TailRecursive
} else {
Recursive::NotRecursive
};
closure_data.recursive = is_recursive;
@ -2664,7 +2673,6 @@ fn canonicalize_pending_body<'a>(
}
};
let expr_var = var_store.fresh();
let mut vars_by_symbol = SendMap::default();
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);

View file

@ -23,6 +23,10 @@ use roc_region::all::{Loc, Region};
//
// Thank you, Markus!
/// Desugar a single binary operation.
///
/// When using this function, don't desugar `left` and `right` before calling so that
/// we can properly desugar `|> try` expressions!
fn new_op_call_expr<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
@ -33,10 +37,43 @@ fn new_op_call_expr<'a>(
let region = Region::span_across(&left.region, &right.region);
let value = match loc_op.value {
// Rewrite the Pizza operator into an Apply
Pizza => {
// Rewrite the Pizza operator into an Apply
// Allow `left |> try (optional)` to desugar to `try left (optional)`
let right_without_spaces = without_spaces(&right.value);
match right_without_spaces {
Try => {
let desugared_left = desugar_expr(env, scope, left);
return desugar_try_expr(env, scope, desugared_left);
}
Apply(&Loc { value: Try, .. }, arguments, _called_via) => {
let try_fn = desugar_expr(env, scope, arguments.first().unwrap());
match &right.value {
let mut args = Vec::with_capacity_in(arguments.len(), env.arena);
args.push(desugar_expr(env, scope, left));
args.extend(
arguments
.iter()
.skip(1)
.map(|a| desugar_expr(env, scope, a)),
);
return desugar_try_expr(
env,
scope,
env.arena.alloc(Loc::at(
right.region,
Expr::Apply(try_fn, args.into_bump_slice(), CalledVia::Try),
)),
);
}
_ => {}
}
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
match right.value {
Apply(function, arguments, _called_via) => {
let mut args = Vec::with_capacity_in(1 + arguments.len(), env.arena);
@ -55,6 +92,9 @@ fn new_op_call_expr<'a>(
}
}
binop => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
// This is a normal binary operator like (+), so desugar it
// into the appropriate function call.
let (module_name, ident) = binop_to_function(binop);
@ -73,6 +113,13 @@ fn new_op_call_expr<'a>(
Loc { region, value }
}
fn without_spaces<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> {
match expr {
Expr::SpaceBefore(inner, _) | Expr::SpaceAfter(inner, _) => without_spaces(inner),
_ => expr,
}
}
fn desugar_value_def<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
@ -315,7 +362,7 @@ 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,
ModuleImport { .. } | IngestedFileImport(_) => value_def,
@ -351,7 +398,8 @@ pub fn desugar_expr<'a>(
| OptionalFieldInRecordBuilder { .. }
| Tag(_)
| OpaqueRef(_)
| Crash => loc_expr,
| Crash
| Try => loc_expr,
Str(str_literal) => match str_literal {
StrLiteral::PlainLine(_) => loc_expr,
@ -870,6 +918,31 @@ pub fn desugar_expr<'a>(
})
}
}
Apply(
Loc {
value: Try,
region: _,
},
loc_args,
_called_via,
) => {
let result_expr = if loc_args.len() == 1 {
desugar_expr(env, scope, loc_args[0])
} else {
let function = desugar_expr(env, scope, loc_args.first().unwrap());
let mut desugared_args = Vec::with_capacity_in(loc_args.len() - 1, env.arena);
for loc_arg in &loc_args[1..] {
desugared_args.push(desugar_expr(env, scope, loc_arg));
}
env.arena.alloc(Loc::at(
loc_expr.region,
Expr::Apply(function, desugared_args.into_bump_slice(), CalledVia::Try),
))
};
env.arena.alloc(desugar_try_expr(env, scope, result_expr))
}
Apply(loc_fn, loc_args, called_via) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
@ -1008,6 +1081,7 @@ pub fn desugar_expr<'a>(
Expect(condition, continuation) => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
env.arena.alloc(Loc {
value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region,
@ -1019,7 +1093,6 @@ pub fn desugar_expr<'a>(
}
DbgStmt(condition, continuation) => {
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
env.arena.alloc(Loc {
@ -1027,12 +1100,87 @@ pub fn desugar_expr<'a>(
region: loc_expr.region,
})
}
Return(return_value, after_return) => {
let desugared_return_value = &*env.arena.alloc(desugar_expr(env, scope, return_value));
env.arena.alloc(Loc {
// Do not desugar after_return since it isn't run anyway
value: Return(desugared_return_value, *after_return),
region: loc_expr.region,
})
}
// note this only exists after desugaring
LowLevelDbg(_, _, _) => loc_expr,
}
}
pub fn desugar_try_expr<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
result_expr: &'a Loc<Expr<'a>>,
) -> Loc<Expr<'a>> {
let region = result_expr.region;
let ok_symbol = env.arena.alloc(scope.gen_unique_symbol_name().to_string());
let err_symbol = env.arena.alloc(scope.gen_unique_symbol_name().to_string());
let ok_branch = env.arena.alloc(WhenBranch {
patterns: env.arena.alloc([Loc::at(
region,
Pattern::Apply(
env.arena.alloc(Loc::at(region, Pattern::Tag("Ok"))),
env.arena
.alloc([Loc::at(region, Pattern::Identifier { ident: ok_symbol })]),
),
)]),
value: Loc::at(
region,
Expr::Var {
module_name: "",
ident: ok_symbol,
},
),
guard: None,
});
let err_branch = env.arena.alloc(WhenBranch {
patterns: env.arena.alloc([Loc::at(
region,
Pattern::Apply(
env.arena.alloc(Loc::at(region, Pattern::Tag("Err"))),
env.arena
.alloc([Loc::at(region, Pattern::Identifier { ident: err_symbol })]),
),
)]),
value: Loc::at(
region,
Expr::Return(
env.arena.alloc(Loc::at(
region,
Expr::Apply(
env.arena.alloc(Loc::at(region, Expr::Tag("Err"))),
&*env.arena.alloc([&*env.arena.alloc(Loc::at(
region,
Expr::Var {
module_name: "",
ident: err_symbol,
},
))]),
CalledVia::Try,
),
)),
None,
),
),
guard: None,
});
Loc::at(
region,
Expr::When(result_expr, &*env.arena.alloc([&*ok_branch, &*err_branch])),
)
}
fn desugar_str_segments<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
@ -1398,7 +1546,7 @@ fn desugar_bin_ops<'a>(
let mut op_stack: Vec<Loc<BinOp>> = Vec::with_capacity_in(lefts.len(), env.arena);
for (loc_expr, loc_op) in lefts {
arg_stack.push(desugar_expr(env, scope, loc_expr));
arg_stack.push(loc_expr);
match run_binop_step(
env,
scope,
@ -1412,7 +1560,7 @@ fn desugar_bin_ops<'a>(
}
}
let mut expr = desugar_expr(env, scope, right);
let mut expr = right;
for (left, loc_op) in arg_stack.into_iter().zip(op_stack.into_iter()).rev() {
expr = env

View file

@ -237,9 +237,6 @@ fn index_var(
};
return Ok(std::iter::repeat(Variable::NULL).take(num_fields).collect());
}
FlatType::EmptyTuple => {
return Ok(std::iter::repeat(Variable::NULL).take(0).collect());
}
FlatType::EmptyTagUnion => {
internal_error!("empty tag unions are not indexable")
}

View file

@ -39,7 +39,7 @@ pub type PendingDerives = VecMap<Symbol, (Type, Vec<Loc<Symbol>>)>;
#[derive(Clone, Default, Debug)]
pub struct Output {
pub references: References,
pub tail_call: Option<Symbol>,
pub tail_calls: Vec<Symbol>,
pub introduced_variables: IntroducedVariables,
pub aliases: VecMap<Symbol, Alias>,
pub non_closures: VecSet<Symbol>,
@ -50,8 +50,8 @@ impl Output {
pub fn union(&mut self, other: Self) {
self.references.union_mut(&other.references);
if let (None, Some(later)) = (self.tail_call, other.tail_call) {
self.tail_call = Some(later);
if self.tail_calls.is_empty() && !other.tail_calls.is_empty() {
self.tail_calls = other.tail_calls;
}
self.introduced_variables
@ -287,6 +287,11 @@ pub enum Expr {
symbol: Symbol,
},
Return {
return_value: Box<Loc<Expr>>,
return_var: Variable,
},
/// Rendered as empty box in editor
TypedHole(Variable),
@ -361,6 +366,7 @@ impl Expr {
Self::Expect { .. } => Category::Expect,
Self::ExpectFx { .. } => Category::Expect,
Self::Crash { .. } => Category::Crash,
Self::Return { .. } => Category::Return,
Self::Dbg { .. } => Category::Expect,
@ -401,6 +407,7 @@ pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
pub return_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
@ -477,6 +484,7 @@ impl StructAccessorData {
function_type: function_var,
closure_type: closure_var,
return_type: field_var,
early_returns: vec![],
name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -550,6 +558,7 @@ impl OpaqueWrapFunctionData {
function_type: function_var,
closure_type: closure_var,
return_type: opaque_var,
early_returns: vec![],
name: function_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -715,7 +724,6 @@ pub fn canonicalize_expr<'a>(
let output = Output {
references,
tail_call: None,
..Default::default()
};
@ -783,7 +791,6 @@ pub fn canonicalize_expr<'a>(
let output = Output {
references,
tail_call: None,
..Default::default()
};
@ -900,17 +907,19 @@ pub fn canonicalize_expr<'a>(
output.union(fn_expr_output);
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
output.tail_calls = vec![];
let expr = match fn_expr.value {
Var(symbol, _) => {
output.references.insert_call(symbol);
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
output.tail_call = match &env.tailcallable_symbol {
Some(tc_sym) if *tc_sym == symbol => Some(symbol),
Some(_) | None => None,
};
if env
.tailcallable_symbol
.is_some_and(|tc_sym| tc_sym == symbol)
{
output.tail_calls.push(symbol);
}
Call(
Box::new((
@ -1009,7 +1018,7 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::Defs(loc_defs, loc_ret) => {
// The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| {
scope.inner_def_scope(|inner_scope| {
let defs: Defs = (*loc_defs).clone();
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
})
@ -1036,12 +1045,12 @@ pub fn canonicalize_expr<'a>(
canonicalize_expr(env, var_store, scope, loc_cond.region, &loc_cond.value);
// the condition can never be a tail-call
output.tail_call = None;
output.tail_calls = vec![];
let mut can_branches = Vec::with_capacity(branches.len());
for branch in branches.iter() {
let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| {
let (can_when_branch, branch_references) = scope.inner_def_scope(|inner_scope| {
canonicalize_when_branch(
env,
var_store,
@ -1061,7 +1070,7 @@ pub fn canonicalize_expr<'a>(
// if code gen mistakenly thinks this is a tail call just because its condition
// happened to be one. (The condition gave us our initial output value.)
if branches.is_empty() {
output.tail_call = None;
output.tail_calls = vec![];
}
// Incorporate all three expressions into a combined Output value.
@ -1123,6 +1132,11 @@ pub fn canonicalize_expr<'a>(
ast::Expr::TrySuffix { .. } => internal_error!(
"a Expr::TrySuffix expression was not completely removed in desugar_value_def_suffixed"
),
ast::Expr::Try => {
// Treat remaining `try` keywords as normal variables so that we can continue to support `Result.try`
canonicalize_var_lookup(env, var_store, scope, "", "try", region)
}
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
@ -1259,6 +1273,40 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::Return(return_expr, after_return) => {
let mut output = Output::default();
if let Some(after_return) = after_return {
let region_with_return =
Region::span_across(&return_expr.region, &after_return.region);
env.problem(Problem::StatementsAfterReturn {
region: region_with_return,
});
}
let (loc_return_expr, output1) = canonicalize_expr(
env,
var_store,
scope,
return_expr.region,
&return_expr.value,
);
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 {
if_thens,
final_else: final_else_branch,
@ -1494,7 +1542,7 @@ pub fn canonicalize_closure<'a>(
loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>,
) -> (ClosureData, Output) {
scope.inner_scope(|inner_scope| {
scope.inner_function_scope(|inner_scope| {
canonicalize_closure_body(
env,
var_store,
@ -1609,6 +1657,17 @@ fn canonicalize_closure_body<'a>(
}
}
let mut final_expr = &loc_body_expr;
while let Expr::LetRec(_, inner, _) | Expr::LetNonRec(_, inner) = &final_expr.value {
final_expr = inner;
}
if let Expr::Return { return_value, .. } = &final_expr.value {
env.problem(Problem::ReturnAtEndOfFunction {
region: return_value.region,
});
}
// store the references of this function in the Env. This information is used
// when we canonicalize a surrounding def (if it exists)
env.closures.insert(symbol, output.references.clone());
@ -1622,10 +1681,13 @@ fn canonicalize_closure_body<'a>(
output.non_closures.insert(symbol);
}
let return_type_var = var_store.fresh();
let closure_data = ClosureData {
function_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,
captured_symbols,
recursive: Recursive::NotRecursive,
@ -2028,7 +2090,8 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ TypedHole { .. }
| other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_)
| other @ Crash { .. } => other,
| other @ Crash { .. }
| other @ Return { .. } => other,
List {
elem_var,
@ -2254,6 +2317,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type,
closure_type,
return_type,
early_returns,
recursive,
name,
captured_symbols,
@ -2270,6 +2334,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type,
closure_type,
return_type,
early_returns,
recursive,
name,
captured_symbols,
@ -2487,6 +2552,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::RecordUpdater(_)
| ast::Expr::Crash
| ast::Expr::Dbg
| ast::Expr::Try
| ast::Expr::Underscore(_)
| ast::Expr::MalformedIdent(_, _)
| ast::Expr::Tag(_)
@ -2496,6 +2562,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
ast::Expr::DbgStmt(_, _)
| ast::Expr::LowLevelDbg(_, _, _)
| ast::Expr::Expect(_, _)
| ast::Expr::Return(_, _)
| ast::Expr::When(_, _)
| ast::Expr::Backpassing(_, _, _)
| ast::Expr::SpaceBefore(_, _)
@ -2796,6 +2863,7 @@ impl Declarations {
pub fn new() -> Self {
Self::with_capacity(0)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
declarations: Vec::with_capacity(capacity),
@ -2842,6 +2910,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_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,
arguments: loc_closure_data.value.arguments,
};
@ -2893,6 +2962,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_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,
arguments: loc_closure_data.value.arguments,
};
@ -3073,6 +3143,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: closure_data.closure_type,
return_type: closure_data.return_type,
early_returns: closure_data.early_returns,
captured_symbols: closure_data.captured_symbols,
arguments: closure_data.arguments,
};
@ -3113,6 +3184,7 @@ impl Declarations {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
early_returns: vec![],
name: self.symbols[index].value,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -3125,6 +3197,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_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,
arguments: loc_closure_data.value.arguments,
};
@ -3259,6 +3332,7 @@ impl DeclarationTag {
pub struct FunctionDef {
pub closure_type: Variable,
pub return_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
}
@ -3403,6 +3477,9 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
// Intentionally ignore the lookups in the nested `expect` condition itself,
// 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::Num(_, _, _, _)
| Expr::Float(_, _, _, _, _)

View file

@ -369,6 +369,12 @@ pub fn canonicalize_module_defs<'a>(
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;
// See if any of the new idents we defined went unused.
@ -425,7 +431,7 @@ pub fn canonicalize_module_defs<'a>(
..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 scope,
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: _ } => {
fix_values_captured_in_closure_expr(
&mut msg.value,

View file

@ -48,6 +48,8 @@ pub struct Scope {
/// Ignored variables (variables that start with an underscore).
/// We won't intern them because they're only used during canonicalization for error reporting.
ignored_locals: VecMap<String, Region>,
pub early_returns: Vec<(Variable, Region)>,
}
impl Scope {
@ -73,6 +75,7 @@ impl Scope {
modules: ScopeModules::new(home, module_name),
imported_symbols: default_imports,
ignored_locals: VecMap::default(),
early_returns: Vec::default(),
}
}
@ -429,7 +432,8 @@ impl Scope {
self.aliases.contains_key(&name)
}
pub fn inner_scope<F, T>(&mut self, f: F) -> T
/// Enter an inner scope within a definition, e.g. a def or when block.
pub fn inner_def_scope<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Scope) -> T,
{
@ -462,6 +466,20 @@ impl Scope {
result
}
/// Enter an inner scope within a child function, e.g. a closure body.
pub fn inner_function_scope<F, T>(&mut self, f: F) -> T
where
F: FnOnce(&mut Scope) -> T,
{
let early_returns_snapshot = std::mem::take(&mut self.early_returns);
let result = self.inner_def_scope(f);
self.early_returns = early_returns_snapshot;
result
}
pub fn register_debug_idents(&self) {
self.home.register_debug_idents(&self.locals.ident_ids)
}
@ -868,7 +886,7 @@ mod test {
}
#[test]
fn inner_scope_does_not_influence_outer() {
fn inner_def_scope_does_not_influence_outer() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
@ -882,7 +900,7 @@ mod test {
assert!(scope.lookup(&ident, region).is_err());
scope.inner_scope(|inner| {
scope.inner_def_scope(|inner| {
assert!(inner.introduce(ident.clone(), region).is_ok());
});
@ -908,7 +926,7 @@ mod test {
}
#[test]
fn idents_with_inner_scope() {
fn idents_with_inner_def_scope() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(
ModuleId::ATTR,
@ -943,7 +961,7 @@ mod test {
&[ident1.clone(), ident2.clone(), ident3.clone(),]
);
scope.inner_scope(|inner| {
scope.inner_def_scope(|inner| {
let ident4 = Ident::from("Ångström");
let ident5 = Ident::from("Sirály");

View file

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

View file

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

View file

@ -16,7 +16,7 @@ mod test_can {
use bumpalo::Bump;
use core::panic;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_can::expr::{ClosureData, IntValue, Recursive, WhenBranch};
use roc_can::pattern::Pattern;
use roc_module::called_via::CalledVia;
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
@ -504,6 +504,7 @@ mod test_can {
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_double_nested_body() {
let src = indoc!(
@ -801,6 +802,280 @@ mod test_can {
}
}
#[test]
fn try_desugar_plain_prefix() {
let src = indoc!(
r#"
try Str.toU64 "123"
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems, Vec::new());
// Assert that we desugar to:
//
// when Str.toU64 "123" is
// Ok `0` -> `0`
// Err `1` -> return Err `1`
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
let cond_args = assert_func_call(cond_expr, "toU64", CalledVia::Try, &out.interns);
assert_eq!(cond_args.len(), 1);
assert_str_value(&cond_args[0].1.value, "123");
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].patterns.len(), 1);
assert!(!branches[0].patterns[0].degenerate);
match &branches[0].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Ok");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "0", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
assert!(&branches[0].guard.is_none());
assert_var_usage(&branches[0].value.value, "0", &out.interns);
assert_eq!(branches[1].patterns.len(), 1);
assert!(!branches[1].patterns[0].degenerate);
match &branches[1].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "1", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
match &branches[1].value.value {
Expr::Return { return_value, .. } => match &return_value.value {
Expr::Tag {
name, arguments, ..
} => {
assert_eq!(name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_var_usage(&arguments[0].1.value, "1", &out.interns);
}
other_inner => panic!("Expr was not a Tag: {:?}", other_inner),
},
other_outer => panic!("Expr was not a Return: {:?}", other_outer),
}
}
#[test]
fn try_desugar_pipe_prefix() {
let src = indoc!(
r#"
"123" |> try Str.toU64
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems, Vec::new());
// Assert that we desugar to:
//
// when Str.toU64 "123" is
// Ok `0` -> `0`
// Err `1` -> return Err `1`
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
let cond_args = assert_func_call(cond_expr, "toU64", CalledVia::Try, &out.interns);
assert_eq!(cond_args.len(), 1);
assert_str_value(&cond_args[0].1.value, "123");
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].patterns.len(), 1);
assert!(!branches[0].patterns[0].degenerate);
match &branches[0].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Ok");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "0", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
assert!(&branches[0].guard.is_none());
assert_var_usage(&branches[0].value.value, "0", &out.interns);
assert_eq!(branches[1].patterns.len(), 1);
assert!(!branches[1].patterns[0].degenerate);
match &branches[1].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "1", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
match &branches[1].value.value {
Expr::Return { return_value, .. } => match &return_value.value {
Expr::Tag {
name, arguments, ..
} => {
assert_eq!(name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_var_usage(&arguments[0].1.value, "1", &out.interns);
}
other_inner => panic!("Expr was not a Tag: {:?}", other_inner),
},
other_outer => panic!("Expr was not a Return: {:?}", other_outer),
}
}
#[test]
fn try_desugar_pipe_suffix() {
let src = indoc!(
r#"
Str.toU64 "123" |> try
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems, Vec::new());
// Assert that we desugar to:
//
// when Str.toU64 "123" is
// Ok `0` -> `0`
// Err `1` -> return Err `1`
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
let cond_args = assert_func_call(cond_expr, "toU64", CalledVia::Space, &out.interns);
assert_eq!(cond_args.len(), 1);
assert_str_value(&cond_args[0].1.value, "123");
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].patterns.len(), 1);
assert!(!branches[0].patterns[0].degenerate);
match &branches[0].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Ok");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "0", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
assert!(&branches[0].guard.is_none());
assert_var_usage(&branches[0].value.value, "0", &out.interns);
assert_eq!(branches[1].patterns.len(), 1);
assert!(!branches[1].patterns[0].degenerate);
match &branches[1].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "1", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
match &branches[1].value.value {
Expr::Return { return_value, .. } => match &return_value.value {
Expr::Tag {
name, arguments, ..
} => {
assert_eq!(name.0.to_string(), "Err");
assert_eq!(arguments.len(), 1);
assert_var_usage(&arguments[0].1.value, "1", &out.interns);
}
other_inner => panic!("Expr was not a Tag: {:?}", other_inner),
},
other_outer => panic!("Expr was not a Return: {:?}", other_outer),
}
}
#[test]
fn try_desugar_works_elsewhere() {
let src = indoc!(
r#"
when Foo 123 is
Foo try -> try
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems, Vec::new());
// Assert that we don't treat `try` as a keyword here
// by desugaring to:
//
// when Foo 123 is
// Foo try -> try
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
match cond_expr {
Expr::Tag {
name, arguments, ..
} => {
assert_eq!(name.0.to_string(), "Foo");
assert_eq!(arguments.len(), 1);
assert_num_value(&arguments[0].1.value, 123);
}
_ => panic!("cond_expr was not a Tag: {:?}", cond_expr),
}
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].patterns.len(), 1);
assert!(!branches[0].patterns[0].degenerate);
match &branches[0].patterns[0].pattern.value {
Pattern::AppliedTag {
tag_name,
arguments,
..
} => {
assert_eq!(tag_name.0.to_string(), "Foo");
assert_eq!(arguments.len(), 1);
assert_pattern_name(&arguments[0].1.value, "try", &out.interns);
}
other => panic!("First argument was not an applied tag: {:?}", other),
}
assert_var_usage(&branches[0].value.value, "try", &out.interns);
assert!(&branches[0].guard.is_none());
}
fn assert_num_value(expr: &Expr, num: usize) {
match expr {
Expr::Num(_, num_str, _, _) => {
@ -810,6 +1085,15 @@ mod test_can {
}
}
fn assert_str_value(expr: &Expr, str_val: &str) {
match expr {
Expr::Str(str_expr) => {
assert_eq!(&**str_expr, str_val)
}
_ => panic!("Expr wasn't a Str with value {str_val}: {:?}", expr),
}
}
fn assert_var_usage(expr: &Expr, name: &str, interns: &roc_module::symbol::Interns) {
match expr {
Expr::Var(sym, _) => assert_eq!(sym.as_str(interns), name),
@ -835,7 +1119,10 @@ mod test_can {
args.clone()
}
_ => panic!("Expr was not a RecordBuilder Call: {:?}", expr),
_ => panic!(
"Expr was not a Call with CalledVia={:?}: {:?}",
called_via, expr
),
}
}
@ -846,6 +1133,15 @@ mod test_can {
}
}
fn assert_when(expr: &Expr) -> (&Expr, &Vec<WhenBranch>) {
match expr {
Expr::When {
loc_cond, branches, ..
} => (&loc_cond.value, branches),
_ => panic!("Expr was not a When: {:?}", expr),
}
}
// TAIL CALLS
fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
match expr {

View file

@ -496,20 +496,6 @@
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTuple"
]
}
}
},
{
"type": "object",
"required": [

View file

@ -123,7 +123,6 @@ impl AsSchema<Content> for subs::FlatType {
ext.as_schema(subs),
),
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
subs::FlatType::EmptyTuple => Content::EmptyTuple(),
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
}
}

View file

@ -221,9 +221,6 @@ export default function DrawHeadConstructor({
case "EmptyRecord": {
return <>{"{}"}</>;
}
case "EmptyTuple": {
return <>()</>;
}
case "EmptyTagUnion": {
return <>[]</>;
}

View file

@ -56,8 +56,6 @@ export function contentStyles(desc: TypeDescriptor | undefined): ContentStyles {
return { name: "", bg: "bg-lime-400" };
case "EmptyRecord":
return { name: "{}", bg: "bg-purple-400" };
case "EmptyTuple":
return { name: "()", bg: "bg-deep-purple-400" };
case "EmptyTagUnion":
return { name: "[]", bg: "bg-cyan-200" };
case "Error":

View file

@ -251,7 +251,6 @@ function VariableNodeContent(
return {};
}
case "EmptyRecord":
case "EmptyTuple":
case "EmptyTagUnion":
case "Error": {
return {};

View file

@ -148,10 +148,6 @@ export type Content =
type: "EmptyRecord";
[k: string]: unknown;
}
| {
type: "EmptyTuple";
[k: string]: unknown;
}
| {
type: "EmptyTagUnion";
[k: string]: unknown;

View file

@ -97,7 +97,6 @@ impl_content! {
extension: TagUnionExtension,
},
EmptyRecord {},
EmptyTuple {},
EmptyTagUnion {},
RangedNumber {
range: NumericRange,

View file

@ -113,6 +113,7 @@ fn constrain_untyped_closure(
fn_var: Variable,
closure_var: Variable,
ret_var: Variable,
early_returns: &[(Variable, Region)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
loc_body_expr: &Loc<Expr>,
captured_symbols: &[(Symbol, Variable)],
@ -134,7 +135,12 @@ fn constrain_untyped_closure(
vars.push(closure_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::FunctionOutput,
return_type_index,
loc_body_expr.region,
));
let ret_constraint = constrain_expr(
types,
constraints,
@ -144,6 +150,21 @@ fn constrain_untyped_closure(
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!
debug_assert_eq!(captured_symbols.to_vec(), {
let mut copy = captured_symbols.to_vec();
@ -185,6 +206,7 @@ fn constrain_untyped_closure(
region,
fn_var,
),
early_returns_constraint,
closure_constraint,
];
@ -624,6 +646,7 @@ pub fn constrain_expr(
function_type: fn_var,
closure_type: closure_var,
return_type: ret_var,
early_returns,
arguments,
loc_body: boxed,
captured_symbols,
@ -640,6 +663,7 @@ pub fn constrain_expr(
*fn_var,
*closure_var,
*ret_var,
early_returns,
arguments,
boxed,
captured_symbols,
@ -1378,6 +1402,29 @@ pub fn constrain_expr(
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::FunctionOutput,
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_union_var: variant_var,
ext_var,
@ -1870,6 +1917,7 @@ fn constrain_function_def(
expr_var,
function_def.closure_type,
function_def.return_type,
&function_def.early_returns,
&function_def.arguments,
loc_body_expr,
&function_def.captured_symbols,
@ -2071,6 +2119,7 @@ fn constrain_function_def(
expr_var,
function_def.closure_type,
function_def.return_type,
&function_def.early_returns,
&function_def.arguments,
loc_expr,
&function_def.captured_symbols,
@ -3651,6 +3700,7 @@ fn constraint_recursive_function(
expr_var,
function_def.closure_type,
function_def.return_type,
&function_def.early_returns,
&function_def.arguments,
loc_expr,
&function_def.captured_symbols,
@ -4133,6 +4183,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
| Expect { .. }
| ExpectFx { .. }
| Dbg { .. }
| Return { .. }
| TypedHole(_)
| RuntimeError(..)
| ZeroArgumentTag { .. }

View file

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

View file

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

View file

@ -556,6 +556,7 @@ fn step_elem(
function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var,
early_returns: vec![],
name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)],
recursive: Recursive::NotRecursive,
@ -710,6 +711,7 @@ fn step_elem(
function_type,
closure_type,
return_type: keep_or_skip_var,
early_returns: vec![],
name: step_elem_closure,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
@ -896,6 +898,7 @@ fn finalizer(
function_type: function_var,
closure_type,
return_type: return_type_var,
early_returns: vec![],
name: function_symbol,
captured_symbols: Vec::new(),
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,
closure_type: to_elem_encoder_lset,
return_type: elem_encoder_var,
early_returns: vec![],
name: to_elem_encoder_sym,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -281,6 +282,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -490,6 +492,7 @@ fn to_encoder_record(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -672,6 +675,7 @@ fn to_encoder_tuple(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -914,6 +918,7 @@ fn to_encoder_tag_union(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
@ -1025,6 +1030,7 @@ fn wrap_in_encode_custom(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: Variable::LIST_U8,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![(captured_symbol, captured_var)],
recursive: Recursive::NotRecursive,

View file

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

View file

@ -66,9 +66,7 @@ impl FlatDecodable {
FlatType::Tuple(elems, ext) => {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
check_derivable_ext_var(subs, ext, |_| false)?;
Ok(Key(FlatDecodableKey::Tuple(elems_iter.count() as _)))
}
@ -79,7 +77,6 @@ impl FlatDecodable {
Err(Underivable) // yet
}
FlatType::EmptyRecord => Ok(Key(FlatDecodableKey::Record(vec![]))),
FlatType::EmptyTuple => todo!(),
FlatType::EmptyTagUnion => {
Err(Underivable) // yet
}

View file

@ -73,9 +73,7 @@ impl FlatEncodable {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
// TODO someday we can put #[cfg(debug_assertions)] around this, but for now let's always do it.
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
check_derivable_ext_var(subs, ext, |_| false)?;
Ok(Key(FlatEncodableKey::Tuple(elems_iter.count() as _)))
}
@ -120,7 +118,6 @@ impl FlatEncodable {
FlatType::EmptyRecord => Ok(Key(FlatEncodableKey::Record(vec![]))),
FlatType::EmptyTagUnion => Ok(Key(FlatEncodableKey::TagUnion(vec![]))),
FlatType::Func(..) => Err(Underivable),
FlatType::EmptyTuple => unreachable!("Somehow Encoding derivation got an expression that's an empty tuple, which shouldn't be possible!"),
},
Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
Some(lambda) => lambda,

View file

@ -70,9 +70,7 @@ impl FlatHash {
FlatType::Tuple(elems, ext) => {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
})?;
check_derivable_ext_var(subs, ext, |_| false)?;
Ok(Key(FlatHashKey::Tuple(elems_iter.count() as _)))
}
@ -112,7 +110,6 @@ impl FlatHash {
.collect(),
))),
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
FlatType::EmptyTuple => todo!(),
FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))),
//
FlatType::Func(..) => Err(Underivable),

View file

@ -82,8 +82,8 @@ impl FlatInspectable {
let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
// TODO someday we can put #[cfg(debug_assertions)] around this, but for now let's always do it.
check_derivable_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTuple))
check_derivable_ext_var(subs, ext, |_| {
false
}).expect("Compiler error: unexpected nonempty ext var when deriving Inspect for tuple");
Key(FlatInspectableKey::Tuple(elems_iter.count() as _))
@ -128,26 +128,24 @@ impl FlatInspectable {
}
FlatType::EmptyRecord => Key(FlatInspectableKey::Record(Vec::new())),
FlatType::EmptyTagUnion => Key(FlatInspectableKey::TagUnion(Vec::new())),
FlatType::Func(..) => {
Immediate(Symbol::INSPECT_FUNCTION)
}
FlatType::EmptyTuple => unreachable!("Somehow Inspect derivation got an expression that's an empty tuple, which shouldn't be possible!"),
FlatType::Func(..) => Immediate(Symbol::INSPECT_FUNCTION),
},
Content::Alias(sym, _, real_var, kind) => match Self::from_builtin_alias(sym) {
Some(lambda) => lambda,
_ => {
match kind {
AliasKind::Structural => {
Self::from_var(subs, real_var)
}
AliasKind::Structural => Self::from_var(subs, real_var),
// Special case, an unbound `Frac *` will become a `Dec`.
AliasKind::Opaque if matches!(*subs.get_content_without_compacting(real_var), Content::FlexVar(_) | Content::FlexAbleVar(_, _)) => {
AliasKind::Opaque
if matches!(
*subs.get_content_without_compacting(real_var),
Content::FlexVar(_) | Content::FlexAbleVar(_, _)
) =>
{
Immediate(Symbol::INSPECT_DEC)
}
AliasKind::Opaque if sym.is_builtin() => {
Self::from_var(subs, real_var)
}
AliasKind::Opaque if sym.is_builtin() => Self::from_var(subs, real_var),
AliasKind::Opaque => {
// There are two cases in which `Inspect` can be derived for an opaque
// type.
@ -169,7 +167,6 @@ impl FlatInspectable {
}
}
}
},
Content::RangedNumber(range) => {
Self::from_var(subs, range.default_compilation_variable())
@ -180,7 +177,8 @@ impl FlatInspectable {
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::LambdaSet(_) | Content::ErasedLambda => {
| Content::LambdaSet(_)
| Content::ErasedLambda => {
unreachable!("There must have been a bug in the solver, because we're trying to derive Inspect on a non-concrete type.");
}
}

View file

@ -14,6 +14,7 @@ use roc_parse::ast::{
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor;
use roc_parse::keyword;
use roc_region::all::Loc;
impl<'a> Formattable for Expr<'a> {
@ -47,7 +48,8 @@ impl<'a> Formattable for Expr<'a> {
| Tag(_)
| OpaqueRef(_)
| Crash
| Dbg => false,
| Dbg
| Try => false,
RecordAccess(inner, _) | TupleAccess(inner, _) | TrySuffix { expr: inner, .. } => {
inner.is_multiline()
@ -70,6 +72,9 @@ impl<'a> Formattable for Expr<'a> {
LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
),
Return(return_value, after_return) => {
return_value.is_multiline() || after_return.is_some()
}
If {
if_thens: branches,
@ -195,6 +200,10 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent);
buf.push_str("crash");
}
Try => {
buf.indent(indent);
buf.push_str("try");
}
Apply(loc_expr, loc_args, _) => {
// Sadly this assertion fails in practice. The fact that the parser produces code like this is going to
// confuse the formatter, because it depends on being able to "see" spaces that logically come before the inner
@ -452,6 +461,9 @@ impl<'a> Formattable for Expr<'a> {
LowLevelDbg(_, _, _) => unreachable!(
"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_thens: branches,
final_else,
@ -1064,6 +1076,33 @@ fn fmt_expect<'a>(
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);
let return_indent = if return_value.is_multiline() {
indent + INDENT
} else {
indent
};
return_value.format(buf, return_indent);
if let Some(after_return) = after_return {
after_return.format_with_options(buf, parens, newlines, indent);
}
}
fn fmt_if<'a>(
buf: &mut Buf,
branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],

View file

@ -14426,4 +14426,192 @@ All branches in an `if` must have the same type!
// ),
// @r""
// );
test_report!(
issue_6240_1,
indoc!(
r"
{}.abcde
"
),
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This record doesnt have a `abcde` field:
4 {}.abcde
^^^^^^^^
In fact, its a record with no fields at all!
"###
);
test_report!(
issue_6240_2,
indoc!(
r#"
("", "").abcde
"#
),
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression is used in an unexpected way:
4 ("", "").abcde
^^^^^^^^^^^^^^
It is a tuple of type:
(
Str,
Str,
)a
But you are trying to use it as:
{ abcde : * }b
"###
);
test_report!(
issue_6240_3,
indoc!(
r"
{}.0
"
),
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This expression is used in an unexpected way:
4 {}.0
^^^^
It is a record of type:
{}
But you are trying to use it as:
(*)b
"###
);
test_report!(
return_outside_of_function,
indoc!(
r"
someVal =
if 10 > 5 then
x = 5
return x
else
6
someVal + 2
"
),
@r###"
RETURN OUTSIDE OF FUNCTION in /code/proj/Main.roc
This `return` statement doesn't belong to a function:
7 return x
^^^^^^^^
I wouldn't know where to return to if I used it!
"###
);
test_report!(
statements_after_return,
indoc!(
r#"
myFunction = \x ->
if x == 2 then
return x
log! "someData"
useX x 123
else
x + 5
myFunction 2
"#
),
@r###"
UNREACHABLE CODE in /code/proj/Main.roc
This code won't run because it follows a `return` statement:
6> return x
7>
8> log! "someData"
9> useX x 123
Hint: you can move the `return` statement below this block to make the
code that follows it run.
"###
);
test_report!(
return_at_end_of_function,
indoc!(
r#"
myFunction = \x ->
y = Num.toStr x
return y
myFunction 3
"#
),
@r###"
UNNECESSARY RETURN in /code/proj/Main.roc
This `return` keyword is redundant:
7 return y
^^^^^^^^
The last expression in a function is treated like a `return` statement.
You can safely remove `return` here.
"###
);
test_report!(
mismatch_early_return_with_function_output,
indoc!(
r#"
myFunction = \x ->
if x == 5 then
return "abc"
else
x
myFunction 3
"#
),
@r###"
TYPE MISMATCH in /code/proj/Main.roc
This `return` statement doesn't match the return type of its enclosing
function:
5 if x == 5 then
6> return "abc"
7 else
8 x
This returns a value of type:
Str
But I expected the function to have return type:
Num *
"###
);
}

View file

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

View file

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

View file

@ -98,6 +98,16 @@ pub enum CalledVia {
/// This call is a result of lowering a reference to a module-params-extended def
NakedParamsVar,
/// This call is the result of desugaring a `try` expression into an early return on Err
/// e.g. `try parseDate input` becomes:
///
/// ```roc
/// when parseDate input is
/// Err err -> return Err err
/// Ok value -> value
/// ```
Try,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -4978,12 +4978,17 @@ pub fn with_hole<'a>(
}
}
let struct_index = match index {
Some(index) => index,
None => return runtime_error(env, "No such field in record"),
};
compile_struct_like_access(
env,
procs,
layout_cache,
field_layouts,
index.expect("field not in its own type") as _,
struct_index,
*loc_expr,
record_var,
hole,
@ -5076,12 +5081,17 @@ pub fn with_hole<'a>(
}
}
let tuple_index = match final_index {
Some(index) => index as u64,
None => return runtime_error(env, "No such index in tuple"),
};
compile_struct_like_access(
env,
procs,
layout_cache,
field_layouts,
final_index.expect("elem not in its own type") as u64,
tuple_index,
*loc_expr,
tuple_var,
hole,
@ -5878,6 +5888,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"),
RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())),
Crash { msg, ret_var: _ } => {
@ -8055,7 +8087,10 @@ fn can_reuse_symbol<'a>(
.enumerate()
.find_map(|(current, (label, _, _))| (label == *field).then_some(current));
let struct_index = index.expect("field not in its own type");
let struct_index = match index {
Some(index) => index as u64,
None => return NotASymbol,
};
let struct_symbol = possible_reuse_symbol_or_specialize(
env,
@ -10136,7 +10171,6 @@ fn find_lambda_sets_help(
}
}
FlatType::EmptyRecord => {}
FlatType::EmptyTuple => {}
FlatType::EmptyTagUnion => {}
},
Content::Alias(_, _, actual, _) => {

View file

@ -2191,7 +2191,7 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
}
stack.push((ext.var(), depth_any + 1, depth_lset));
}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
},
Content::FlexVar(_)
| Content::RigidVar(_)
@ -3459,7 +3459,6 @@ fn layout_from_flat_type<'a>(
}
EmptyTagUnion => cacheable(Ok(Layout::VOID)),
EmptyRecord => cacheable(Ok(Layout::UNIT)),
EmptyTuple => cacheable(Ok(Layout::UNIT)),
}
}

View file

@ -496,6 +496,9 @@ pub enum Expr<'a> {
// This form of debug is a desugared call to roc_dbg
LowLevelDbg(&'a (&'a str, &'a str), &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
/// The `try` keyword that performs early return on errors
Try,
// Application
/// To apply by name, do Apply(Var(...), ...)
/// To apply a tag by name, do Apply(Tag(...), ...)
@ -520,6 +523,13 @@ pub enum Expr<'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.
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a Expr<'a>, &'a [CommentOrNewline<'a>]),
@ -665,10 +675,14 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::Dbg => false,
Expr::DbgStmt(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::Try => false,
Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value),
Expr::When(cond, branches) => {
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::SpaceAfter(a, _) => is_expr_suffixed(a),
Expr::MalformedIdent(_, _) => false,
@ -938,6 +952,15 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&condition.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.push(&return_value.value);
}
}
Apply(fun, args, _) => {
expr_stack.reserve(args.len() + 1);
expr_stack.push(&fun.value);
@ -1008,6 +1031,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
| Underscore(_)
| Crash
| Dbg
| Try
| Tag(_)
| OpaqueRef(_)
| MalformedIdent(_, _)
@ -2464,6 +2488,8 @@ impl<'a> Malformed for Expr<'a> {
Dbg => false,
DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
Try => false,
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()),
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),
UnaryOp(expr, _) => 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,
skip_second, specialize_err, specialize_err_ref, then, two_bytes, zero_or_more, EClosure,
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::state::State;
@ -195,6 +195,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc(specialize_err(EExpr::Closure, closure_help(options))),
loc(crash_kw()),
loc(specialize_err(EExpr::Dbg, dbg_kw())),
loc(try_kw()),
loc(underscore_expression()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
@ -217,6 +218,7 @@ fn loc_term_or_underscore<'a>(
)),
loc(specialize_err(EExpr::Closure, closure_help(options))),
loc(specialize_err(EExpr::Dbg, dbg_kw())),
loc(try_kw()),
loc(underscore_expression()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
@ -235,6 +237,7 @@ fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EEx
)),
loc(specialize_err(EExpr::Closure, closure_help(options))),
loc(specialize_err(EExpr::Dbg, dbg_kw())),
loc(try_kw()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
ident_seq(),
@ -546,6 +549,7 @@ fn stmt_start<'a>(
EExpr::Dbg,
dbg_stmt_help(options, preceding_comment)
)),
loc(specialize_err(EExpr::Return, return_help(options))),
loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))),
map(
loc(specialize_err(EExpr::Closure, closure_help(options))),
@ -1443,6 +1447,7 @@ fn parse_stmt_operator<'a>(
let op_start = loc_op.region.start();
let op_end = loc_op.region.end();
let new_start = state.pos();
match op {
OperatorOrDef::BinOp(BinOp::Minus) if expr_state.end != op_start && op_end == new_start => {
parse_negated_term(
@ -2127,6 +2132,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
pattern
}
Expr::Try => Pattern::Identifier { ident: "try" },
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) | Expr::ParensAround(..) => unreachable!(),
Expr::Record(fields) => {
@ -2172,6 +2179,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::Dbg
| Expr::DbgStmt(_, _)
| Expr::LowLevelDbg(_, _, _)
| Expr::Return(_, _)
| Expr::MalformedClosure
| Expr::MalformedSuffixed(..)
| Expr::PrecedenceConflict { .. }
@ -2644,6 +2652,33 @@ 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::Expr(Expr::Return(
arena.alloc(Loc::at(region, return_value.value)),
None,
));
Ok((MadeProgress, stmt, state))
})
.trace("return_help")
}
fn dbg_stmt_help<'a>(
options: ExprParseOptions,
preceding_comment: Region,
@ -2682,6 +2717,16 @@ fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
.trace("dbg_kw")
}
fn try_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, _, next_state) =
parser::keyword("try", EExpr::Try).parse(arena, state, min_indent)?;
Ok((MadeProgress, Expr::Try, next_state))
})
.trace("try_kw")
}
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
skip_second(
skip_first(
@ -3060,6 +3105,20 @@ fn stmts_to_defs<'a>(
while i < stmts.len() {
let sp_stmt = stmts[i];
match sp_stmt.item.value {
Stmt::Expr(Expr::Return(return_value, _after_return)) => {
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::Expr(e) => {
if is_expr_suffixed(&e) && i + 1 < stmts.len() {
defs.push_value_def(

View file

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

View file

@ -22,9 +22,9 @@ use crate::{
parser::{
EAbility, EClosure, EExpect, EExposes, EExpr, EHeader, EIf, EImport, EImportParams,
EImports, EInParens, EList, EPackageEntry, EPackageName, EPackages, EParams, EPattern,
EProvides, ERecord, ERequires, EString, EType, ETypeAbilityImpl, ETypeApply, ETypeInParens,
ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ETypedIdent, EWhen, PInParens, PList,
PRecord, SyntaxError,
EProvides, ERecord, ERequires, EReturn, EString, EType, ETypeAbilityImpl, ETypeApply,
ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ETypedIdent, EWhen, PInParens,
PList, PRecord, SyntaxError,
},
};
@ -756,6 +756,11 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(a.normalize(arena)),
arena.alloc(b.normalize(arena)),
),
Expr::Try => Expr::Try,
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(arena.alloc(a.normalize(arena)), b.normalize(arena), c)
}
@ -1038,6 +1043,9 @@ impl<'a> Normalize<'a> for EExpr<'a> {
EExpr::Expect(inner_err, _pos) => {
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::Import(inner_err, _pos) => {
EExpr::Import(inner_err.normalize(arena), Position::zero())
@ -1047,6 +1055,7 @@ impl<'a> Normalize<'a> for EExpr<'a> {
}
EExpr::Underscore(_pos) => EExpr::Underscore(Position::zero()),
EExpr::Crash(_pos) => EExpr::Crash(Position::zero()),
EExpr::Try(_pos) => EExpr::Try(Position::zero()),
EExpr::InParens(inner_err, _pos) => {
EExpr::InParens(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> {
fn normalize(&self, arena: &'a Bump) -> Self {
match self {

View file

@ -97,6 +97,7 @@ impl_space_problem! {
EPattern<'a>,
EProvides<'a>,
ERecord<'a>,
EReturn<'a>,
ERequires<'a>,
EString<'a>,
EType<'a>,
@ -337,10 +338,12 @@ pub enum EExpr<'a> {
Expect(EExpect<'a>, Position),
Dbg(EExpect<'a>, Position),
Import(EImport<'a>, Position),
Return(EReturn<'a>, Position),
Closure(EClosure<'a>, Position),
Underscore(Position),
Crash(Position),
Try(Position),
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),
@ -513,6 +516,14 @@ pub enum EExpect<'a> {
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)]
pub enum EImport<'a> {
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,15 @@ pub enum Problem {
one_occurrence: Region,
kind: AliasKind,
},
ReturnOutsideOfFunction {
region: Region,
},
StatementsAfterReturn {
region: Region,
},
ReturnAtEndOfFunction {
region: Region,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -323,6 +332,9 @@ impl Problem {
Problem::OverAppliedDbg { .. } => RuntimeError,
Problem::DefsOnlyUsedInRecursion(_, _) => Warning,
Problem::FileProblem { .. } => Fatal,
Problem::ReturnOutsideOfFunction { .. } => Warning,
Problem::StatementsAfterReturn { .. } => Warning,
Problem::ReturnAtEndOfFunction { .. } => Warning,
}
}
@ -485,7 +497,10 @@ impl Problem {
| Problem::UnappliedCrash { region }
| Problem::OverAppliedDbg { region }
| Problem::UnappliedDbg { region }
| Problem::DefsOnlyUsedInRecursion(_, region) => Some(*region),
| Problem::DefsOnlyUsedInRecursion(_, region)
| Problem::ReturnOutsideOfFunction { region }
| Problem::StatementsAfterReturn { region }
| Problem::ReturnAtEndOfFunction { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region)

View file

@ -612,14 +612,6 @@ trait DerivableVisitor {
})
}
#[inline(always)]
fn visit_empty_tuple(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_empty_tag_union(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
@ -786,7 +778,6 @@ trait DerivableVisitor {
}
}
EmptyRecord => Self::visit_empty_record(var)?,
EmptyTuple => Self::visit_empty_tuple(var)?,
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
},
Alias(

View file

@ -186,7 +186,7 @@ fn deep_copy_var_help(
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTagUnion => same,
Record(fields, ext_var) => {
let record_fields = {

View file

@ -2184,7 +2184,7 @@ fn adjust_rank_content(
rank
}
EmptyRecord | EmptyTuple => {
EmptyRecord => {
// from elm-compiler: THEORY: an empty record never needs to get generalized
//
// But for us, that theory does not hold, because there might be type variables hidden

View file

@ -0,0 +1,109 @@
#![cfg(not(feature = "gen-wasm"))]
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::identity;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::identity;
#[allow(unused_imports)]
use indoc::indoc;
#[allow(unused_imports)]
use roc_std::{RocList, RocResult, RocStr, I128, U128};
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn early_return_nested_ifs() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
displayN = \n ->
first = Num.toStr n
second =
if n == 1 then
return "early 1"
else
third = Num.toStr (n + 1)
if n == 2 then
return "early 2"
else
third
"$(first), $(second)"
main : List Str
main = List.map [1, 2, 3] displayN
"#
),
RocList::from_slice(&[
RocStr::from("early 1"),
RocStr::from("early 2"),
RocStr::from("3, 4")
]),
RocList<RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn early_return_nested_whens() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
displayN = \n ->
first = Num.toStr n
second =
when n is
1 ->
return "early 1"
_ ->
third = Num.toStr (n + 1)
when n is
2 ->
return "early 2"
_ ->
third
"$(first), $(second)"
main : List Str
main = List.map [1, 2, 3] displayN
"#
),
RocList::from_slice(&[
RocStr::from("early 1"),
RocStr::from("early 2"),
RocStr::from("3, 4")
]),
RocList<RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn early_return_solo() {
assert_evals_to!(
r#"
identity = \x ->
return x
identity "abc"
"#,
RocStr::from("abc"),
RocStr,
identity,
true
);
}

View file

@ -16,6 +16,7 @@ pub mod gen_primitives;
pub mod gen_records;
pub mod gen_refcount;
pub mod gen_result;
pub mod gen_return;
pub mod gen_set;
pub mod gen_str;
pub mod gen_tags;

View file

@ -0,0 +1,53 @@
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.24 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.24;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.283 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.283;
procedure Num.96 (#Attr.2):
let Num.282 : Str = lowlevel NumToStr #Attr.2;
ret Num.282;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.247 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.247;
procedure Test.1 (Test.2):
let Test.3 : Str = CallByName Num.96 Test.2;
joinpoint Test.12 Test.4:
let Test.10 : Str = ", ";
let Test.9 : Str = CallByName Str.3 Test.10 Test.4;
dec Test.4;
let Test.8 : Str = CallByName Str.3 Test.3 Test.9;
dec Test.9;
ret Test.8;
in
let Test.22 : I64 = 1i64;
let Test.20 : Int1 = CallByName Bool.11 Test.2 Test.22;
if Test.20 then
dec Test.3;
let Test.21 : Str = "early 1";
ret Test.21;
else
let Test.19 : I64 = 1i64;
let Test.18 : I64 = CallByName Num.19 Test.2 Test.19;
let Test.5 : Str = CallByName Num.96 Test.18;
joinpoint Test.14 Test.11:
jump Test.12 Test.11;
in
let Test.17 : I64 = 2i64;
let Test.15 : Int1 = CallByName Bool.11 Test.2 Test.17;
if Test.15 then
dec Test.3;
dec Test.5;
let Test.16 : Str = "early 2";
ret Test.16;
else
jump Test.14 Test.5;
procedure Test.0 ():
let Test.7 : I64 = 3i64;
let Test.6 : Str = CallByName Test.1 Test.7;
ret Test.6;

View file

@ -3672,3 +3672,26 @@ fn issue_6606_2() {
"
)
}
#[mono_test]
fn dec_refcount_for_usage_after_early_return_in_if() {
indoc!(
r#"
displayN = \n ->
first = Num.toStr n
second =
if n == 1 then
return "early 1"
else
third = Num.toStr (n + 1)
if n == 2 then
return "early 2"
else
third
"$(first), $(second)"
displayN 3
"#
)
}

View file

@ -0,0 +1 @@
Expr(Return(IndentReturnValue(@6), @0), @0)

View file

@ -0,0 +1 @@
return

View file

@ -96,10 +96,7 @@ SpaceAfter(
"",
),
@50-74 Apply(
@50-53 Var {
module_name: "",
ident: "try",
},
@50-53 Try,
[
@54-57 Var {
module_name: "",

View file

@ -0,0 +1,10 @@
maybeEarlyReturn = \x ->
y =
if x > 5 then
return "abc"
else
x + 2
Num.toStr y
maybeEarlyReturn 10

View file

@ -0,0 +1,167 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-127,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-16 Identifier {
ident: "maybeEarlyReturn",
},
@19-127 Closure(
[
@20-21 Identifier {
ident: "x",
},
],
@29-127 SpaceBefore(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@29-110,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@29-30 Identifier {
ident: "y",
},
@41-110 SpaceBefore(
If {
if_thens: [
(
@44-49 BinOps(
[
(
@44-45 Var {
module_name: "",
ident: "x",
},
@46-47 GreaterThan,
),
],
@48-49 Num(
"5",
),
),
@67-79 SpaceBefore(
SpaceAfter(
Return(
@67-79 Str(
PlainLine(
"abc",
),
),
None,
),
[
Newline,
],
),
[
Newline,
],
),
),
],
final_else: @105-110 SpaceBefore(
BinOps(
[
(
@105-106 Var {
module_name: "",
ident: "x",
},
@107-108 Plus,
),
],
@109-110 Num(
"2",
),
),
[
Newline,
],
),
indented_else: false,
},
[
Newline,
],
),
),
],
},
@116-127 SpaceBefore(
Apply(
@116-125 Var {
module_name: "Num",
ident: "toStr",
},
[
@126-127 Var {
module_name: "",
ident: "y",
},
],
Space,
),
[
Newline,
Newline,
],
),
),
[
Newline,
],
),
),
),
],
},
@129-148 SpaceBefore(
Apply(
@129-145 Var {
module_name: "",
ident: "maybeEarlyReturn",
},
[
@146-148 Num(
"10",
),
],
Space,
),
[
Newline,
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,10 @@
maybeEarlyReturn = \x ->
y =
if x > 5 then
return "abc"
else
x + 2
Num.toStr y
maybeEarlyReturn 10

View file

@ -0,0 +1,11 @@
staticValueDef =
someVal =
if 10 > 5 then
x = 5
return x
else
6
someVal + 2
staticValueDef

View file

@ -0,0 +1,169 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-142,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-14 Identifier {
ident: "staticValueDef",
},
@21-142 SpaceBefore(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@21-125,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@21-28 Identifier {
ident: "someVal",
},
@39-125 SpaceBefore(
If {
if_thens: [
(
@42-48 BinOps(
[
(
@42-44 Num(
"10",
),
@45-46 GreaterThan,
),
],
@47-48 Num(
"5",
),
),
@67-97 SpaceBefore(
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@67-72,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@67-68 Identifier {
ident: "x",
},
@71-72 Num(
"5",
),
),
],
},
Return(
@86-97 Var {
module_name: "",
ident: "x",
},
None,
),
),
[
Newline,
],
),
[
Newline,
],
),
),
],
final_else: @124-125 SpaceBefore(
Num(
"6",
),
[
Newline,
],
),
indented_else: false,
},
[
Newline,
],
),
),
],
},
@131-142 SpaceBefore(
BinOps(
[
(
@131-138 Var {
module_name: "",
ident: "someVal",
},
@139-140 Plus,
),
],
@141-142 Num(
"2",
),
),
[
Newline,
Newline,
],
),
),
[
Newline,
],
),
),
],
},
@145-159 SpaceBefore(
Var {
module_name: "",
ident: "staticValueDef",
},
[
Newline,
Newline,
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,12 @@
staticValueDef =
someVal =
if 10 > 5 then
x = 5
return x
else
6
someVal + 2
staticValueDef

View file

@ -0,0 +1,11 @@
maybeEarlyReturn = \x ->
y =
when x is
5 ->
return "abc"
_ -> x + 2
Num.toStr y
maybeEarlyRetun 3

View file

@ -0,0 +1,177 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-154,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-16 Identifier {
ident: "maybeEarlyReturn",
},
@19-154 Closure(
[
@20-21 Identifier {
ident: "x",
},
],
@29-154 SpaceBefore(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@29-136,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@29-30 Identifier {
ident: "y",
},
@37-136 SpaceBefore(
When(
@42-43 Var {
module_name: "",
ident: "x",
},
[
WhenBranch {
patterns: [
@55-56 SpaceBefore(
NumLiteral(
"5",
),
[
Newline,
],
),
],
value: @80-116 SpaceBefore(
Return(
@80-116 SpaceBefore(
Str(
PlainLine(
"abc",
),
),
[
Newline,
],
),
None,
),
[
Newline,
],
),
guard: None,
},
WhenBranch {
patterns: [
@126-127 SpaceBefore(
Underscore(
"",
),
[
Newline,
Newline,
],
),
],
value: @131-136 BinOps(
[
(
@131-132 Var {
module_name: "",
ident: "x",
},
@133-134 Plus,
),
],
@135-136 Num(
"2",
),
),
guard: None,
},
],
),
[
Newline,
],
),
),
],
},
@143-154 SpaceBefore(
Apply(
@143-152 Var {
module_name: "Num",
ident: "toStr",
},
[
@153-154 Var {
module_name: "",
ident: "y",
},
],
Space,
),
[
Newline,
Newline,
Newline,
],
),
),
[
Newline,
],
),
),
),
],
},
@156-173 SpaceBefore(
Apply(
@156-171 Var {
module_name: "",
ident: "maybeEarlyRetun",
},
[
@172-173 Num(
"3",
),
],
Space,
),
[
Newline,
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,13 @@
maybeEarlyReturn = \x ->
y =
when x is
5 ->
return
"abc"
_ -> x + 2
Num.toStr y
maybeEarlyRetun 3

View file

@ -0,0 +1,3 @@
return something
|> pipeToFunction
|> andAnother

View file

@ -0,0 +1,45 @@
SpaceAfter(
Return(
@0-84 SpaceBefore(
BinOps(
[
(
@15-24 SpaceAfter(
Var {
module_name: "",
ident: "something",
},
[
Newline,
],
),
@37-39 Pizza,
),
(
@40-54 SpaceAfter(
Var {
module_name: "",
ident: "pipeToFunction",
},
[
Newline,
],
),
@71-73 Pizza,
),
],
@74-84 Var {
module_name: "",
ident: "andAnother",
},
),
[
Newline,
],
),
None,
),
[
Newline,
],
)

View file

@ -0,0 +1,4 @@
return
something
|> pipeToFunction
|> andAnother

View file

@ -0,0 +1,4 @@
identityFn = \x ->
return x
identityFn 45

View file

@ -0,0 +1,68 @@
SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-33,
],
space_before: [
Slice { start: 0, length: 0 },
],
space_after: [
Slice { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-10 Identifier {
ident: "identityFn",
},
@13-33 Closure(
[
@14-15 Identifier {
ident: "x",
},
],
@21-33 SpaceBefore(
Return(
@21-33 Var {
module_name: "",
ident: "x",
},
None,
),
[
Newline,
],
),
),
),
],
},
@36-49 SpaceBefore(
Apply(
@36-46 Var {
module_name: "",
ident: "identityFn",
},
[
@47-49 Num(
"45",
),
],
Space,
),
[
Newline,
Newline,
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,5 @@
identityFn = \x ->
return x
identityFn 45

View file

@ -0,0 +1,2 @@
"123"
|> try Str.toU64

View file

@ -0,0 +1,32 @@
SpaceAfter(
BinOps(
[
(
@0-5 SpaceAfter(
Str(
PlainLine(
"123",
),
),
[
Newline,
],
),
@17-19 Pizza,
),
],
@20-33 Apply(
@20-23 Try,
[
@24-33 Var {
module_name: "Str",
ident: "toU64",
},
],
Space,
),
),
[
Newline,
],
)

View file

@ -0,0 +1,2 @@
"123"
|> try Str.toU64

View file

@ -0,0 +1 @@
Str.toU64 "123" |> try

View file

@ -0,0 +1,27 @@
SpaceAfter(
BinOps(
[
(
@0-15 Apply(
@0-9 Var {
module_name: "Str",
ident: "toU64",
},
[
@10-15 Str(
PlainLine(
"123",
),
),
],
Space,
),
@15-17 Pizza,
),
],
@18-21 Try,
),
[
Newline,
],
)

View file

@ -0,0 +1 @@
Str.toU64 "123"|> try

View file

@ -0,0 +1 @@
try Str.toU64 "123"

View file

@ -0,0 +1,20 @@
SpaceAfter(
Apply(
@0-3 Try,
[
@5-14 Var {
module_name: "Str",
ident: "toU64",
},
@16-21 Str(
PlainLine(
"123",
),
),
],
Space,
),
[
Newline,
],
)

View file

@ -0,0 +1 @@
try Str.toU64 "123"

View file

@ -6071,6 +6071,41 @@ mod test_fmt {
);
}
#[test]
fn format_try() {
expr_formats_same(indoc!(
r#"
_ = crash
_ = crash ""
crash "" ""
"#
));
expr_formats_to(
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
);
}
#[test]
fn issue_6197() {
expr_formats_to(

View file

@ -196,6 +196,7 @@ mod test_snapshots {
fail/double_plus.expr,
fail/elm_function_syntax.expr,
fail/empty_or_pattern.expr,
fail/empty_return.expr,
fail/error_inline_alias_argument_uppercase.expr,
fail/error_inline_alias_not_an_alias.expr,
fail/error_inline_alias_qualified.expr,
@ -233,6 +234,7 @@ mod test_snapshots {
fail/record_type_open.expr,
fail/record_type_open_indent.expr,
fail/record_type_tab.expr,
fail/return_as_single_line_expr.expr,
fail/single_no_end.expr,
fail/tab_crash.header,
fail/tag_union_end.expr,
@ -463,6 +465,11 @@ mod test_snapshots {
pass/record_updater_var_apply.expr,
pass/record_with_if.expr,
pass/requires_type.header,
pass/return_in_if.expr,
pass/return_in_static_def.expr,
pass/return_in_when.expr,
pass/return_multiline.expr,
pass/return_only_statement.expr,
pass/separate_defs.moduledefs,
pass/single_arg_closure.expr,
pass/single_underscore_closure.expr,
@ -488,6 +495,9 @@ mod test_snapshots {
pass/tag_pattern.expr,
pass/ten_times_eleven.expr,
pass/three_arg_closure.expr,
pass/try_function_after_pipe.expr,
pass/try_pipe_suffix.expr,
pass/try_plain_prefix.expr,
pass/tuple_access_after_ident.expr,
pass/tuple_access_after_record.expr,
pass/tuple_accessor_function.expr,

View file

@ -16,9 +16,6 @@ pub static WILDCARD: &str = "*";
static EMPTY_RECORD: &str = "{}";
static EMPTY_TAG_UNION: &str = "[]";
// TODO: since we technically don't support empty tuples at the source level, this should probably be removed
static EMPTY_TUPLE: &str = "()";
/// Requirements for parentheses.
///
/// If we're inside a function (that is, this is either an argument or a return
@ -402,11 +399,7 @@ fn find_names_needed(
find_under_alias,
);
}
Error
| Structure(EmptyRecord)
| Structure(EmptyTuple)
| Structure(EmptyTagUnion)
| ErasedLambda => {
Error | Structure(EmptyRecord) | Structure(EmptyTagUnion) | ErasedLambda => {
// Errors and empty records don't need names.
}
}
@ -1113,7 +1106,6 @@ fn write_flat_type<'a>(
pol,
),
EmptyRecord => buf.push_str(EMPTY_RECORD),
EmptyTuple => buf.push_str(EMPTY_TUPLE),
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
Func(args, closure, ret) => write_fn(
env,
@ -1220,19 +1212,12 @@ fn write_flat_type<'a>(
buf.push_str(" )");
match subs.get_content_without_compacting(ext_var) {
Content::Structure(EmptyTuple) => {
// This is a closed tuple. We're done!
}
_ => {
// This is an open tuple, so print the variable
// right after the ')'
//
// e.g. the "*" at the end of `( I64, I64 )*`
// or the "r" at the end of `( I64, I64 )r`
write_content(env, ctx, ext_var, subs, buf, parens, pol)
}
}
// This is an open tuple, so print the variable
// right after the ')'
//
// e.g. the "*" at the end of `( I64, I64 )*`
// or the "r" at the end of `( I64, I64 )r`
write_content(env, ctx, ext_var, subs, buf, parens, pol)
}
TagUnion(tags, ext_var) => {
buf.push('[');

View file

@ -827,7 +827,6 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
write!(f, "]<{new_ext:?}> as <{rec:?}>")
}
FlatType::EmptyRecord => write!(f, "EmptyRecord"),
FlatType::EmptyTuple => write!(f, "EmptyTuple"),
FlatType::EmptyTagUnion => write!(f, "EmptyTagUnion"),
}
}
@ -2581,7 +2580,6 @@ pub enum FlatType {
RecursiveTagUnion(Variable, UnionTags, TagExt),
EmptyRecord,
EmptyTuple,
EmptyTagUnion,
}
@ -3478,7 +3476,7 @@ fn occurs(
short_circuit_help(subs, root_var, ctx, ext_var)
}
EmptyRecord | EmptyTuple | EmptyTagUnion => Ok(()),
EmptyRecord | EmptyTagUnion => Ok(()),
},
Alias(_, args, _, _) => {
// THEORY: we only need to explore the args, as that is the surface of all
@ -3668,7 +3666,7 @@ fn explicit_substitute(
subs.set_content(in_var, Structure(Tuple(vars_by_elem, new_ext)));
}
EmptyRecord | EmptyTuple | EmptyTagUnion => {}
EmptyRecord | EmptyTagUnion => {}
}
in_var
@ -3851,9 +3849,7 @@ fn get_var_names(
accum
}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {
taken_names
}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => taken_names,
FlatType::Record(vars_by_field, ext) => {
let mut accum = get_var_names(subs, ext, taken_names);
@ -4198,7 +4194,6 @@ fn flat_type_to_err_type(
}
EmptyRecord => ErrorType::Record(SendMap::default(), TypeExt::Closed),
EmptyTuple => ErrorType::Tuple(Vec::default(), TypeExt::Closed),
EmptyTagUnion => ErrorType::TagUnion(SendMap::default(), TypeExt::Closed, pol),
Record(vars_by_field, ext) => {
@ -4654,7 +4649,6 @@ impl StorageSubs {
ext.map(|v| Self::offset_variable(offsets, v)),
),
FlatType::EmptyRecord => FlatType::EmptyRecord,
FlatType::EmptyTuple => FlatType::EmptyTuple,
FlatType::EmptyTagUnion => FlatType::EmptyTagUnion,
}
}
@ -4949,7 +4943,7 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTagUnion => same,
Record(fields, ext) => {
let record_fields = {
@ -5415,7 +5409,7 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same,
same @ EmptyRecord | same @ EmptyTagUnion => same,
Record(fields, ext) => {
let record_fields = {
@ -5788,7 +5782,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
stack.push(closure_var);
}
EmptyRecord | EmptyTuple | EmptyTagUnion => (),
EmptyRecord | EmptyTagUnion => (),
Record(fields, ext) => {
let fields = *fields;
@ -5939,7 +5933,7 @@ pub fn get_member_lambda_sets_at_region(subs: &Subs, var: Variable, target_regio
);
stack.push(ext.var());
}
FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => {}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
},
Content::Alias(_, _, real_var, _) => {
stack.push(*real_var);
@ -6014,7 +6008,6 @@ fn is_inhabited(subs: &Subs, var: Variable) -> bool {
}
FlatType::FunctionOrTagUnion(_, _, _) => {}
FlatType::EmptyRecord => {}
FlatType::EmptyTuple => {}
FlatType::EmptyTagUnion => {
return false;
}

View file

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

View file

@ -3,22 +3,22 @@
app "test" provides [main] to "./platform"
f = \{} ->
#^{-1} <2897><113>{} -<116>[[f(1)]]-> <112>[Ok <2905>{}]<76>*
#^{-1} <2897><114>{} -<117>[[f(1)]]-> <113>[Ok <2905>{}]<77>*
when g {} is
# ^ <2887><2905>{} -<2895>[[g(2)]]-> <68>[Ok <2905>{}]<98>*
# ^ <2887><2905>{} -<2895>[[g(2)]]-> <69>[Ok <2905>{}]<99>*
_ -> Ok {}
g = \{} ->
#^{-1} <2887><2905>{} -<2895>[[g(2)]]-> <68>[Ok <2905>{}]<98>*
#^{-1} <2887><2905>{} -<2895>[[g(2)]]-> <69>[Ok <2905>{}]<99>*
when h {} is
# ^ <2892><2905>{} -<2900>[[h(3)]]-> <90>[Ok <2905>{}]<120>*
# ^ <2892><2905>{} -<2900>[[h(3)]]-> <91>[Ok <2905>{}]<121>*
_ -> Ok {}
h = \{} ->
#^{-1} <2892><2905>{} -<2900>[[h(3)]]-> <90>[Ok <2905>{}]<120>*
#^{-1} <2892><2905>{} -<2900>[[h(3)]]-> <91>[Ok <2905>{}]<121>*
when f {} is
# ^ <2897><113>{} -<116>[[f(1)]]-> <112>[Ok <2905>{}]<76>*
# ^ <2897><114>{} -<117>[[f(1)]]-> <113>[Ok <2905>{}]<77>*
_ -> Ok {}
main = f {}
# ^ <2907><129>{} -<132>[[f(1)]]-> <134>[Ok <2905>{}]<2906>w_a
# ^ <2907><130>{} -<133>[[f(1)]]-> <129>[Ok <2905>{}]<2906>w_a

View file

@ -112,6 +112,10 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
.replace(
"<!-- Module links -->",
render_sidebar(exposed_module_docs.iter().map(|(_, docs)| docs)).as_str(),
)
.replace(
"<!-- Search Type Ahead -->",
render_search_type_ahead(exposed_module_docs.iter().map(|(_, docs)| docs)).as_str(),
);
let all_exposed_symbols = {
@ -469,6 +473,72 @@ fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -
buf
}
fn render_search_type_ahead<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -> String {
let mut buf = String::new();
for module in modules {
let module_name = module.name.as_str();
for entry in &module.entries {
if let DocEntry::DocDef(doc_def) = entry {
if module.exposed_symbols.contains(&doc_def.symbol) {
let mut entry_contents_buf = String::new();
push_html(
&mut entry_contents_buf,
"p",
vec![("class", "type-ahead-def-name")],
doc_def.name.as_str(),
);
let mut entry_signature_buf = String::new();
type_annotation_to_html(
0,
&mut entry_signature_buf,
&doc_def.type_annotation,
false,
);
push_html(
&mut entry_contents_buf,
"p",
vec![("class", "type-ahead-signature")],
entry_signature_buf.as_str(),
);
push_html(
&mut entry_contents_buf,
"p",
vec![("class", "type-ahead-doc-path")],
format!("{} > {}", module_name, doc_def.name),
);
let mut entry_href = String::new();
entry_href.push_str(module_name);
entry_href.push('#');
entry_href.push_str(doc_def.name.as_str());
let mut anchor_buf = String::new();
push_html(
&mut anchor_buf,
"a",
vec![("href", entry_href.as_str()), ("class", "type-ahead-link")],
entry_contents_buf.as_str(),
);
push_html(
&mut buf,
"li",
vec![("role", "option")],
anchor_buf.as_str(),
);
}
}
}
}
buf
}
pub fn load_module_for_docs(filename: PathBuf) -> LoadedModule {
let arena = Bump::new();
let load_config = LoadConfig {

View file

@ -19,9 +19,6 @@
<body>
<nav id="sidebar-nav">
<input id="module-search" aria-labelledby="search-link" type="text" placeholder="Search" />
<label for="module-search" id="search-link"><span id="search-link-text">Search</span> <span
id="search-link-hint">(press <span id="search-shortcut-key">s</span>)</span></label>
<div class="module-links">
<!-- Module links -->
</div>
@ -41,6 +38,23 @@
</a>
<!-- Package Name -->
</div>
<form id="module-search-form">
<input
id="module-search"
aria-labelledby="search-link"
type="text"
placeholder="Search"
role="combobox"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="search-type-ahead"
/>
<label for="module-search" id="search-link">Search (press s)</label>
<span id="search-shortcut-key" aria-hidden="true">s</span>
<ul id="search-type-ahead" role="listbox" aria-label="Search Results" class="hidden">
<!-- Search Type Ahead -->
</ul>
</form>
<div class="top-header-triangle">
<!-- if the window gets big, this extends the purple bar on the top header to the left edge of the window -->
</div>

View file

@ -1,69 +1,88 @@
(() => {
let sidebar = document.getElementById("sidebar-nav");
// Un-hide everything
sidebar
.querySelectorAll(".sidebar-entry a")
.forEach((entry) => entry.classList.remove("hidden"));
// Re-hide all the sub-entries except for those of the current module
let currentModuleName = document.querySelector(".module-name").textContent;
sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => {
let entryName = entry.querySelector(".sidebar-module-link").textContent;
if (currentModuleName === entryName) {
entry.firstChild.classList.add("active");
return;
}
entry
.querySelectorAll(".sidebar-sub-entries a")
.forEach((subEntry) => subEntry.classList.add("hidden"));
});
let searchTypeAhead = document.getElementById("search-type-ahead");
let searchBox = document.getElementById("module-search");
let searchForm = document.getElementById("module-search-form");
let topSearchResultListItem = undefined;
if (searchBox != null) {
function search() {
topSearchResultListItem = undefined;
let text = searchBox.value.toLowerCase(); // Search is case-insensitive.
if (text === "") {
// Un-hide everything
sidebar
.querySelectorAll(".sidebar-entry a")
.forEach((entry) => entry.classList.remove("hidden"));
if (text === "") {
searchTypeAhead.classList.add("hidden");
} else {
let totalResults = 0;
// Firsttype-ahead-signature", show/hide all the sub-entries within each module (top-level functions etc.)
searchTypeAhead.querySelectorAll("li").forEach((entry) => {
const entryName = entry
.querySelector(".type-ahead-def-name")
.textContent.toLowerCase();
const entrySignature = entry
.querySelector(".type-ahead-signature")
.textContent.toLowerCase()
.replace(/\s+/g, "");
// Re-hide all the sub-entries except for those of the current module
let currentModuleName =
document.querySelector(".module-name").textContent;
sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => {
let entryName = entry.querySelector(
".sidebar-module-link"
).textContent;
if (currentModuleName === entryName) {
entry.firstChild.classList.add("active");
return;
}
entry
.querySelectorAll(".sidebar-sub-entries a")
.forEach((subEntry) =>
subEntry.classList.add("hidden")
);
});
} else {
// First, show/hide all the sub-entries within each module (top-level functions etc.)
sidebar
.querySelectorAll(".sidebar-sub-entries a")
.forEach((entry) => {
if (entry.textContent.toLowerCase().includes(text)) {
entry.classList.remove("hidden");
} else {
entry.classList.add("hidden");
}
});
// Then, show/hide modules based on whether they match, or any of their sub-entries matched
sidebar
.querySelectorAll(".sidebar-module-link")
.forEach((entry) => {
if (
entry.textContent.toLowerCase().includes(text) ||
entry.parentNode.querySelectorAll(
".sidebar-sub-entries a:not(.hidden)"
).length > 0
totalResults < 5 &&
(entryName.includes(text) ||
entrySignature.includes(text.replace(/\s+/g, "")))
) {
totalResults++;
entry.classList.remove("hidden");
if (topSearchResultListItem === undefined) {
topSearchResultListItem = entry;
}
} else {
entry.classList.add("hidden");
}
});
}
if (totalResults < 1) {
searchTypeAhead.classList.add("hidden");
} else {
searchTypeAhead.classList.remove("hidden");
}
}
}
searchBox.addEventListener("input", search);
search();
function searchSubmit(e) {
// pick the top result if the user submits search form
e.preventDefault();
if (topSearchResultListItem !== undefined) {
let topSearchResultListItemAnchor =
topSearchResultListItem.querySelector("a");
if (topSearchResultListItemAnchor !== null) {
topSearchResultListItemAnchor.click();
}
}
}
searchForm.addEventListener("submit", searchSubmit);
// Capture '/' keypress for quick search
window.addEventListener("keyup", (e) => {
if (e.key === "s" && document.activeElement !== searchBox) {
@ -77,13 +96,20 @@
// De-focus input box
searchBox.blur();
} else if (
e.key === "Escape" &&
searchTypeAhead.contains(document.activeElement)
) {
e.preventDefault;
// Reset sidebar state
search();
// De-focus type ahead
searchBox.focus();
searchBox.blur();
}
});
}
const isTouchSupported = () => {
try {
document.createEvent("TouchEvent");

View file

@ -24,6 +24,9 @@
monospace;
--top-header-height: 67px;
--sidebar-width: 280px;
--module-search-height: 48px;
--module-search-padding-height: 12px;
--module-search-form-padding-width: 20px;
}
a {
@ -439,40 +442,37 @@ pre>samp {
display: none !important;
}
#module-search:placeholder-shown {
padding: 0;
opacity: 0;
height: 0;
#module-search-form {
display: flex;
align-items: center;
align-content: center;
height: 100%;
background-color: var(--violet-bg);
position: relative;
max-width: 500px;
flex-grow: 1;
box-sizing: border-box;
padding: 0px var(--module-search-form-padding-width);
}
#module-search,
#module-search:focus {
opacity: 1;
padding: 12px 16px;
height: 48px;
}
/* Show the "Search" label link when the text input has a placeholder */
#module-search:placeholder-shown + #search-link {
display: flex;
}
/* Hide the "Search" label link when the text input has focus */
#module-search:focus + #search-link {
display: none;
height: var(--module-search-height);
}
#module-search {
display: block;
position: relative;
box-sizing: border-box;
width: 100%;
box-sizing: border-box;
font-size: 18px;
line-height: 18px;
margin-top: 6px;
border: none;
color: var(--faded-color);
background-color: var(--code-bg);
background-color: var(--body-bg-color);
font-family: var(--font-serif);
}
@ -481,6 +481,62 @@ pre>samp {
opacity: 1;
}
#module-search-form:focus-within #search-type-ahead {
display: flex;
gap: 20px;
flex-direction: column;
position: absolute;
top: calc(var(--module-search-padding-height) + var(--module-search-height));
left: var(--module-search-form-padding-width);
width: calc(100% - 2 * var(--module-search-form-padding-width));
}
#search-type-ahead {
box-sizing: border-box;
display: none;
z-index: 100;
background-color: var(--body-bg-color);
border-width: 1px;
border-style: solid;
border-color: var(--border-color);
list-style-type: none;
margin: 0;
padding: 10px;
}
#search-type-ahead .type-ahead-link {
font-size: 0.875rem;
color: var(--text-color);
p {
margin: 0px;
}
p.type-ahead-def-name {
color: var(--violet);
font-size: 1rem;
}
p.type-ahead-doc-path {
font-size: 0.75rem;
color: var(--faded-color);
}
}
#search-type-ahead li {
box-sizing: border-box;
padding: 4px;
}
/*use browser defaults for focus outline*/
#search-type-ahead li:focus-within {
/*firefox*/
outline: 5px auto Highlight;
/*chrome and safari*/
outline: 5px auto -webkit-focus-ring-color;
}
.type-ahead-link:focus-visible {
outline: none;
}
#search-link {
box-sizing: border-box;
display: none;
@ -492,21 +548,16 @@ pre>samp {
cursor: pointer;
}
#search-link:hover #search-link-text {
text-decoration: underline;
}
#search-link-hint {
margin-left: 1em;
opacity: 0.6;
}
#search-shortcut-key {
font-family: monospace;
border: 1px solid #666;
border-radius: 5px;
padding: 1px 3px 3px;
font-style: normal;
line-height: 15px;
opacity: 0.6;
position: absolute;
right: 30px;
}
.builtins-tip {
@ -546,7 +597,11 @@ pre>samp {
}
@media only screen and (max-device-width: 480px) and (orientation: portrait) {
#search-link-hint {
:root {
--top-header-height: 140px;
}
#search-shortcut-key {
display: none;
}
@ -555,8 +610,9 @@ pre>samp {
}
.top-header {
flex-direction: column;
height: auto;
justify-content: space-between;
width: auto;
/* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants. */
min-width: 0;
}

View file

@ -1,10 +1,10 @@
# Glue
Glue is a bit of tooling built into Roc to help with platform development. Roc platforms are written in a different language than Roc, and some it requires some finesse to let other languages to read and write Roc types like records and unions in a way compatible with how Roc uses those types.
Glue is a bit of tooling built into Roc to help with platform development. Roc platforms are written in a different language than Roc, and it requires some finesse to let other languages read and write Roc types like records and unions in a way that's compatible with how Roc uses those types.
The `roc glue` command generates code in other languages for interacting with the Roc types used by your platform. It takes three arguments:
1. A 'glue spec', this is a Roc file specifying how to output type helpers fora particular language. You can find some examples in the src/ subdirectory:
1. A 'glue spec', this is a Roc file specifying how to output type helpers for a particular language. You can find some examples in the src/ subdirectory:
- **RustGlue.roc:** Generates Roc bindings for rust platforms.
- **ZigGlue.roc:** Generates Roc bindings for zig platforms (out of date).

View file

@ -324,7 +324,6 @@ fn number_lambda_sets(subs: &Subs, initial: Variable) -> Vec<Variable> {
EmptyRecord => (),
EmptyTagUnion => (),
EmptyTuple => (),
Record(fields, ext) => {
let fields = *fields;

View file

@ -1424,9 +1424,6 @@ fn add_type_help<'a>(
Content::Structure(FlatType::EmptyRecord) => {
types.add_anonymous(&env.layout_cache.interner, RocType::Unit, layout)
}
Content::Structure(FlatType::EmptyTuple) => {
types.add_anonymous(&env.layout_cache.interner, RocType::Unit, layout)
}
Content::Structure(FlatType::EmptyTagUnion) => {
types.add_anonymous(&env.layout_cache.interner, RocType::EmptyTagUnion, layout)
}

View file

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

View file

@ -699,6 +699,7 @@ impl IterTokens for Loc<Expr<'_>> {
Expr::LowLevelDbg(_, e1, e2) => (e1.iter_tokens(arena).into_iter())
.chain(e2.iter_tokens(arena))
.collect_in(arena),
Expr::Try => onetoken(Token::Keyword, region, arena),
Expr::Apply(e1, e2, _called_via) => (e1.iter_tokens(arena).into_iter())
.chain(e2.iter_tokens(arena))
.collect_in(arena),
@ -718,6 +719,11 @@ impl IterTokens for Loc<Expr<'_>> {
Expr::When(e, branches) => (e.iter_tokens(arena).into_iter())
.chain(branches.iter_tokens(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, _) => {
Loc::at(region, *e).iter_tokens(arena)
}

View file

@ -1346,6 +1346,60 @@ pub fn can_problem<'b>(
doc = report.doc;
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),
alloc.reflow("I wouldn't know where to return to if I used it!"),
]);
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),
alloc.concat([
alloc.hint("you can move the "),
alloc.keyword("return"),
alloc.reflow(
" statement below this block to make the code that follows it run.",
),
]),
]);
title = "UNREACHABLE CODE".to_string();
}
Problem::ReturnAtEndOfFunction { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "),
alloc.keyword("return"),
alloc.reflow(" keyword is redundant:"),
]),
alloc.region(lines.convert_region(region), severity),
alloc.concat([
alloc.reflow("The last expression in a function is treated like a "),
alloc.keyword("return"),
alloc.reflow(" statement. You can safely remove "),
alloc.keyword("return"),
alloc.reflow(" here."),
]),
]);
title = "UNNECESSARY RETURN".to_string();
}
};
Report {

View file

@ -1645,6 +1645,40 @@ fn to_expr_report<'b>(
unimplemented!("record default field is not implemented yet")
}
Reason::ImportParams(_) => unreachable!(),
Reason::FunctionOutput => {
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"), &category),
alloc.reflow("But I expected the function to have return type:"),
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,
}
}
},
}
}
@ -1956,7 +1990,7 @@ fn format_category<'b>(
}
Uniqueness => (
alloc.concat([this_is, alloc.text(" an uniqueness attribute")]),
alloc.concat([this_is, alloc.text(" a uniqueness attribute")]),
alloc.text(" of type:"),
),
Crash => {
@ -1983,6 +2017,10 @@ fn format_category<'b>(
alloc.concat([this_is, alloc.text(" a dbg statement")]),
alloc.text(" of type:"),
),
Return => (
alloc.concat([text!(alloc, "{}his", t), alloc.reflow(" returns a value")]),
alloc.text(" of type:"),
),
}
}

View file

@ -64,7 +64,7 @@ $ cargo build --bin roc
$ ./target/debug/roc build ./examples/platform-switching/rocLovesRust.roc
# To view in editor
$ cargo llvm-cov report --lcov --output-path lcov.info
# To view in browser
# To view in browser. This html report also allows you to see how many times each line of code was run.
$ cargo llvm-cov report --html
```
Viewing lcov.info will depend on your editor. For vscode, you can use the [coverage gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension. After installing, click `Watch` in the bottom bar and go to a file for which you want to see the coverage, for example `crates/compiler/build/src/link.rs`. `Watch` in the bottom bar will now be replaced with `x% Coverage`.