mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 04:08:19 +00:00
Merge remote-tracking branch 'remote/main' into rebuild-platform
This commit is contained in:
commit
c00db6da37
100 changed files with 2554 additions and 279 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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(_, _, _, _, _)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */ }
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -496,20 +496,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"EmptyTuple"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,9 +221,6 @@ export default function DrawHeadConstructor({
|
|||
case "EmptyRecord": {
|
||||
return <>{"{}"}</>;
|
||||
}
|
||||
case "EmptyTuple": {
|
||||
return <>()</>;
|
||||
}
|
||||
case "EmptyTagUnion": {
|
||||
return <>[]</>;
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -251,7 +251,6 @@ function VariableNodeContent(
|
|||
return {};
|
||||
}
|
||||
case "EmptyRecord":
|
||||
case "EmptyTuple":
|
||||
case "EmptyTagUnion":
|
||||
case "Error": {
|
||||
return {};
|
||||
|
|
|
@ -148,10 +148,6 @@ export type Content =
|
|||
type: "EmptyRecord";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "EmptyTuple";
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| {
|
||||
type: "EmptyTagUnion";
|
||||
[k: string]: unknown;
|
||||
|
|
|
@ -97,7 +97,6 @@ impl_content! {
|
|||
extension: TagUnionExtension,
|
||||
},
|
||||
EmptyRecord {},
|
||||
EmptyTuple {},
|
||||
EmptyTagUnion {},
|
||||
RangedNumber {
|
||||
range: NumericRange,
|
||||
|
|
|
@ -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 { .. }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>)],
|
||||
|
|
|
@ -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 doesn’t have a `abcde` field:
|
||||
|
||||
4│ {}.abcde
|
||||
^^^^^^^^
|
||||
|
||||
In fact, it’s 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 *
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -181,7 +181,8 @@ fn remove_for_reason(
|
|||
def_region: _,
|
||||
}
|
||||
| Reason::CrashArg
|
||||
| Reason::ImportParams(_) => {}
|
||||
| Reason::ImportParams(_)
|
||||
| Reason::FunctionOutput => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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, _) => {
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
109
crates/compiler/test_gen/src/gen_return.rs
Normal file
109
crates/compiler/test_gen/src/gen_return.rs
Normal 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
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
|
|
53
crates/compiler/test_mono/generated/dec_refcount_for_usage_after_early_return_in_if.txt
generated
Normal file
53
crates/compiler/test_mono/generated/dec_refcount_for_usage_after_early_return_in_if.txt
generated
Normal 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;
|
|
@ -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
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Expr(Return(IndentReturnValue(@6), @0), @0)
|
|
@ -0,0 +1 @@
|
|||
return
|
|
@ -0,0 +1 @@
|
|||
Expr(Start(@0), @0)
|
|
@ -0,0 +1 @@
|
|||
x = return 5
|
|
@ -96,10 +96,7 @@ SpaceAfter(
|
|||
"",
|
||||
),
|
||||
@50-74 Apply(
|
||||
@50-53 Var {
|
||||
module_name: "",
|
||||
ident: "try",
|
||||
},
|
||||
@50-53 Try,
|
||||
[
|
||||
@54-57 Var {
|
||||
module_name: "",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
maybeEarlyReturn = \x ->
|
||||
y =
|
||||
if x > 5 then
|
||||
return "abc"
|
||||
else
|
||||
x + 2
|
||||
|
||||
Num.toStr y
|
||||
|
||||
maybeEarlyReturn 10
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
maybeEarlyReturn = \x ->
|
||||
y =
|
||||
if x > 5 then
|
||||
return "abc"
|
||||
else
|
||||
x + 2
|
||||
|
||||
Num.toStr y
|
||||
|
||||
maybeEarlyReturn 10
|
|
@ -0,0 +1,11 @@
|
|||
staticValueDef =
|
||||
someVal =
|
||||
if 10 > 5 then
|
||||
x = 5
|
||||
return x
|
||||
else
|
||||
6
|
||||
|
||||
someVal + 2
|
||||
|
||||
staticValueDef
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
staticValueDef =
|
||||
someVal =
|
||||
if 10 > 5 then
|
||||
x = 5
|
||||
return x
|
||||
else
|
||||
6
|
||||
|
||||
someVal + 2
|
||||
|
||||
|
||||
staticValueDef
|
|
@ -0,0 +1,11 @@
|
|||
maybeEarlyReturn = \x ->
|
||||
y =
|
||||
when x is
|
||||
5 ->
|
||||
return "abc"
|
||||
|
||||
_ -> x + 2
|
||||
|
||||
Num.toStr y
|
||||
|
||||
maybeEarlyRetun 3
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
maybeEarlyReturn = \x ->
|
||||
y =
|
||||
when x is
|
||||
5 ->
|
||||
return
|
||||
"abc"
|
||||
|
||||
_ -> x + 2
|
||||
|
||||
|
||||
Num.toStr y
|
||||
|
||||
maybeEarlyRetun 3
|
|
@ -0,0 +1,3 @@
|
|||
return something
|
||||
|> pipeToFunction
|
||||
|> andAnother
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
return
|
||||
something
|
||||
|> pipeToFunction
|
||||
|> andAnother
|
|
@ -0,0 +1,4 @@
|
|||
identityFn = \x ->
|
||||
return x
|
||||
|
||||
identityFn 45
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
identityFn = \x ->
|
||||
return x
|
||||
|
||||
|
||||
identityFn 45
|
|
@ -0,0 +1,2 @@
|
|||
"123"
|
||||
|> try Str.toU64
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
"123"
|
||||
|> try Str.toU64
|
|
@ -0,0 +1 @@
|
|||
Str.toU64 "123" |> try
|
|
@ -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,
|
||||
],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
Str.toU64 "123"|> try
|
|
@ -0,0 +1 @@
|
|||
try Str.toU64 "123"
|
|
@ -0,0 +1,20 @@
|
|||
SpaceAfter(
|
||||
Apply(
|
||||
@0-3 Try,
|
||||
[
|
||||
@5-14 Var {
|
||||
module_name: "Str",
|
||||
ident: "toU64",
|
||||
},
|
||||
@16-21 Str(
|
||||
PlainLine(
|
||||
"123",
|
||||
),
|
||||
),
|
||||
],
|
||||
Space,
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
try Str.toU64 "123"
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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('[');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -324,7 +324,6 @@ fn number_lambda_sets(subs: &Subs, initial: Variable) -> Vec<Variable> {
|
|||
|
||||
EmptyRecord => (),
|
||||
EmptyTagUnion => (),
|
||||
EmptyTuple => (),
|
||||
|
||||
Record(fields, ext) => {
|
||||
let fields = *fields;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -214,6 +214,7 @@ impl CompletionVisitor<'_> {
|
|||
DeclarationInfo::Value {
|
||||
expr_var, pattern, ..
|
||||
} => self.patterns(pattern, expr_var),
|
||||
DeclarationInfo::Return { .. } => vec![],
|
||||
DeclarationInfo::Function {
|
||||
expr_var,
|
||||
pattern,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue