mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Initial working version of proper try
keyword
This commit is contained in:
parent
a7168a4ad6
commit
eedade8e81
33 changed files with 1036 additions and 521 deletions
|
@ -247,6 +247,8 @@ fn main() -> io::Result<()> {
|
|||
) {
|
||||
Ok((problems, total_time)) => {
|
||||
problems.print_error_warning_count(total_time);
|
||||
println!(".\n");
|
||||
|
||||
exit_code = problems.exit_code();
|
||||
}
|
||||
|
||||
|
|
|
@ -334,7 +334,7 @@ mod cli_tests {
|
|||
);
|
||||
|
||||
let expected_out =
|
||||
"0 error and 0 warning found in <ignored for test> ms\n0 error and 0 warning found in <ignored for test> ms\n";
|
||||
"0 errors and 0 warnings found in <ignored for test> ms.\n\n0 errors and 0 warnings found in <ignored for test> ms.\n\n";
|
||||
|
||||
cli_build.run().assert_clean_stdout(expected_out);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
source: crates/cli/tests/cli_tests.rs
|
||||
assertion_line: 429
|
||||
expression: cli_dev_out.normalize_stdout_and_stderr()
|
||||
snapshot_kind: text
|
||||
---
|
||||
|
||||
── TOO MANY ARGS in tests/test-projects/module_params/arity_mismatch.roc ───────
|
||||
|
@ -36,8 +36,6 @@ make partial application explicit.
|
|||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
3 error and 0 warning found in <ignored for test> ms
|
||||
.
|
||||
3 errors and 0 warnings found in <ignored for test> ms.
|
||||
|
||||
You can run <ignored for tests>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
source: crates/cli/tests/cli_tests.rs
|
||||
assertion_line: 445
|
||||
expression: cli_dev_out.normalize_stdout_and_stderr()
|
||||
snapshot_kind: text
|
||||
---
|
||||
|
||||
── TYPE MISMATCH in tests/test-projects/module_params/BadAnn.roc ───────────────
|
||||
|
@ -41,8 +41,6 @@ Tip: It looks like it takes too many arguments. I'm seeing 1 extra.
|
|||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
2 error and 1 warning found in <ignored for test> ms
|
||||
.
|
||||
2 errors and 1 warning found in <ignored for test> ms.
|
||||
|
||||
You can run <ignored for tests>
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
---
|
||||
source: crates/cli/tests/cli_tests.rs
|
||||
assertion_line: 476
|
||||
expression: cli_dev_out.normalize_stdout_and_stderr()
|
||||
snapshot_kind: text
|
||||
---
|
||||
|
||||
── TYPE MISMATCH in tests/test-projects/module_params/unexpected_fn.roc ────────
|
||||
|
||||
This argument to this string interpolation has an unexpected type:
|
||||
|
||||
11│ $(Api.getPost)
|
||||
^^^^^^^^^^^
|
||||
10│ """
|
||||
11│> $(Api.getPost)
|
||||
12│ """
|
||||
|
||||
The argument is an anonymous function of type:
|
||||
|
||||
|
@ -21,8 +22,6 @@ But this string interpolation needs its argument to be:
|
|||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
1 error and 0 warning found in <ignored for test> ms
|
||||
.
|
||||
1 error and 0 warnings found in <ignored for test> ms.
|
||||
|
||||
You can run <ignored for tests>
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ pub struct Constraints {
|
|||
pub cycles: Vec<Cycle>,
|
||||
pub fx_call_constraints: Vec<FxCallConstraint>,
|
||||
pub fx_suffix_constraints: Vec<FxSuffixConstraint>,
|
||||
pub try_target_constraints: Vec<TryTargetConstraint>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Constraints {
|
||||
|
@ -87,6 +88,7 @@ impl Constraints {
|
|||
let cycles = Vec::new();
|
||||
let fx_call_constraints = Vec::with_capacity(16);
|
||||
let fx_suffix_constraints = Vec::new();
|
||||
let result_type_constraints = Vec::new();
|
||||
|
||||
categories.extend([
|
||||
Category::Record,
|
||||
|
@ -103,7 +105,6 @@ impl Constraints {
|
|||
Category::List,
|
||||
Category::Str,
|
||||
Category::Character,
|
||||
Category::Return,
|
||||
]);
|
||||
|
||||
pattern_categories.extend([
|
||||
|
@ -138,6 +139,7 @@ impl Constraints {
|
|||
cycles,
|
||||
fx_call_constraints,
|
||||
fx_suffix_constraints,
|
||||
try_target_constraints: result_type_constraints,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,6 +637,25 @@ impl Constraints {
|
|||
Constraint::FlexToPure(fx_var)
|
||||
}
|
||||
|
||||
pub fn try_target(
|
||||
&mut self,
|
||||
result_type_index: TypeOrVar,
|
||||
ok_payload_var: Variable,
|
||||
err_payload_var: Variable,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let constraint = TryTargetConstraint {
|
||||
target_type_index: result_type_index,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
region,
|
||||
};
|
||||
|
||||
let constraint_index = index_push_new(&mut self.try_target_constraints, constraint);
|
||||
|
||||
Constraint::TryTarget(constraint_index)
|
||||
}
|
||||
|
||||
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
|
||||
match constraint {
|
||||
Constraint::SaveTheEnvironment => true,
|
||||
|
@ -660,6 +681,7 @@ impl Constraints {
|
|||
| Constraint::Lookup(..)
|
||||
| Constraint::Pattern(..)
|
||||
| Constraint::ExpectEffectful(..)
|
||||
| Constraint::TryTarget(_)
|
||||
| Constraint::FxCall(_)
|
||||
| Constraint::FxSuffix(_)
|
||||
| Constraint::FlexToPure(_)
|
||||
|
@ -843,6 +865,8 @@ pub enum Constraint {
|
|||
FlexToPure(Variable),
|
||||
/// Expect statement or ignored def to be effectful
|
||||
ExpectEffectful(Variable, ExpectEffectfulReason, Region),
|
||||
/// Expect value to be some kind of Result
|
||||
TryTarget(Index<TryTargetConstraint>),
|
||||
/// Used for things that always unify, e.g. blanks and runtime errors
|
||||
True,
|
||||
SaveTheEnvironment,
|
||||
|
@ -909,6 +933,14 @@ pub struct IncludesTag {
|
|||
pub region: Region,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TryTargetConstraint {
|
||||
pub target_type_index: TypeOrVar,
|
||||
pub ok_payload_var: Variable,
|
||||
pub err_payload_var: Variable,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cycle {
|
||||
pub def_names: Slice<(Symbol, Region)>,
|
||||
|
@ -1000,6 +1032,9 @@ impl std::fmt::Debug for Constraint {
|
|||
Self::FlexToPure(arg0) => {
|
||||
write!(f, "FlexToPure({arg0:?})")
|
||||
}
|
||||
Self::TryTarget(arg0) => {
|
||||
write!(f, "ExpectResultType({arg0:?})")
|
||||
}
|
||||
Self::True => write!(f, "True"),
|
||||
Self::SaveTheEnvironment => write!(f, "SaveTheEnvironment"),
|
||||
Self::Let(arg0, arg1) => f.debug_tuple("Let").field(arg0).field(arg1).finish(),
|
||||
|
|
|
@ -479,7 +479,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
|
|||
fx_type: sub!(*fx_type),
|
||||
early_returns: early_returns
|
||||
.iter()
|
||||
.map(|(var, region)| (sub!(*var), *region))
|
||||
.map(|(var, region, type_)| (sub!(*var), *region, *type_))
|
||||
.collect(),
|
||||
name: *name,
|
||||
captured_symbols: captured_symbols
|
||||
|
@ -718,6 +718,22 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
|
|||
symbol: *symbol,
|
||||
},
|
||||
|
||||
Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
} => Try {
|
||||
result_expr: Box::new(result_expr.map(|e| go_help!(e))),
|
||||
result_var: sub!(*result_var),
|
||||
return_var: sub!(*return_var),
|
||||
ok_payload_var: sub!(*ok_payload_var),
|
||||
err_payload_var: sub!(*err_payload_var),
|
||||
err_ext_var: sub!(*err_ext_var),
|
||||
},
|
||||
|
||||
RuntimeError(err) => RuntimeError(err.clone()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -452,6 +452,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
|||
),
|
||||
Dbg { .. } => todo!(),
|
||||
Expect { .. } => todo!(),
|
||||
Try { .. } => todo!(),
|
||||
Return { .. } => todo!(),
|
||||
RuntimeError(_) => todo!(),
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ fn new_op_call_expr<'a>(
|
|||
match right_without_spaces {
|
||||
Try => {
|
||||
let desugared_left = desugar_expr(env, scope, left);
|
||||
return desugar_try_expr(env, scope, desugared_left);
|
||||
return Loc::at(region, Expr::LowLevelTry(desugared_left));
|
||||
}
|
||||
Apply(&Loc { value: Try, .. }, arguments, _called_via) => {
|
||||
let try_fn = desugar_expr(env, scope, arguments.first().unwrap());
|
||||
|
@ -58,13 +58,12 @@ fn new_op_call_expr<'a>(
|
|||
.map(|a| desugar_expr(env, scope, a)),
|
||||
);
|
||||
|
||||
return desugar_try_expr(
|
||||
env,
|
||||
scope,
|
||||
env.arena.alloc(Loc::at(
|
||||
right.region,
|
||||
return Loc::at(
|
||||
region,
|
||||
Expr::LowLevelTry(env.arena.alloc(Loc::at(
|
||||
region,
|
||||
Expr::Apply(try_fn, args.into_bump_slice(), CalledVia::Try),
|
||||
)),
|
||||
))),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -957,13 +956,17 @@ pub fn desugar_expr<'a>(
|
|||
desugared_args.push(desugar_expr(env, scope, loc_arg));
|
||||
}
|
||||
|
||||
let args_region =
|
||||
Region::span_across(&loc_args[0].region, &loc_args[loc_args.len() - 1].region);
|
||||
|
||||
env.arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
args_region,
|
||||
Expr::Apply(function, desugared_args.into_bump_slice(), CalledVia::Try),
|
||||
))
|
||||
};
|
||||
|
||||
env.arena.alloc(desugar_try_expr(env, scope, result_expr))
|
||||
env.arena
|
||||
.alloc(Loc::at(loc_expr.region, Expr::LowLevelTry(result_expr)))
|
||||
}
|
||||
Apply(loc_fn, loc_args, called_via) => {
|
||||
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
@ -1134,77 +1137,11 @@ pub fn desugar_expr<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
// note this only exists after desugaring
|
||||
LowLevelDbg(_, _, _) => loc_expr,
|
||||
// note these only exist after desugaring
|
||||
LowLevelDbg(_, _, _) | LowLevelTry(_) => 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,
|
||||
|
|
|
@ -26,7 +26,9 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
|||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::num::SingleQuoteBound;
|
||||
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
|
||||
use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
|
||||
use roc_types::types::{
|
||||
Alias, Category, EarlyReturnKind, IndexOrField, LambdaSet, OptAbleVar, Type,
|
||||
};
|
||||
use soa::Index;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::path::PathBuf;
|
||||
|
@ -328,6 +330,15 @@ pub enum Expr {
|
|||
symbol: Symbol,
|
||||
},
|
||||
|
||||
Try {
|
||||
result_expr: Box<Loc<Expr>>,
|
||||
result_var: Variable,
|
||||
return_var: Variable,
|
||||
ok_payload_var: Variable,
|
||||
err_payload_var: Variable,
|
||||
err_ext_var: Variable,
|
||||
},
|
||||
|
||||
Return {
|
||||
return_value: Box<Loc<Expr>>,
|
||||
return_var: Variable,
|
||||
|
@ -403,9 +414,10 @@ impl Expr {
|
|||
}
|
||||
Self::Expect { .. } => Category::Expect,
|
||||
Self::Crash { .. } => Category::Crash,
|
||||
Self::Return { .. } => Category::Return,
|
||||
Self::Return { .. } => Category::Return(EarlyReturnKind::Return),
|
||||
|
||||
Self::Dbg { .. } => Category::Expect,
|
||||
Self::Try { .. } => Category::TrySuccess,
|
||||
|
||||
// these nodes place no constraints on the expression's type
|
||||
Self::RuntimeError(..) => Category::Unknown,
|
||||
|
@ -429,7 +441,7 @@ impl Expr {
|
|||
| Self::ZeroArgumentTag { .. }
|
||||
| Self::OpaqueWrapFunction(_)
|
||||
| Self::RuntimeError(..) => false,
|
||||
Self::Return { .. } => true,
|
||||
Self::Return { .. } | Self::Try { .. } => true,
|
||||
Self::List { loc_elems, .. } => loc_elems
|
||||
.iter()
|
||||
.any(|elem| elem.value.contains_any_early_returns()),
|
||||
|
@ -545,7 +557,7 @@ pub struct ClosureData {
|
|||
pub closure_type: Variable,
|
||||
pub return_type: Variable,
|
||||
pub fx_type: Variable,
|
||||
pub early_returns: Vec<(Variable, Region)>,
|
||||
pub early_returns: Vec<(Variable, Region, EarlyReturnKind)>,
|
||||
pub name: Symbol,
|
||||
pub captured_symbols: Vec<(Symbol, Variable)>,
|
||||
pub recursive: Recursive,
|
||||
|
@ -1385,6 +1397,28 @@ pub fn canonicalize_expr<'a>(
|
|||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::LowLevelTry(loc_expr) => {
|
||||
let (loc_result_expr, output) =
|
||||
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||
|
||||
let return_var = var_store.fresh();
|
||||
|
||||
scope
|
||||
.early_returns
|
||||
.push((return_var, loc_expr.region, EarlyReturnKind::Try));
|
||||
|
||||
(
|
||||
Try {
|
||||
result_expr: Box::new(loc_result_expr),
|
||||
result_var: var_store.fresh(),
|
||||
return_var,
|
||||
ok_payload_var: var_store.fresh(),
|
||||
err_payload_var: var_store.fresh(),
|
||||
err_ext_var: var_store.fresh(),
|
||||
},
|
||||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::Return(return_expr, after_return) => {
|
||||
let mut output = Output::default();
|
||||
|
||||
|
@ -1409,7 +1443,9 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
let return_var = var_store.fresh();
|
||||
|
||||
scope.early_returns.push((return_var, return_expr.region));
|
||||
scope
|
||||
.early_returns
|
||||
.push((return_var, return_expr.region, EarlyReturnKind::Return));
|
||||
|
||||
(
|
||||
Return {
|
||||
|
@ -2351,6 +2387,29 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
|
|||
}
|
||||
}
|
||||
|
||||
Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
} => {
|
||||
let loc_result_expr = Loc {
|
||||
region: result_expr.region,
|
||||
value: inline_calls(var_store, result_expr.value),
|
||||
};
|
||||
|
||||
Try {
|
||||
result_expr: Box::new(loc_result_expr),
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
}
|
||||
}
|
||||
|
||||
LetRec(defs, loc_expr, mark) => {
|
||||
let mut new_defs = Vec::with_capacity(defs.len());
|
||||
|
||||
|
@ -2645,6 +2704,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
| ast::Expr::MalformedIdent(_, _)
|
||||
| ast::Expr::Tag(_)
|
||||
| ast::Expr::OpaqueRef(_) => true,
|
||||
ast::Expr::LowLevelTry(loc_expr) => is_valid_interpolation(&loc_expr.value),
|
||||
// Newlines are disallowed inside interpolation, and these all require newlines
|
||||
ast::Expr::DbgStmt { .. }
|
||||
| ast::Expr::LowLevelDbg(_, _, _)
|
||||
|
@ -3401,7 +3461,7 @@ pub struct FunctionDef {
|
|||
pub closure_type: Variable,
|
||||
pub return_type: Variable,
|
||||
pub fx_type: Variable,
|
||||
pub early_returns: Vec<(Variable, Region)>,
|
||||
pub early_returns: Vec<(Variable, Region, EarlyReturnKind)>,
|
||||
pub captured_symbols: Vec<(Symbol, Variable)>,
|
||||
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
|
||||
}
|
||||
|
@ -3543,6 +3603,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::Try { result_expr, .. } => {
|
||||
stack.push(&result_expr.value);
|
||||
}
|
||||
Expr::Return { return_value, .. } => {
|
||||
stack.push(&return_value.value);
|
||||
}
|
||||
|
|
|
@ -371,9 +371,10 @@ pub fn canonicalize_module_defs<'a>(
|
|||
PatternType::TopLevelDef,
|
||||
);
|
||||
|
||||
for (_early_return_var, early_return_region) in &scope.early_returns {
|
||||
for (_early_return_var, early_return_region, early_return_kind) in &scope.early_returns {
|
||||
env.problem(Problem::ReturnOutsideOfFunction {
|
||||
region: *early_return_region,
|
||||
return_kind: *early_return_kind,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -964,6 +965,14 @@ fn fix_values_captured_in_closure_expr(
|
|||
);
|
||||
}
|
||||
|
||||
Try { result_expr, .. } => {
|
||||
fix_values_captured_in_closure_expr(
|
||||
&mut result_expr.value,
|
||||
no_capture_symbols,
|
||||
closure_captures,
|
||||
);
|
||||
}
|
||||
|
||||
Return { return_value, .. } => {
|
||||
fix_values_captured_in_closure_expr(
|
||||
&mut return_value.value,
|
||||
|
|
|
@ -5,7 +5,7 @@ use roc_module::symbol::{IdentId, IdentIds, ModuleId, ModuleIds, Symbol};
|
|||
use roc_problem::can::{RuntimeError, ScopeModuleSource};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::{Alias, AliasKind, AliasVar, Type};
|
||||
use roc_types::types::{Alias, AliasKind, AliasVar, EarlyReturnKind, Type};
|
||||
|
||||
use crate::abilities::PendingAbilitiesStore;
|
||||
|
||||
|
@ -49,7 +49,7 @@ pub struct Scope {
|
|||
/// 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)>,
|
||||
pub early_returns: Vec<(Variable, Region, EarlyReturnKind)>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
|
|
|
@ -400,6 +400,16 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
|
|||
Variable::NULL,
|
||||
);
|
||||
}
|
||||
Expr::Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var: _,
|
||||
ok_payload_var: _,
|
||||
err_payload_var: _,
|
||||
err_ext_var: _,
|
||||
} => {
|
||||
visitor.visit_expr(&result_expr.value, result_expr.region, *result_var);
|
||||
}
|
||||
Expr::Return {
|
||||
return_value,
|
||||
return_var,
|
||||
|
|
|
@ -816,64 +816,13 @@ mod test_can {
|
|||
|
||||
// Assert that we desugar to:
|
||||
//
|
||||
// when Str.toU64 "123" is
|
||||
// Ok `0` -> `0`
|
||||
// Err `1` -> return Err `1`
|
||||
// Try(Str.toU64 "123")
|
||||
|
||||
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
|
||||
let cond_expr = assert_try_expr(&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]
|
||||
|
@ -890,64 +839,13 @@ mod test_can {
|
|||
|
||||
// Assert that we desugar to:
|
||||
//
|
||||
// when Str.toU64 "123" is
|
||||
// Ok `0` -> `0`
|
||||
// Err `1` -> return Err `1`
|
||||
// Try(Str.toU64 "123")
|
||||
|
||||
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
|
||||
let cond_expr = assert_try_expr(&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]
|
||||
|
@ -964,64 +862,13 @@ mod test_can {
|
|||
|
||||
// Assert that we desugar to:
|
||||
//
|
||||
// when Str.toU64 "123" is
|
||||
// Ok `0` -> `0`
|
||||
// Err `1` -> return Err `1`
|
||||
// Try(Str.toU64 "123")
|
||||
|
||||
let (cond_expr, branches) = assert_when(&out.loc_expr.value);
|
||||
let cond_expr = assert_try_expr(&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]
|
||||
|
@ -1142,6 +989,13 @@ mod test_can {
|
|||
}
|
||||
}
|
||||
|
||||
fn assert_try_expr(expr: &Expr) -> &Expr {
|
||||
match expr {
|
||||
Expr::Try { result_expr, .. } => &result_expr.value,
|
||||
_ => panic!("Expr was not a Try: {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
// TAIL CALLS
|
||||
fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
|
||||
match expr {
|
||||
|
|
|
@ -30,8 +30,8 @@ use roc_region::all::{Loc, Region};
|
|||
use roc_types::subs::{IllegalCycleMark, Variable};
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{
|
||||
AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField,
|
||||
TypeExtension, TypeTag, Types,
|
||||
AliasKind, AnnotationSource, Category, EarlyReturnKind, IndexOrField, OptAbleType, PReason,
|
||||
Reason, RecordField, TypeExtension, TypeTag, Types,
|
||||
};
|
||||
use soa::{Index, Slice};
|
||||
|
||||
|
@ -152,7 +152,7 @@ fn constrain_untyped_closure(
|
|||
closure_var: Variable,
|
||||
ret_var: Variable,
|
||||
fx_var: Variable,
|
||||
early_returns: &[(Variable, Region)],
|
||||
early_returns: &[(Variable, Region, EarlyReturnKind)],
|
||||
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
|
||||
loc_body_expr: &Loc<Expr>,
|
||||
captured_symbols: &[(Symbol, Variable)],
|
||||
|
@ -184,30 +184,16 @@ fn constrain_untyped_closure(
|
|||
));
|
||||
|
||||
let returns_constraint = env.with_fx_expectation(fx_var, None, |env| {
|
||||
let return_con = constrain_expr(
|
||||
constrain_function_return(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
loc_body_expr,
|
||||
early_returns,
|
||||
return_type_index,
|
||||
);
|
||||
|
||||
let mut return_constraints = Vec::with_capacity(early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
|
||||
for (early_return_variable, early_return_region) in early_returns {
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
return_type_index,
|
||||
Category::Return,
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
}
|
||||
|
||||
constraints.and_constraint(return_constraints)
|
||||
ret_var,
|
||||
false,
|
||||
)
|
||||
});
|
||||
|
||||
// make sure the captured symbols are sorted!
|
||||
|
@ -259,6 +245,51 @@ fn constrain_untyped_closure(
|
|||
constraints.exists_many(vars, cons)
|
||||
}
|
||||
|
||||
pub fn constrain_function_return(
|
||||
types: &mut Types,
|
||||
constraints: &mut Constraints,
|
||||
env: &mut Env,
|
||||
body_expr: &Loc<Expr>,
|
||||
early_returns: &[(Variable, Region, EarlyReturnKind)],
|
||||
return_type_expected: ExpectedTypeIndex,
|
||||
ret_var: Variable,
|
||||
should_attach_res_constraints: bool,
|
||||
) -> Constraint {
|
||||
let return_con = constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
body_expr.region,
|
||||
&body_expr.value,
|
||||
return_type_expected,
|
||||
);
|
||||
|
||||
let mut return_constraints = Vec::with_capacity(early_returns.len() + 1);
|
||||
let mut return_type_vars = Vec::with_capacity(early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
return_type_vars.push(ret_var);
|
||||
|
||||
for (early_return_variable, early_return_region, early_return_kind) in early_returns {
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
return_type_expected,
|
||||
Category::Return(*early_return_kind),
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
return_type_vars.push(*early_return_variable);
|
||||
}
|
||||
|
||||
let returns_constraint = constraints.exists_many(return_type_vars, return_constraints);
|
||||
|
||||
if should_attach_res_constraints {
|
||||
attach_resolution_constraints(constraints, env, returns_constraint)
|
||||
} else {
|
||||
returns_constraint
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_expr(
|
||||
types: &mut Types,
|
||||
constraints: &mut Constraints,
|
||||
|
@ -576,7 +607,6 @@ pub fn constrain_expr(
|
|||
let mut arg_cons = Vec::with_capacity(loc_args.len());
|
||||
|
||||
for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() {
|
||||
let region = loc_arg.region;
|
||||
let arg_type = Variable(*arg_var);
|
||||
let arg_type_index = constraints.push_variable(*arg_var);
|
||||
|
||||
|
@ -833,6 +863,78 @@ pub fn constrain_expr(
|
|||
constraints.exists_many([*variable], [message_con, continuation_con])
|
||||
}
|
||||
|
||||
Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
} => {
|
||||
let result_var_index = constraints.push_variable(*result_var);
|
||||
let result_expected_type = constraints.push_expected_type(ForReason(
|
||||
Reason::TryResult,
|
||||
result_var_index,
|
||||
result_expr.region,
|
||||
));
|
||||
let result_constraint = constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
result_expr.region,
|
||||
&result_expr.value,
|
||||
result_expected_type,
|
||||
);
|
||||
|
||||
let try_target_constraint = constraints.try_target(
|
||||
result_var_index,
|
||||
*ok_payload_var,
|
||||
*err_payload_var,
|
||||
result_expr.region,
|
||||
);
|
||||
|
||||
let return_type_index = constraints.push_variable(*return_var);
|
||||
let expected_return_value = constraints.push_expected_type(ForReason(
|
||||
Reason::TryResult,
|
||||
return_type_index,
|
||||
result_expr.region,
|
||||
));
|
||||
|
||||
let try_failure_type_index = {
|
||||
let typ = types.from_old_type(&Type::TagUnion(
|
||||
vec![("Err".into(), vec![Type::Variable(*err_payload_var)])],
|
||||
TypeExtension::from_non_annotation_type(Type::Variable(*err_ext_var)),
|
||||
));
|
||||
constraints.push_type(types, typ)
|
||||
};
|
||||
let try_failure_constraint = constraints.equal_types(
|
||||
try_failure_type_index,
|
||||
expected_return_value,
|
||||
Category::TryFailure,
|
||||
region,
|
||||
);
|
||||
|
||||
let ok_type_index = constraints.push_variable(*ok_payload_var);
|
||||
let try_success_constraint =
|
||||
constraints.equal_types(ok_type_index, expected, Category::TrySuccess, region);
|
||||
|
||||
constraints.exists_many(
|
||||
[
|
||||
*return_var,
|
||||
*result_var,
|
||||
*ok_payload_var,
|
||||
*err_payload_var,
|
||||
*err_ext_var,
|
||||
],
|
||||
[
|
||||
result_constraint,
|
||||
try_target_constraint,
|
||||
try_failure_constraint,
|
||||
try_success_constraint,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
branch_var,
|
||||
|
@ -1018,26 +1120,25 @@ pub fn constrain_expr(
|
|||
Region::span_across(&loc_cond.region, &branches.last().unwrap().value.region)
|
||||
};
|
||||
|
||||
let branch_expr_reason =
|
||||
|expected: &Expected<TypeOrVar>, index, branch_region| match expected {
|
||||
FromAnnotation(name, arity, ann_source, _typ) => {
|
||||
// NOTE deviation from elm.
|
||||
//
|
||||
// in elm, `_typ` is used, but because we have this `expr_var` too
|
||||
// and need to constrain it, this is what works and gives better error messages
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
*arity,
|
||||
AnnotationSource::TypedWhenBranch {
|
||||
index,
|
||||
region: ann_source.region(),
|
||||
},
|
||||
body_type_index,
|
||||
)
|
||||
}
|
||||
let branch_expr_reason = |expected: &Expected<TypeOrVar>, index| match expected {
|
||||
FromAnnotation(name, arity, ann_source, _typ) => {
|
||||
// NOTE deviation from elm.
|
||||
//
|
||||
// in elm, `_typ` is used, but because we have this `expr_var` too
|
||||
// and need to constrain it, this is what works and gives better error messages
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
*arity,
|
||||
AnnotationSource::TypedWhenBranch {
|
||||
index,
|
||||
region: ann_source.region(),
|
||||
},
|
||||
body_type_index,
|
||||
)
|
||||
}
|
||||
|
||||
_ => ForReason(Reason::WhenBranch { index }, body_type_index, branch_region),
|
||||
};
|
||||
_ => ForReason(Reason::WhenBranch { index }, body_type_index, region),
|
||||
};
|
||||
|
||||
// Our goal is to constrain and introduce variables in all pattern when branch patterns before
|
||||
// looking at their bodies.
|
||||
|
@ -1091,11 +1192,7 @@ pub fn constrain_expr(
|
|||
region,
|
||||
when_branch,
|
||||
expected_pattern,
|
||||
branch_expr_reason(
|
||||
&constraints[expected],
|
||||
HumanIndex::zero_based(index),
|
||||
when_branch.value.region,
|
||||
),
|
||||
branch_expr_reason(&constraints[expected], HumanIndex::zero_based(index)),
|
||||
);
|
||||
|
||||
pattern_vars.extend(new_pattern_vars);
|
||||
|
@ -2074,43 +2171,19 @@ fn constrain_function_def(
|
|||
constraints.push_type(types, fn_type)
|
||||
};
|
||||
|
||||
let returns_constraint = {
|
||||
let return_con = constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
return_type_annotation_expected,
|
||||
);
|
||||
|
||||
let mut return_constraints =
|
||||
Vec::with_capacity(function_def.early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
|
||||
for (early_return_variable, early_return_region) in &function_def.early_returns {
|
||||
let early_return_type_expected =
|
||||
constraints.push_expected_type(Expected::ForReason(
|
||||
Reason::FunctionOutput,
|
||||
ret_type_index,
|
||||
*early_return_region,
|
||||
));
|
||||
|
||||
vars.push(*early_return_variable);
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
early_return_type_expected,
|
||||
Category::Return,
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
}
|
||||
|
||||
let returns_constraint = constraints.and_constraint(return_constraints);
|
||||
|
||||
attach_resolution_constraints(constraints, env, returns_constraint)
|
||||
};
|
||||
let returns_constraint =
|
||||
env.with_fx_expectation(function_def.fx_type, Some(annotation.region), |env| {
|
||||
constrain_function_return(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr,
|
||||
&function_def.early_returns,
|
||||
return_type_annotation_expected,
|
||||
function_def.return_type,
|
||||
true,
|
||||
)
|
||||
});
|
||||
|
||||
vars.push(expr_var);
|
||||
|
||||
|
@ -2459,7 +2532,7 @@ fn constrain_when_branch_help(
|
|||
types,
|
||||
constraints,
|
||||
env,
|
||||
region,
|
||||
when_branch.value.region,
|
||||
&when_branch.value.value,
|
||||
expr_expected,
|
||||
);
|
||||
|
@ -2966,32 +3039,16 @@ fn constrain_typed_def(
|
|||
|
||||
let returns_constraint =
|
||||
env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
let return_con = constrain_expr(
|
||||
constrain_function_return(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
loc_body_expr,
|
||||
early_returns,
|
||||
return_type,
|
||||
);
|
||||
|
||||
let mut return_constraints = Vec::with_capacity(early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
|
||||
for (early_return_variable, early_return_region) in early_returns {
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
return_type,
|
||||
Category::Return,
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
}
|
||||
|
||||
let returns_constraint = constraints.and_constraint(return_constraints);
|
||||
|
||||
attach_resolution_constraints(constraints, env, returns_constraint)
|
||||
ret_var,
|
||||
true,
|
||||
)
|
||||
});
|
||||
|
||||
vars.push(*fn_var);
|
||||
|
@ -4025,35 +4082,22 @@ fn constraint_recursive_function(
|
|||
|
||||
let returns_constraint =
|
||||
env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
let expected = constraints.push_expected_type(NoExpectation(ret_type_index));
|
||||
let return_con = constrain_expr(
|
||||
let expected = constraints.push_expected_type(ForReason(
|
||||
Reason::FunctionOutput,
|
||||
ret_type_index,
|
||||
region,
|
||||
));
|
||||
|
||||
constrain_function_return(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
loc_body_expr,
|
||||
&function_def.early_returns,
|
||||
expected,
|
||||
);
|
||||
|
||||
let mut return_constraints =
|
||||
Vec::with_capacity(function_def.early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
|
||||
for (early_return_variable, early_return_region) in &function_def.early_returns
|
||||
{
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
expected,
|
||||
Category::Return,
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
}
|
||||
|
||||
let returns_constraint = constraints.and_constraint(return_constraints);
|
||||
|
||||
attach_resolution_constraints(constraints, env, returns_constraint)
|
||||
ret_var,
|
||||
true,
|
||||
)
|
||||
});
|
||||
|
||||
vars.push(expr_var);
|
||||
|
@ -4406,6 +4450,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
|
|||
| RecordUpdate { .. }
|
||||
| Expect { .. }
|
||||
| Dbg { .. }
|
||||
| Try { .. }
|
||||
| Return { .. }
|
||||
| RuntimeError(..)
|
||||
| ZeroArgumentTag { .. }
|
||||
|
@ -4597,37 +4642,20 @@ fn rec_defs_help(
|
|||
};
|
||||
let returns_constraint =
|
||||
env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
|
||||
let return_type_expected =
|
||||
constraints.push_expected_type(NoExpectation(ret_type_index));
|
||||
let return_type_expected = constraints.push_expected_type(
|
||||
ForReason(Reason::FunctionOutput, ret_type_index, region),
|
||||
);
|
||||
|
||||
let return_con = constrain_expr(
|
||||
constrain_function_return(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
loc_body_expr.region,
|
||||
&loc_body_expr.value,
|
||||
loc_body_expr,
|
||||
early_returns,
|
||||
return_type_expected,
|
||||
);
|
||||
|
||||
let mut return_constraints =
|
||||
Vec::with_capacity(early_returns.len() + 1);
|
||||
return_constraints.push(return_con);
|
||||
|
||||
for (early_return_variable, early_return_region) in early_returns {
|
||||
let early_return_con = constraints.equal_types_var(
|
||||
*early_return_variable,
|
||||
return_type_expected,
|
||||
Category::Return,
|
||||
*early_return_region,
|
||||
);
|
||||
|
||||
return_constraints.push(early_return_con);
|
||||
}
|
||||
|
||||
let returns_constraint =
|
||||
constraints.and_constraint(return_constraints);
|
||||
|
||||
attach_resolution_constraints(constraints, env, returns_constraint)
|
||||
ret_var,
|
||||
true,
|
||||
)
|
||||
});
|
||||
|
||||
vars.push(*fn_var);
|
||||
|
|
|
@ -67,10 +67,6 @@ 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, _) => {
|
||||
let apply_needs_parens = parens == Parens::InApply;
|
||||
|
||||
|
@ -201,6 +197,13 @@ impl<'a> Formattable for Expr<'a> {
|
|||
LowLevelDbg(_, _, _) => unreachable!(
|
||||
"LowLevelDbg should only exist after desugaring, not during formatting"
|
||||
),
|
||||
Try => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("try");
|
||||
}
|
||||
LowLevelTry(_) => unreachable!(
|
||||
"LowLevelTry should only exist after desugaring, not during formatting"
|
||||
),
|
||||
Return(return_value, after_return) => {
|
||||
fmt_return(buf, return_value, after_return, parens, newlines, indent);
|
||||
}
|
||||
|
@ -367,6 +370,9 @@ pub fn expr_is_multiline(me: &Expr<'_>, comments_only: bool) -> bool {
|
|||
| Expr::Crash
|
||||
| Expr::Dbg
|
||||
| Expr::Try => false,
|
||||
Expr::LowLevelTry(_) => {
|
||||
unreachable!("LowLevelTry should only exist after desugaring, not during formatting")
|
||||
}
|
||||
|
||||
Expr::RecordAccess(inner, _)
|
||||
| Expr::TupleAccess(inner, _)
|
||||
|
|
|
@ -1203,7 +1203,8 @@ mod test_reporting {
|
|||
@r"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression is used in an unexpected way:
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
5│ f = \x -> g x
|
||||
^^^
|
||||
|
@ -1212,7 +1213,7 @@ mod test_reporting {
|
|||
|
||||
List List a
|
||||
|
||||
But you are trying to use it as:
|
||||
But I expected the function to have return type:
|
||||
|
||||
List a
|
||||
|
||||
|
@ -1239,7 +1240,8 @@ mod test_reporting {
|
|||
@r"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression is used in an unexpected way:
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
7│ g = \x -> f [x]
|
||||
^^^^^
|
||||
|
@ -1248,7 +1250,7 @@ mod test_reporting {
|
|||
|
||||
List List b
|
||||
|
||||
But you are trying to use it as:
|
||||
But I expected the function to have return type:
|
||||
|
||||
List b
|
||||
|
||||
|
@ -6626,7 +6628,7 @@ All branches in an `if` must have the same type!
|
|||
@r"
|
||||
── UNFINISHED FUNCTION in tmp/unfinished_closure_pattern_in_parens/Test.roc ────
|
||||
|
||||
I was partway through parsing a function, but I got stuck here:
|
||||
I was partway through parsing a function, but I got stuck here:
|
||||
|
||||
4│ x = \( a
|
||||
5│ )
|
||||
|
@ -10258,16 +10260,17 @@ All branches in an `if` must have the same type!
|
|||
@r"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression is used in an unexpected way:
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
5│ f = \_ -> if Bool.true then {} else f {}
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is a value of type:
|
||||
It a value of type:
|
||||
|
||||
{}
|
||||
|
||||
But you are trying to use it as:
|
||||
But I expected the function to have return type:
|
||||
|
||||
* -> Str
|
||||
"
|
||||
|
@ -10414,11 +10417,12 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Something is off with the 2nd branch of this `when` expression:
|
||||
|
||||
10│ olist : OList
|
||||
11│ olist =
|
||||
12│> when alist is
|
||||
13│> Nil -> @OList Nil
|
||||
14│> Cons _ lst -> lst
|
||||
10│ olist : OList
|
||||
11│ olist =
|
||||
12│ when alist is
|
||||
13│ Nil -> @OList Nil
|
||||
14│ Cons _ lst -> lst
|
||||
^^^
|
||||
|
||||
This `lst` value is a:
|
||||
|
||||
|
@ -10449,6 +10453,7 @@ All branches in an `if` must have the same type!
|
|||
|
||||
This 2nd argument to `map` has an unexpected type:
|
||||
|
||||
4│ A := U8
|
||||
5│ List.map [1u16, 2u16, 3u16] @A
|
||||
^^
|
||||
|
||||
|
@ -14556,7 +14561,7 @@ All branches in an `if` must have the same type!
|
|||
@r###"
|
||||
── RETURN OUTSIDE OF FUNCTION in /code/proj/Main.roc ───────────────────────────
|
||||
|
||||
This `return` statement doesn't belong to a function:
|
||||
This `return` doesn't belong to a function:
|
||||
|
||||
7│ return x
|
||||
^^^^^^^^
|
||||
|
@ -14634,25 +14639,25 @@ All branches in an `if` must have the same type!
|
|||
myFunction 3
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
@r#"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This `return` statement doesn't match the return type of its enclosing
|
||||
function:
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
5│ if x == 5 then
|
||||
6│> return "abc"
|
||||
7│ else
|
||||
8│ x
|
||||
5│ if x == 5 then
|
||||
6│> return "abc"
|
||||
7│ else
|
||||
8│ x
|
||||
|
||||
This returns a value of type:
|
||||
This returns a value of type:
|
||||
|
||||
Str
|
||||
Str
|
||||
|
||||
But I expected the function to have return type:
|
||||
But I expected the function to have return type:
|
||||
|
||||
Num *
|
||||
"###
|
||||
Num *
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -14701,6 +14706,54 @@ All branches in an `if` must have the same type!
|
|||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
return_in_bare_statement,
|
||||
indoc!(
|
||||
r#"
|
||||
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! = \{} ->
|
||||
Effect.putLine! "hello"
|
||||
|
||||
# this outputs {}, so it's ignored
|
||||
if 7 > 5 then
|
||||
{}
|
||||
else
|
||||
return Err TooBig
|
||||
|
||||
# this outputs a value, so we are incorrectly
|
||||
# dropping the parsed value
|
||||
when List.get [1, 2, 3] 5 is
|
||||
Ok item -> item
|
||||
Err err ->
|
||||
return Err err
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── IGNORED RESULT in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
The result of this expression is ignored:
|
||||
|
||||
16│> when List.get [1, 2, 3] 5 is
|
||||
17│> Ok item -> item
|
||||
18│> Err err ->
|
||||
19│> return Err err
|
||||
|
||||
Standalone statements are required to produce an empty record, but the
|
||||
type of this one is:
|
||||
|
||||
Num *
|
||||
|
||||
If you still want to ignore it, assign it to `_`, like this:
|
||||
|
||||
_ = File.delete! "data.json"
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
mismatch_only_early_returns,
|
||||
indoc!(
|
||||
|
@ -14714,26 +14767,26 @@ All branches in an `if` must have the same type!
|
|||
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│ return 123
|
||||
^^^^^^^^^^
|
||||
|
||||
This returns a value of type:
|
||||
|
||||
Num *
|
||||
|
||||
But I expected the function to have return type:
|
||||
|
||||
Str
|
||||
"###
|
||||
@r#"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
5│ if x == 5 then
|
||||
6│ return "abc"
|
||||
7│ else
|
||||
8│ return 123
|
||||
^^^^^^^^^^
|
||||
|
||||
This returns a value of type:
|
||||
|
||||
Num *
|
||||
|
||||
But I expected the function to have return type:
|
||||
|
||||
Str
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -14776,6 +14829,61 @@ All branches in an `if` must have the same type!
|
|||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
return_with_ignored_output,
|
||||
indoc!(
|
||||
r#"
|
||||
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! = \{} ->
|
||||
Effect.putLine! "hello"
|
||||
|
||||
# not ignored, warning
|
||||
when List.get [1, 2, 3] 5 is
|
||||
Ok item -> item
|
||||
Err err ->
|
||||
return Err err
|
||||
|
||||
# ignored, OK
|
||||
_ =
|
||||
when List.get [1, 2, 3] 5 is
|
||||
Ok item -> item
|
||||
Err err ->
|
||||
return Err err
|
||||
|
||||
# also ignored, also OK
|
||||
_ignored =
|
||||
when List.get [1, 2, 3] 5 is
|
||||
Ok item -> item
|
||||
Err err ->
|
||||
return Err err
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── IGNORED RESULT in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
The result of this expression is ignored:
|
||||
|
||||
9│> when List.get [1, 2, 3] 5 is
|
||||
10│> Ok item -> item
|
||||
11│> Err err ->
|
||||
12│> return Err err
|
||||
|
||||
Standalone statements are required to produce an empty record, but the
|
||||
type of this one is:
|
||||
|
||||
Num *
|
||||
|
||||
If you still want to ignore it, assign it to `_`, like this:
|
||||
|
||||
_ = File.delete! "data.json"
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
no_early_return_in_bare_statement,
|
||||
indoc!(
|
||||
|
@ -14908,6 +15016,141 @@ All branches in an `if` must have the same type!
|
|||
@"" // no errors
|
||||
);
|
||||
|
||||
test_report!(
|
||||
try_with_non_result_target,
|
||||
indoc!(
|
||||
r#"
|
||||
invalidTry = \{} ->
|
||||
x = try 64
|
||||
|
||||
Ok (x * 2)
|
||||
|
||||
invalidTry {}
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
── INVALID TRY TARGET in /code/proj/Main.roc ───────────────────────────────────
|
||||
|
||||
This expression cannot be used as a `try` target:
|
||||
|
||||
5│ x = try 64
|
||||
^^
|
||||
|
||||
I expected a Result, but it actually has type:
|
||||
|
||||
Num *
|
||||
|
||||
Hint: Did you forget to wrap the value with an `Ok` or an `Err` tag?
|
||||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
incompatible_try_errs,
|
||||
indoc!(
|
||||
r#"
|
||||
incompatibleTrys = \{} ->
|
||||
x = try Err 123
|
||||
|
||||
y = try Err "abc"
|
||||
|
||||
Ok (x + y)
|
||||
|
||||
incompatibleTrys {}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This returns something that's incompatible with the return type of the
|
||||
enclosing function:
|
||||
|
||||
5│ x = try Err 123
|
||||
6│
|
||||
7│> y = try Err "abc"
|
||||
8│
|
||||
9│ Ok (x + y)
|
||||
|
||||
This returns an `Err` of type:
|
||||
|
||||
[Err Str, …]
|
||||
|
||||
But I expected the function to have return type:
|
||||
|
||||
[Err (Num *), …]a
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
try_prefix_in_pipe,
|
||||
indoc!(
|
||||
r#"
|
||||
readFile : Str -> Str
|
||||
|
||||
getFileContents : Str -> Result Str _
|
||||
getFileContents = \filePath ->
|
||||
contents =
|
||||
readFile filePath
|
||||
|> try Result.mapErr ErrWrapper
|
||||
|
||||
contents
|
||||
|
||||
getFileContents "file.txt"
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This 1st argument to this function has an unexpected type:
|
||||
|
||||
9│> readFile filePath
|
||||
10│ |> try Result.mapErr ErrWrapper
|
||||
|
||||
This `readFile` call produces:
|
||||
|
||||
Str
|
||||
|
||||
But this function needs its 1st argument to be:
|
||||
|
||||
Result ok a
|
||||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
try_suffix_in_pipe,
|
||||
indoc!(
|
||||
r#"
|
||||
readFile : Str -> Str
|
||||
|
||||
getFileContents : Str -> Result Str _
|
||||
getFileContents = \filePath ->
|
||||
contents =
|
||||
readFile filePath
|
||||
|> Result.mapErr ErrWrapper
|
||||
|> try
|
||||
|
||||
contents
|
||||
|
||||
getFileContents "file.txt"
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This 1st argument to |> has an unexpected type:
|
||||
|
||||
9│> readFile filePath
|
||||
10│ |> Result.mapErr ErrWrapper
|
||||
|
||||
This `readFile` call produces:
|
||||
|
||||
Str
|
||||
|
||||
But |> needs its 1st argument to be:
|
||||
|
||||
Result ok a
|
||||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
leftover_statement,
|
||||
indoc!(
|
||||
|
|
|
@ -373,6 +373,16 @@ impl<'a> LowerParams<'a> {
|
|||
expr_stack.push(&mut loc_message.value);
|
||||
expr_stack.push(&mut loc_continuation.value);
|
||||
}
|
||||
Try {
|
||||
result_expr,
|
||||
result_var: _,
|
||||
return_var: _,
|
||||
ok_payload_var: _,
|
||||
err_payload_var: _,
|
||||
err_ext_var: _,
|
||||
} => {
|
||||
expr_stack.push(&mut result_expr.value);
|
||||
}
|
||||
Return {
|
||||
return_value,
|
||||
return_var: _,
|
||||
|
|
|
@ -104,7 +104,8 @@ pub fn remove_module_param_arguments(
|
|||
| TypeError::FxInTopLevel(_, _)
|
||||
| TypeError::ExpectedEffectful(_, _)
|
||||
| TypeError::UnsuffixedEffectfulFunction(_, _)
|
||||
| TypeError::SuffixedPureFunction(_, _) => {}
|
||||
| TypeError::SuffixedPureFunction(_, _)
|
||||
| TypeError::InvalidTryTarget(_, _) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +189,8 @@ fn remove_for_reason(
|
|||
| Reason::CrashArg
|
||||
| Reason::ImportParams(_)
|
||||
| Reason::Stmt(_)
|
||||
| Reason::FunctionOutput => {}
|
||||
| Reason::FunctionOutput
|
||||
| Reason::TryResult => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::layout::{
|
|||
use bumpalo::collections::{CollectIn, Vec};
|
||||
use bumpalo::Bump;
|
||||
use roc_can::abilities::SpecializationId;
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup};
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup, WhenBranch, WhenBranchPattern};
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
|
||||
use roc_collections::VecMap;
|
||||
|
@ -2930,7 +2930,7 @@ fn pattern_to_when_help(
|
|||
body: Loc<roc_can::expr::Expr>,
|
||||
symbol: Symbol,
|
||||
) -> (Symbol, Loc<roc_can::expr::Expr>) {
|
||||
use roc_can::expr::{Expr, WhenBranch, WhenBranchPattern};
|
||||
use roc_can::expr::Expr;
|
||||
|
||||
let wrapped_body = Expr::When {
|
||||
cond_var: pattern_var,
|
||||
|
@ -5871,6 +5871,85 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
} => {
|
||||
let ok_symbol = env.unique_symbol();
|
||||
let err_symbol = env.unique_symbol();
|
||||
|
||||
let ok_branch = WhenBranch {
|
||||
patterns: vec![WhenBranchPattern {
|
||||
pattern: Loc::at_zero(roc_can::pattern::Pattern::AppliedTag {
|
||||
whole_var: result_var,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
tag_name: "Ok".into(),
|
||||
arguments: vec![(
|
||||
ok_payload_var,
|
||||
Loc::at_zero(roc_can::pattern::Pattern::Identifier(ok_symbol)),
|
||||
)],
|
||||
}),
|
||||
degenerate: false,
|
||||
}],
|
||||
value: Loc::at_zero(Var(ok_symbol, ok_payload_var)),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
};
|
||||
|
||||
let err_branch = WhenBranch {
|
||||
patterns: vec![WhenBranchPattern {
|
||||
pattern: Loc::at_zero(roc_can::pattern::Pattern::AppliedTag {
|
||||
whole_var: result_var,
|
||||
ext_var: err_ext_var,
|
||||
tag_name: "Err".into(),
|
||||
arguments: vec![(
|
||||
err_payload_var,
|
||||
Loc::at_zero(roc_can::pattern::Pattern::Identifier(err_symbol)),
|
||||
)],
|
||||
}),
|
||||
degenerate: false,
|
||||
}],
|
||||
value: Loc::at_zero(Return {
|
||||
return_var,
|
||||
return_value: Box::new(Loc::at_zero(Tag {
|
||||
tag_union_var: return_var,
|
||||
ext_var: err_ext_var,
|
||||
name: "Err".into(),
|
||||
arguments: vec![(
|
||||
err_payload_var,
|
||||
Loc::at_zero(Var(err_symbol, err_payload_var)),
|
||||
)],
|
||||
})),
|
||||
}),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
};
|
||||
|
||||
let result_region = result_expr.region;
|
||||
let when_expr = When {
|
||||
loc_cond: result_expr,
|
||||
cond_var: result_var,
|
||||
expr_var: ok_payload_var,
|
||||
region: result_region,
|
||||
branches: vec![ok_branch, err_branch],
|
||||
branches_cond_var: result_var,
|
||||
exhaustive: ExhaustiveMark::known_exhaustive(),
|
||||
};
|
||||
|
||||
with_hole(
|
||||
env,
|
||||
when_expr,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
Return {
|
||||
return_value,
|
||||
return_var,
|
||||
|
|
|
@ -511,11 +511,13 @@ pub enum Expr<'a> {
|
|||
continuation: &'a Loc<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,
|
||||
// This form of try is a desugared Result unwrapper
|
||||
LowLevelTry(&'a Loc<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>>),
|
||||
|
||||
// Application
|
||||
/// To apply by name, do Apply(Var(...), ...)
|
||||
|
@ -700,6 +702,7 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
|
|||
}
|
||||
Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
|
||||
Expr::Try => false,
|
||||
Expr::LowLevelTry(loc_expr) => is_expr_suffixed(&loc_expr.value),
|
||||
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))
|
||||
|
@ -973,6 +976,9 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
|
|||
expr_stack.push(&condition.value);
|
||||
expr_stack.push(&cont.value);
|
||||
}
|
||||
LowLevelTry(loc_expr) => {
|
||||
expr_stack.push(&loc_expr.value);
|
||||
}
|
||||
Return(return_value, after_return) => {
|
||||
if let Some(after_return) = after_return {
|
||||
expr_stack.reserve(2);
|
||||
|
@ -2513,6 +2519,7 @@ impl<'a> Malformed for Expr<'a> {
|
|||
DbgStmt { first, extra_args, continuation } => first.is_malformed() || extra_args.iter().any(|a| a.is_malformed()) || continuation.is_malformed(),
|
||||
LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
|
||||
Try => false,
|
||||
LowLevelTry(loc_expr) => loc_expr.is_malformed(),
|
||||
Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),
|
||||
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
|
||||
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),
|
||||
|
|
|
@ -2180,6 +2180,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
| Expr::Dbg
|
||||
| Expr::DbgStmt { .. }
|
||||
| Expr::LowLevelDbg(_, _, _)
|
||||
| Expr::LowLevelTry(_)
|
||||
| Expr::Return(_, _)
|
||||
| Expr::MalformedSuffixed(..)
|
||||
| Expr::PrecedenceConflict { .. }
|
||||
|
|
|
@ -727,6 +727,7 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
|||
arena.alloc(b.normalize(arena)),
|
||||
),
|
||||
Expr::Try => Expr::Try,
|
||||
Expr::LowLevelTry(a) => Expr::LowLevelTry(arena.alloc(a.normalize(arena))),
|
||||
Expr::Return(a, b) => Expr::Return(
|
||||
arena.alloc(a.normalize(arena)),
|
||||
b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))),
|
||||
|
|
|
@ -8,7 +8,7 @@ use roc_module::symbol::{ModuleId, Symbol};
|
|||
use roc_parse::ast::Base;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_types::types::{AliasKind, EarlyReturnKind};
|
||||
|
||||
use crate::Severity;
|
||||
|
||||
|
@ -244,6 +244,7 @@ pub enum Problem {
|
|||
},
|
||||
ReturnOutsideOfFunction {
|
||||
region: Region,
|
||||
return_kind: EarlyReturnKind,
|
||||
},
|
||||
StatementsAfterReturn {
|
||||
region: Region,
|
||||
|
@ -504,7 +505,7 @@ impl Problem {
|
|||
| Problem::OverAppliedDbg { region }
|
||||
| Problem::UnappliedDbg { region }
|
||||
| Problem::DefsOnlyUsedInRecursion(_, region)
|
||||
| Problem::ReturnOutsideOfFunction { region }
|
||||
| Problem::ReturnOutsideOfFunction { region, .. }
|
||||
| Problem::StatementsAfterReturn { region }
|
||||
| Problem::ReturnAtEndOfFunction { region }
|
||||
| Problem::UnsuffixedEffectfulRecordField(region)
|
||||
|
|
|
@ -16,6 +16,7 @@ use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo};
|
|||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::{
|
||||
Cycle, FxCallConstraint, FxSuffixConstraint, FxSuffixKind, LetConstraint, OpportunisticResolve,
|
||||
TryTargetConstraint,
|
||||
};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_can::module::ModuleParams;
|
||||
|
@ -908,6 +909,95 @@ fn solve(
|
|||
}
|
||||
}
|
||||
}
|
||||
TryTarget(index) => {
|
||||
let try_target_constraint = &env.constraints.try_target_constraints[index.index()];
|
||||
|
||||
let TryTargetConstraint {
|
||||
target_type_index,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
region,
|
||||
} = try_target_constraint;
|
||||
|
||||
let target_actual = either_type_index_to_var(
|
||||
env,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&mut can_types,
|
||||
aliases,
|
||||
*target_type_index,
|
||||
);
|
||||
|
||||
let wanted_result_ty = can_types.from_old_type(&Type::TagUnion(
|
||||
vec![
|
||||
("Ok".into(), vec![Type::Variable(*ok_payload_var)]),
|
||||
("Err".into(), vec![Type::Variable(*err_payload_var)]),
|
||||
],
|
||||
TypeExtension::Closed,
|
||||
));
|
||||
let wanted_result_var = type_to_var(
|
||||
env,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&mut can_types,
|
||||
aliases,
|
||||
wanted_result_ty,
|
||||
);
|
||||
|
||||
match unify(
|
||||
&mut env.uenv(),
|
||||
target_actual,
|
||||
wanted_result_var,
|
||||
UnificationMode::EQ,
|
||||
Polarity::OF_VALUE,
|
||||
) {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
env.introduce(rank, &vars);
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
env.subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(
|
||||
*region,
|
||||
Category::TryTarget,
|
||||
target_actual,
|
||||
),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
compact_lambdas_and_check_obligations(
|
||||
env,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
awaiting_specializations,
|
||||
lambda_sets_to_specialize,
|
||||
);
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, _expected_type, _bad_impls) => {
|
||||
env.introduce(rank, &vars);
|
||||
|
||||
let problem = TypeError::InvalidTryTarget(*region, actual_type);
|
||||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
Let(index, pool_slice) => {
|
||||
let let_con = &env.constraints.let_constraints[index.index()];
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ pub enum TypeError {
|
|||
ExpectedEffectful(Region, ExpectEffectfulReason),
|
||||
UnsuffixedEffectfulFunction(Region, FxSuffixKind),
|
||||
SuffixedPureFunction(Region, FxSuffixKind),
|
||||
InvalidTryTarget(Region, ErrorType),
|
||||
}
|
||||
|
||||
impl TypeError {
|
||||
|
@ -77,6 +78,7 @@ impl TypeError {
|
|||
TypeError::FxInTopLevel(_, _) => Warning,
|
||||
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
|
||||
TypeError::SuffixedPureFunction(_, _) => Warning,
|
||||
TypeError::InvalidTryTarget(_, _) => RuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +99,8 @@ impl TypeError {
|
|||
| TypeError::FxInTopLevel(region, _)
|
||||
| TypeError::ExpectedEffectful(region, _)
|
||||
| TypeError::UnsuffixedEffectfulFunction(region, _)
|
||||
| TypeError::SuffixedPureFunction(region, _) => Some(*region),
|
||||
| TypeError::SuffixedPureFunction(region, _)
|
||||
| TypeError::InvalidTryTarget(region, _) => Some(*region),
|
||||
TypeError::UnfulfilledAbility(ab, ..) => ab.region(),
|
||||
TypeError::Exhaustive(e) => Some(e.region()),
|
||||
TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region),
|
||||
|
|
|
@ -24,25 +24,25 @@ procedure Str.66 (Str.191):
|
|||
let Str.247 : [C {}, C U64] = TagId(0) Str.248;
|
||||
ret Str.247;
|
||||
|
||||
procedure Test.3 (Test.4):
|
||||
joinpoint Test.14 Test.5:
|
||||
let Test.12 : [C {}, C U64] = TagId(1) Test.5;
|
||||
ret Test.12;
|
||||
procedure Test.1 (Test.2):
|
||||
joinpoint Test.11 Test.3:
|
||||
let Test.7 : [C {}, C U64] = TagId(1) Test.3;
|
||||
ret Test.7;
|
||||
in
|
||||
let Test.13 : [C {}, C U64] = CallByName Str.26 Test.4;
|
||||
let Test.18 : U8 = 1i64;
|
||||
let Test.19 : U8 = GetTagId Test.13;
|
||||
let Test.20 : Int1 = lowlevel Eq Test.18 Test.19;
|
||||
if Test.20 then
|
||||
let Test.6 : U64 = UnionAtIndex (Id 1) (Index 0) Test.13;
|
||||
jump Test.14 Test.6;
|
||||
let Test.10 : [C {}, C U64] = CallByName Str.26 Test.2;
|
||||
let Test.15 : U8 = 1i64;
|
||||
let Test.16 : U8 = GetTagId Test.10;
|
||||
let Test.17 : Int1 = lowlevel Eq Test.15 Test.16;
|
||||
if Test.17 then
|
||||
let Test.8 : U64 = UnionAtIndex (Id 1) (Index 0) Test.10;
|
||||
jump Test.11 Test.8;
|
||||
else
|
||||
let Test.7 : {} = UnionAtIndex (Id 0) (Index 0) Test.13;
|
||||
let Test.17 : [C {}, C U64] = TagId(0) Test.7;
|
||||
ret Test.17;
|
||||
let Test.9 : {} = UnionAtIndex (Id 0) (Index 0) Test.10;
|
||||
let Test.14 : [C {}, C U64] = TagId(0) Test.9;
|
||||
ret Test.14;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.11 : Str = "123";
|
||||
let Test.10 : [C {}, C U64] = CallByName Test.3 Test.11;
|
||||
dec Test.11;
|
||||
ret Test.10;
|
||||
let Test.6 : Str = "123";
|
||||
let Test.5 : [C {}, C U64] = CallByName Test.1 Test.6;
|
||||
dec Test.6;
|
||||
ret Test.5;
|
||||
|
|
|
@ -3483,6 +3483,7 @@ pub enum Reason {
|
|||
CrashArg,
|
||||
ImportParams(ModuleId),
|
||||
FunctionOutput,
|
||||
TryResult,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
|
@ -3532,10 +3533,21 @@ pub enum Category {
|
|||
|
||||
Expect,
|
||||
Dbg,
|
||||
Return,
|
||||
|
||||
TryTarget,
|
||||
TrySuccess,
|
||||
TryFailure,
|
||||
|
||||
Return(EarlyReturnKind),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EarlyReturnKind {
|
||||
Return,
|
||||
Try,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PatternCategory {
|
||||
Record,
|
||||
|
|
|
@ -699,6 +699,7 @@ impl IterTokens for Loc<Expr<'_>> {
|
|||
.chain(e2.iter_tokens(arena))
|
||||
.collect_in(arena),
|
||||
Expr::Try => onetoken(Token::Keyword, region, arena),
|
||||
Expr::LowLevelTry(e1) => e1.iter_tokens(arena),
|
||||
Expr::Apply(e1, e2, _called_via) => (e1.iter_tokens(arena).into_iter())
|
||||
.chain(e2.iter_tokens(arena))
|
||||
.collect_in(arena),
|
||||
|
|
|
@ -33,7 +33,7 @@ impl Problems {
|
|||
const YELLOW: &str = ANSI_STYLE_CODES.yellow;
|
||||
const RESET: &str = ANSI_STYLE_CODES.reset;
|
||||
|
||||
println!(
|
||||
print!(
|
||||
"{}{}{} {} and {}{}{} {} found in {} ms",
|
||||
match self.errors {
|
||||
0 => GREEN,
|
||||
|
|
|
@ -9,7 +9,7 @@ use roc_problem::can::{
|
|||
};
|
||||
use roc_problem::Severity;
|
||||
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_types::types::{AliasKind, EarlyReturnKind};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::r#type::suggest;
|
||||
|
@ -1350,18 +1350,25 @@ pub fn can_problem<'b>(
|
|||
title = report.title;
|
||||
}
|
||||
|
||||
Problem::ReturnOutsideOfFunction { region } => {
|
||||
Problem::ReturnOutsideOfFunction {
|
||||
region,
|
||||
return_kind,
|
||||
} => {
|
||||
let return_keyword;
|
||||
(title, return_keyword) = match return_kind {
|
||||
EarlyReturnKind::Return => ("RETURN OUTSIDE OF FUNCTION".to_string(), "return"),
|
||||
EarlyReturnKind::Try => ("TRY OUTSIDE OF FUNCTION".to_string(), "try"),
|
||||
};
|
||||
|
||||
doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" statement doesn't belong to a function:"),
|
||||
alloc.keyword(return_keyword),
|
||||
alloc.reflow(" 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 } => {
|
||||
|
|
|
@ -1005,7 +1005,7 @@ fn to_unfinished_lambda_report<'a>(
|
|||
let doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow(r"I was partway through parsing a "),
|
||||
alloc.reflow(r" function, but I got stuck here:"),
|
||||
alloc.reflow(r"function, but I got stuck here:"),
|
||||
]),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
message,
|
||||
|
|
|
@ -21,8 +21,8 @@ use roc_solve_problem::{
|
|||
use roc_std::RocDec;
|
||||
use roc_types::pretty_print::{Parens, WILDCARD};
|
||||
use roc_types::types::{
|
||||
AbilitySet, AliasKind, Category, ErrorType, IndexOrField, PatternCategory, Polarity, Reason,
|
||||
RecordField, TypeExt,
|
||||
AbilitySet, AliasKind, Category, EarlyReturnKind, ErrorType, IndexOrField, PatternCategory,
|
||||
Polarity, Reason, RecordField, TypeExt,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::{text, DocAllocator};
|
||||
|
@ -472,6 +472,37 @@ pub fn type_problem<'b>(
|
|||
severity,
|
||||
})
|
||||
}
|
||||
InvalidTryTarget(region, actual_type) => {
|
||||
let stack = [
|
||||
alloc.concat([
|
||||
alloc.reflow("This expression cannot be used as a "),
|
||||
alloc.keyword("try"),
|
||||
alloc.reflow(" target:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.concat([
|
||||
alloc.reflow("I expected a "),
|
||||
alloc.type_str("Result"),
|
||||
alloc.reflow(", but it actually has type:"),
|
||||
]),
|
||||
alloc.type_block(error_type_to_doc(alloc, actual_type)),
|
||||
alloc.concat([
|
||||
alloc.hint(""),
|
||||
alloc.reflow("Did you forget to wrap the value with an "),
|
||||
alloc.tag("Ok".into()),
|
||||
alloc.reflow(" or an "),
|
||||
alloc.tag("Err".into()),
|
||||
alloc.reflow(" tag?"),
|
||||
]),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
title: "INVALID TRY TARGET".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1237,8 +1268,8 @@ fn to_expr_report<'b>(
|
|||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
expr_region,
|
||||
Some(region),
|
||||
alloc.concat([
|
||||
alloc.reflow("The "),
|
||||
alloc.string(index.ordinal()),
|
||||
|
@ -1526,8 +1557,8 @@ fn to_expr_report<'b>(
|
|||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
expr_region,
|
||||
Some(region),
|
||||
alloc.concat([
|
||||
alloc.string(format!("This {argument} to ")),
|
||||
this_function.clone(),
|
||||
|
@ -1806,11 +1837,8 @@ fn to_expr_report<'b>(
|
|||
|
||||
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:",
|
||||
),
|
||||
alloc.reflow("This returns something that's incompatible "),
|
||||
alloc.reflow("with the return type of the enclosing function:"),
|
||||
]);
|
||||
|
||||
let comparison = type_comparison(
|
||||
|
@ -1878,6 +1906,45 @@ fn to_expr_report<'b>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
Reason::TryResult => {
|
||||
let problem = alloc.concat([
|
||||
alloc.text("This "),
|
||||
alloc.keyword("try"),
|
||||
alloc.reflow(
|
||||
" statement doesn't match the return type of its enclosing function:",
|
||||
),
|
||||
]);
|
||||
|
||||
let comparison = type_comparison(
|
||||
alloc,
|
||||
found,
|
||||
expected_type,
|
||||
ExpectationContext::Arbitrary,
|
||||
add_category(alloc, alloc.text("It is"), &category),
|
||||
alloc.concat([
|
||||
alloc.reflow("But I need every "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" statement in that function to return:"),
|
||||
]),
|
||||
None,
|
||||
);
|
||||
|
||||
Report {
|
||||
title: "TYPE MISMATCH".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack([
|
||||
problem,
|
||||
alloc.region_with_subregion(
|
||||
lines.convert_region(region),
|
||||
lines.convert_region(expr_region),
|
||||
severity,
|
||||
),
|
||||
comparison,
|
||||
]),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -2216,10 +2283,45 @@ fn format_category<'b>(
|
|||
alloc.concat([this_is, alloc.text(" a dbg statement")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Return => (
|
||||
Return(EarlyReturnKind::Return) => (
|
||||
alloc.concat([text!(alloc, "{}his", t), alloc.reflow(" returns a value")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Return(EarlyReturnKind::Try) => (
|
||||
alloc.concat([
|
||||
text!(alloc, "{}his", t),
|
||||
alloc.reflow(" returns an "),
|
||||
alloc.tag_name("Err".into()),
|
||||
]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
TryTarget => (
|
||||
alloc.concat([
|
||||
this_is,
|
||||
alloc.reflow(" a "),
|
||||
alloc.keyword("try"),
|
||||
alloc.reflow(" target"),
|
||||
]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
TrySuccess => (
|
||||
alloc.concat([
|
||||
this_is,
|
||||
alloc.reflow(" a "),
|
||||
alloc.keyword("try"),
|
||||
alloc.reflow(" expression"),
|
||||
]),
|
||||
alloc.text(" that succeeds with type:"),
|
||||
),
|
||||
TryFailure => (
|
||||
alloc.concat([
|
||||
this_is,
|
||||
alloc.reflow(" a "),
|
||||
alloc.keyword("try"),
|
||||
alloc.reflow(" expression"),
|
||||
]),
|
||||
alloc.text(" that fails with type:"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue