Use new try impl for ? operator

This commit is contained in:
Sam Mohr 2024-12-05 02:13:08 -08:00
parent 193c23bac8
commit de626102c8
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
20 changed files with 429 additions and 110 deletions

View file

@ -5,6 +5,7 @@ use std::sync::Arc;
use crate::abilities::SpecializationId; use crate::abilities::SpecializationId;
use crate::exhaustive::{ExhaustiveContext, SketchedRows}; use crate::exhaustive::{ExhaustiveContext, SketchedRows};
use crate::expected::{Expected, PExpected}; use crate::expected::{Expected, PExpected};
use crate::expr::TryKind;
use roc_collections::soa::{index_push_new, slice_extend_new}; use roc_collections::soa::{index_push_new, slice_extend_new};
use roc_module::ident::{IdentSuffix, TagName}; use roc_module::ident::{IdentSuffix, TagName};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
@ -643,12 +644,14 @@ impl Constraints {
ok_payload_var: Variable, ok_payload_var: Variable,
err_payload_var: Variable, err_payload_var: Variable,
region: Region, region: Region,
kind: TryKind,
) -> Constraint { ) -> Constraint {
let constraint = TryTargetConstraint { let constraint = TryTargetConstraint {
target_type_index: result_type_index, target_type_index: result_type_index,
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
region, region,
kind,
}; };
let constraint_index = index_push_new(&mut self.try_target_constraints, constraint); let constraint_index = index_push_new(&mut self.try_target_constraints, constraint);
@ -939,6 +942,7 @@ pub struct TryTargetConstraint {
pub ok_payload_var: Variable, pub ok_payload_var: Variable,
pub err_payload_var: Variable, pub err_payload_var: Variable,
pub region: Region, pub region: Region,
pub kind: TryKind,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View file

@ -725,6 +725,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
err_ext_var, err_ext_var,
kind,
} => Try { } => Try {
result_expr: Box::new(result_expr.map(|e| go_help!(e))), result_expr: Box::new(result_expr.map(|e| go_help!(e))),
result_var: sub!(*result_var), result_var: sub!(*result_var),
@ -732,6 +733,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
ok_payload_var: sub!(*ok_payload_var), ok_payload_var: sub!(*ok_payload_var),
err_payload_var: sub!(*err_payload_var), err_payload_var: sub!(*err_payload_var),
err_ext_var: sub!(*err_ext_var), err_ext_var: sub!(*err_ext_var),
kind: *kind,
}, },
RuntimeError(err) => RuntimeError(err.clone()), RuntimeError(err) => RuntimeError(err.clone()),

View file

@ -11,8 +11,8 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{ use roc_parse::ast::{
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, StrLiteral, is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, ResultTryKind,
StrSegment, TypeAnnotation, ValueDef, WhenBranch, StrLiteral, StrSegment, TryTarget, TypeAnnotation, ValueDef, WhenBranch,
}; };
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
@ -44,7 +44,10 @@ fn new_op_call_expr<'a>(
match right_without_spaces { match right_without_spaces {
Try => { Try => {
let desugared_left = desugar_expr(env, scope, left); let desugared_left = desugar_expr(env, scope, left);
return Loc::at(region, Expr::LowLevelTry(desugared_left)); return Loc::at(
region,
Expr::LowLevelTry(desugared_left, ResultTryKind::KeywordPrefix),
);
} }
Apply(&Loc { value: Try, .. }, arguments, _called_via) => { Apply(&Loc { value: Try, .. }, arguments, _called_via) => {
let try_fn = desugar_expr(env, scope, arguments.first().unwrap()); let try_fn = desugar_expr(env, scope, arguments.first().unwrap());
@ -60,10 +63,71 @@ fn new_op_call_expr<'a>(
return Loc::at( return Loc::at(
region, region,
Expr::LowLevelTry(env.arena.alloc(Loc::at( Expr::LowLevelTry(
env.arena.alloc(Loc::at(
region, region,
Expr::Apply(try_fn, args.into_bump_slice(), CalledVia::Try), Expr::Apply(try_fn, args.into_bump_slice(), CalledVia::Try),
))), )),
ResultTryKind::KeywordPrefix,
),
);
}
TrySuffix {
target: TryTarget::Result,
expr: fn_expr,
} => {
let loc_fn = env.arena.alloc(Loc::at(right.region, **fn_expr));
let function = desugar_expr(env, scope, loc_fn);
return Loc::at(
region,
LowLevelTry(
env.arena.alloc(Loc::at(
region,
Expr::Apply(
function,
env.arena.alloc([desugar_expr(env, scope, left)]),
CalledVia::Try,
),
)),
ResultTryKind::OperatorSuffix,
),
);
}
Apply(
&Loc {
value:
TrySuffix {
target: TryTarget::Result,
expr: fn_expr,
},
region: fn_region,
},
loc_args,
_called_via,
) => {
let loc_fn = env.arena.alloc(Loc::at(fn_region, *fn_expr));
let function = desugar_expr(env, scope, loc_fn);
let mut desugared_args = Vec::with_capacity_in(loc_args.len() + 1, env.arena);
desugared_args.push(desugar_expr(env, scope, left));
for loc_arg in &loc_args[..] {
desugared_args.push(desugar_expr(env, scope, loc_arg));
}
return Loc::at(
region,
LowLevelTry(
env.arena.alloc(Loc::at(
region,
Expr::Apply(
function,
desugared_args.into_bump_slice(),
CalledVia::Try,
),
)),
ResultTryKind::OperatorSuffix,
),
); );
} }
_ => {} _ => {}
@ -456,16 +520,23 @@ pub fn desugar_expr<'a>(
env.arena.alloc(Loc { region, value }) env.arena.alloc(Loc { region, value })
} }
// desugar the sub_expression, but leave the TrySuffix as this will
// be unwrapped later in desugar_value_def_suffixed
TrySuffix { TrySuffix {
expr: sub_expr, expr: sub_expr,
target, target,
} => { } => {
let intermediate = env.arena.alloc(Loc::at(loc_expr.region, **sub_expr)); let intermediate = env.arena.alloc(Loc::at(loc_expr.region, **sub_expr));
let new_sub_loc_expr = desugar_expr(env, scope, intermediate); let new_sub_loc_expr = desugar_expr(env, scope, intermediate);
match target {
TryTarget::Result => env.arena.alloc(Loc::at(
loc_expr.region,
LowLevelTry(new_sub_loc_expr, ResultTryKind::OperatorSuffix),
)),
TryTarget::Task => {
let new_sub_expr = env.arena.alloc(new_sub_loc_expr.value); let new_sub_expr = env.arena.alloc(new_sub_loc_expr.value);
// desugar the sub_expression, but leave the TrySuffix as this will
// be unwrapped later in desugar_value_def_suffixed
env.arena.alloc(Loc::at( env.arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
TrySuffix { TrySuffix {
@ -474,6 +545,8 @@ pub fn desugar_expr<'a>(
}, },
)) ))
} }
}
}
RecordAccess(sub_expr, paths) => { RecordAccess(sub_expr, paths) => {
let region = loc_expr.region; let region = loc_expr.region;
let loc_sub_expr = Loc { let loc_sub_expr = Loc {
@ -965,8 +1038,43 @@ pub fn desugar_expr<'a>(
)) ))
}; };
env.arena env.arena.alloc(Loc::at(
.alloc(Loc::at(loc_expr.region, Expr::LowLevelTry(result_expr))) loc_expr.region,
Expr::LowLevelTry(result_expr, ResultTryKind::KeywordPrefix),
))
}
Apply(
Loc {
value:
TrySuffix {
target: TryTarget::Result,
expr: fn_expr,
},
region: fn_region,
},
loc_args,
_called_via,
) => {
let loc_fn = env.arena.alloc(Loc::at(*fn_region, **fn_expr));
let function = desugar_expr(env, scope, loc_fn);
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
for loc_arg in &loc_args[..] {
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);
let result_expr = env.arena.alloc(Loc::at(
args_region,
Expr::Apply(function, desugared_args.into_bump_slice(), CalledVia::Try),
));
env.arena.alloc(Loc::at(
loc_expr.region,
Expr::LowLevelTry(result_expr, ResultTryKind::OperatorSuffix),
))
} }
Apply(loc_fn, loc_args, called_via) => { Apply(loc_fn, loc_args, called_via) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena); let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
@ -1138,7 +1246,7 @@ pub fn desugar_expr<'a>(
} }
// note these only exist after desugaring // note these only exist after desugaring
LowLevelDbg(_, _, _) | LowLevelTry(_) => loc_expr, LowLevelDbg(_, _, _) | LowLevelTry(_, _) => loc_expr,
} }
} }

View file

@ -19,7 +19,7 @@ use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentId, ModuleId, Symbol}; use roc_module::symbol::{IdentId, ModuleId, Symbol};
use roc_parse::ast::{self, Defs, PrecedenceConflict, StrLiteral}; use roc_parse::ast::{self, Defs, PrecedenceConflict, ResultTryKind, StrLiteral};
use roc_parse::ident::Accessor; use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*; use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
@ -337,6 +337,7 @@ pub enum Expr {
ok_payload_var: Variable, ok_payload_var: Variable,
err_payload_var: Variable, err_payload_var: Variable,
err_ext_var: Variable, err_ext_var: Variable,
kind: TryKind,
}, },
Return { Return {
@ -348,6 +349,12 @@ pub enum Expr {
RuntimeError(RuntimeError), RuntimeError(RuntimeError),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TryKind {
KeywordPrefix,
OperatorSuffix,
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct ExpectLookup { pub struct ExpectLookup {
pub symbol: Symbol, pub symbol: Symbol,
@ -1397,7 +1404,7 @@ pub fn canonicalize_expr<'a>(
output, output,
) )
} }
ast::Expr::LowLevelTry(loc_expr) => { ast::Expr::LowLevelTry(loc_expr, kind) => {
let (loc_result_expr, output) = let (loc_result_expr, output) =
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
@ -1415,6 +1422,10 @@ pub fn canonicalize_expr<'a>(
ok_payload_var: var_store.fresh(), ok_payload_var: var_store.fresh(),
err_payload_var: var_store.fresh(), err_payload_var: var_store.fresh(),
err_ext_var: var_store.fresh(), err_ext_var: var_store.fresh(),
kind: match kind {
ResultTryKind::KeywordPrefix => TryKind::KeywordPrefix,
ResultTryKind::OperatorSuffix => TryKind::OperatorSuffix,
},
}, },
output, output,
) )
@ -2394,6 +2405,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
err_ext_var, err_ext_var,
kind,
} => { } => {
let loc_result_expr = Loc { let loc_result_expr = Loc {
region: result_expr.region, region: result_expr.region,
@ -2407,6 +2419,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
err_ext_var, err_ext_var,
kind,
} }
} }
@ -2704,7 +2717,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::MalformedIdent(_, _) | ast::Expr::MalformedIdent(_, _)
| ast::Expr::Tag(_) | ast::Expr::Tag(_)
| ast::Expr::OpaqueRef(_) => true, | ast::Expr::OpaqueRef(_) => true,
ast::Expr::LowLevelTry(loc_expr) => is_valid_interpolation(&loc_expr.value), ast::Expr::LowLevelTry(loc_expr, _) => is_valid_interpolation(&loc_expr.value),
// Newlines are disallowed inside interpolation, and these all require newlines // Newlines are disallowed inside interpolation, and these all require newlines
ast::Expr::DbgStmt { .. } ast::Expr::DbgStmt { .. }
| ast::Expr::LowLevelDbg(_, _, _) | ast::Expr::LowLevelDbg(_, _, _)

View file

@ -407,6 +407,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
ok_payload_var: _, ok_payload_var: _,
err_payload_var: _, err_payload_var: _,
err_ext_var: _, err_ext_var: _,
kind: _,
} => { } => {
visitor.visit_expr(&result_expr.value, result_expr.region, *result_var); visitor.visit_expr(&result_expr.value, result_expr.region, *result_var);
} }

View file

@ -143,60 +143,63 @@ Defs {
ident: "i", ident: "i",
}, },
], ],
@132-172 Apply( @113-189 Defs(
@132-172 Var { Defs {
module_name: "Result", tags: [
ident: "try", EitherIndex(2147483648),
},
[
@132-152 Apply(
@132-152 Var {
module_name: "",
ident: "inc",
},
[
@132-133 Var {
module_name: "",
ident: "i",
},
], ],
BinOp( regions: [
Pizza, @132-172,
),
),
@132-172 Closure(
[
@132-152 Identifier {
ident: "#!0_arg",
},
], ],
@132-172 Apply( space_before: [
@132-172 Var { Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
module_name: "Result",
ident: "try",
},
[
@132-172 Apply(
@132-172 Var {
module_name: "",
ident: "inc",
},
[
@132-152 Var {
module_name: "",
ident: "#!0_arg",
},
], ],
BinOp( space_after: [
Pizza, Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
), ],
), spaces: [],
@132-172 Closure( type_defs: [],
[ value_defs: [
Body(
@113-117 Identifier { @113-117 Identifier {
ident: "newi", ident: "newi",
}, },
@132-172 LowLevelTry(
@132-172 Apply(
@169-172 Var {
module_name: "",
ident: "inc",
},
[
@132-152 LowLevelTry(
@132-152 Apply(
@149-152 Var {
module_name: "",
ident: "inc",
},
[
@132-133 SpaceAfter(
Var {
module_name: "",
ident: "i",
},
[
Newline,
], ],
),
],
Try,
),
OperatorSuffix,
),
],
Try,
),
OperatorSuffix,
),
),
],
},
@182-189 Apply( @182-189 Apply(
@182-184 Tag( @182-184 Tag(
"Ok", "Ok",
@ -210,13 +213,6 @@ Defs {
Space, Space,
), ),
), ),
],
QuestionSuffix,
),
),
],
QuestionSuffix,
),
), ),
), ),
Body( Body(

View file

@ -802,6 +802,104 @@ mod test_can {
} }
} }
#[test]
fn question_suffix_simple() {
let src = indoc!(
r#"
(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:
//
// Try(Str.toU64 "123")
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");
}
#[test]
fn question_suffix_after_function() {
let src = indoc!(
r#"
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:
//
// Try(Str.toU64 "123")
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");
}
#[test]
fn question_suffix_pipe() {
let src = indoc!(
r#"
"123" |> 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:
//
// Try(Str.toU64 "123")
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");
}
#[test]
fn question_suffix_pipe_nested() {
let src = indoc!(
r#"
"123" |> Str.toU64? (Ok 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:
//
// Try(Str.toU64 "123" Try(Ok 123))
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);
let ok_tag = assert_try_expr(&cond_args[0].1.value);
let tag_args = assert_tag_application(ok_tag, "Ok");
assert_eq!(tag_args.len(), 1);
assert_num_value(&tag_args[0].1.value, 123);
}
#[test] #[test]
fn try_desugar_plain_prefix() { fn try_desugar_plain_prefix() {
let src = indoc!( let src = indoc!(
@ -973,6 +1071,16 @@ mod test_can {
} }
} }
fn assert_tag_application(expr: &Expr, tag_name: &str) -> Vec<(Variable, Loc<Expr>)> {
match expr {
Expr::LetNonRec(_, loc_expr) => assert_tag_application(&loc_expr.value, tag_name),
Expr::Tag {
name, arguments, ..
} if *name == tag_name.into() => arguments.clone(),
_ => panic!("Expr was not a Tag named {tag_name:?}: {expr:?}",),
}
}
fn assert_pattern_name(pattern: &Pattern, name: &str, interns: &roc_module::symbol::Interns) { fn assert_pattern_name(pattern: &Pattern, name: &str, interns: &roc_module::symbol::Interns) {
match pattern { match pattern {
Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), name), Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), name),

View file

@ -870,6 +870,7 @@ pub fn constrain_expr(
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
err_ext_var, err_ext_var,
kind,
} => { } => {
let result_var_index = constraints.push_variable(*result_var); let result_var_index = constraints.push_variable(*result_var);
let result_expected_type = constraints.push_expected_type(ForReason( let result_expected_type = constraints.push_expected_type(ForReason(
@ -891,6 +892,7 @@ pub fn constrain_expr(
*ok_payload_var, *ok_payload_var,
*err_payload_var, *err_payload_var,
result_expr.region, result_expr.region,
*kind,
); );
let return_type_index = constraints.push_variable(*return_var); let return_type_index = constraints.push_variable(*return_var);

View file

@ -201,7 +201,7 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent); buf.indent(indent);
buf.push_str("try"); buf.push_str("try");
} }
LowLevelTry(_) => unreachable!( LowLevelTry(_, _) => unreachable!(
"LowLevelTry should only exist after desugaring, not during formatting" "LowLevelTry should only exist after desugaring, not during formatting"
), ),
Return(return_value, after_return) => { Return(return_value, after_return) => {
@ -370,7 +370,7 @@ pub fn expr_is_multiline(me: &Expr<'_>, comments_only: bool) -> bool {
| Expr::Crash | Expr::Crash
| Expr::Dbg | Expr::Dbg
| Expr::Try => false, | Expr::Try => false,
Expr::LowLevelTry(_) => { Expr::LowLevelTry(_, _) => {
unreachable!("LowLevelTry should only exist after desugaring, not during formatting") unreachable!("LowLevelTry should only exist after desugaring, not during formatting")
} }

View file

@ -14998,11 +14998,12 @@ All branches in an `if` must have the same type!
); );
test_report!( test_report!(
try_with_non_result_target, keyword_try_with_non_result_target,
indoc!( indoc!(
r#" r#"
invalidTry = \{} -> invalidTry = \{} ->
x = try 64 nonResult = "abc"
x = try nonResult
Ok (x * 2) Ok (x * 2)
@ -15014,12 +15015,41 @@ All branches in an `if` must have the same type!
This expression cannot be used as a `try` target: This expression cannot be used as a `try` target:
5 x = try 64 6 x = try nonResult
^^ ^^^^^^^^^
I expected a Result, but it actually has type: I expected a Result, but it actually has type:
Num * Str
Hint: Did you forget to wrap the value with an `Ok` or an `Err` tag?
"
);
test_report!(
question_try_with_non_result_target,
indoc!(
r#"
invalidTry = \{} ->
nonResult = "abc"
x = nonResult?
Ok (x * 2)
invalidTry {}
"#
),
@r"
INVALID TRY TARGET in /code/proj/Main.roc
This expression cannot be tried with the `?` operator:
6 x = nonResult?
^^^^^^^^^^
I expected a Result, but it actually has type:
Str
Hint: Did you forget to wrap the value with an `Ok` or an `Err` tag? Hint: Did you forget to wrap the value with an `Ok` or an `Err` tag?
" "
@ -15062,7 +15092,7 @@ All branches in an `if` must have the same type!
); );
test_report!( test_report!(
try_prefix_in_pipe, keyword_try_prefix_in_pipe,
indoc!( indoc!(
r#" r#"
readFile : Str -> Str readFile : Str -> Str
@ -15097,7 +15127,7 @@ All branches in an `if` must have the same type!
); );
test_report!( test_report!(
try_suffix_in_pipe, keyword_try_suffix_in_pipe,
indoc!( indoc!(
r#" r#"
readFile : Str -> Str readFile : Str -> Str
@ -15132,6 +15162,41 @@ All branches in an `if` must have the same type!
" "
); );
test_report!(
question_try_in_pipe,
indoc!(
r#"
readFile : Str -> Str
getFileContents : Str -> Result Str _
getFileContents = \filePath ->
contents =
readFile filePath
|> 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 |> Result.mapErr? ErrWrapper
This `readFile` call produces:
Str
But this function needs its 1st argument to be:
Result ok a
"
);
test_report!( test_report!(
leftover_statement, leftover_statement,
indoc!( indoc!(

View file

@ -380,6 +380,7 @@ impl<'a> LowerParams<'a> {
ok_payload_var: _, ok_payload_var: _,
err_payload_var: _, err_payload_var: _,
err_ext_var: _, err_ext_var: _,
kind: _,
} => { } => {
expr_stack.push(&mut result_expr.value); expr_stack.push(&mut result_expr.value);
} }

View file

@ -105,7 +105,7 @@ pub fn remove_module_param_arguments(
| TypeError::ExpectedEffectful(_, _) | TypeError::ExpectedEffectful(_, _)
| TypeError::UnsuffixedEffectfulFunction(_, _) | TypeError::UnsuffixedEffectfulFunction(_, _)
| TypeError::SuffixedPureFunction(_, _) | TypeError::SuffixedPureFunction(_, _)
| TypeError::InvalidTryTarget(_, _) => {} | TypeError::InvalidTryTarget(_, _, _) => {}
} }
} }
} }

View file

@ -5878,6 +5878,7 @@ pub fn with_hole<'a>(
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
err_ext_var, err_ext_var,
kind: _,
} => { } => {
let ok_symbol = env.unique_symbol(); let ok_symbol = env.unique_symbol();
let err_symbol = env.unique_symbol(); let err_symbol = env.unique_symbol();

View file

@ -415,6 +415,12 @@ pub enum TryTarget {
Result, Result,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResultTryKind {
KeywordPrefix,
OperatorSuffix,
}
/// A parsed expression. This uses lifetimes extensively for two reasons: /// A parsed expression. This uses lifetimes extensively for two reasons:
/// ///
/// 1. It uses Bump::alloc for all allocations, which returns a reference. /// 1. It uses Bump::alloc for all allocations, which returns a reference.
@ -514,7 +520,7 @@ pub enum Expr<'a> {
/// The `try` keyword that performs early return on errors /// The `try` keyword that performs early return on errors
Try, Try,
// This form of try is a desugared Result unwrapper // This form of try is a desugared Result unwrapper
LowLevelTry(&'a Loc<Expr<'a>>), LowLevelTry(&'a Loc<Expr<'a>>, ResultTryKind),
// This form of debug is a desugared call to roc_dbg // 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>>), LowLevelDbg(&'a (&'a str, &'a str), &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
@ -702,7 +708,7 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
} }
Expr::LowLevelDbg(_, 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::Try => false,
Expr::LowLevelTry(loc_expr) => is_expr_suffixed(&loc_expr.value), Expr::LowLevelTry(loc_expr, _) => is_expr_suffixed(&loc_expr.value),
Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value), Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value),
Expr::When(cond, branches) => { Expr::When(cond, branches) => {
is_expr_suffixed(&cond.value) || branches.iter().any(|x| is_when_branch_suffixed(x)) is_expr_suffixed(&cond.value) || branches.iter().any(|x| is_when_branch_suffixed(x))
@ -976,7 +982,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&condition.value); expr_stack.push(&condition.value);
expr_stack.push(&cont.value); expr_stack.push(&cont.value);
} }
LowLevelTry(loc_expr) => { LowLevelTry(loc_expr, _) => {
expr_stack.push(&loc_expr.value); expr_stack.push(&loc_expr.value);
} }
Return(return_value, after_return) => { Return(return_value, after_return) => {
@ -2519,7 +2525,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(), 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(), LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(),
Try => false, Try => false,
LowLevelTry(loc_expr) => loc_expr.is_malformed(), 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()), Return(return_value, after_return) => return_value.is_malformed() || after_return.is_some_and(|ar| ar.is_malformed()),
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(), BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),

View file

@ -2180,7 +2180,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::Dbg | Expr::Dbg
| Expr::DbgStmt { .. } | Expr::DbgStmt { .. }
| Expr::LowLevelDbg(_, _, _) | Expr::LowLevelDbg(_, _, _)
| Expr::LowLevelTry(_) | Expr::LowLevelTry(_, _)
| Expr::Return(_, _) | Expr::Return(_, _)
| Expr::MalformedSuffixed(..) | Expr::MalformedSuffixed(..)
| Expr::PrecedenceConflict { .. } | Expr::PrecedenceConflict { .. }

View file

@ -727,7 +727,7 @@ impl<'a> Normalize<'a> for Expr<'a> {
arena.alloc(b.normalize(arena)), arena.alloc(b.normalize(arena)),
), ),
Expr::Try => Expr::Try, Expr::Try => Expr::Try,
Expr::LowLevelTry(a) => Expr::LowLevelTry(arena.alloc(a.normalize(arena))), Expr::LowLevelTry(a, kind) => Expr::LowLevelTry(arena.alloc(a.normalize(arena)), kind),
Expr::Return(a, b) => Expr::Return( Expr::Return(a, b) => Expr::Return(
arena.alloc(a.normalize(arena)), arena.alloc(a.normalize(arena)),
b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))), b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))),

View file

@ -917,6 +917,7 @@ fn solve(
ok_payload_var, ok_payload_var,
err_payload_var, err_payload_var,
region, region,
kind,
} = try_target_constraint; } = try_target_constraint;
let target_actual = either_type_index_to_var( let target_actual = either_type_index_to_var(
@ -990,7 +991,7 @@ fn solve(
Failure(vars, actual_type, _expected_type, _bad_impls) => { Failure(vars, actual_type, _expected_type, _bad_impls) => {
env.introduce(rank, &vars); env.introduce(rank, &vars);
let problem = TypeError::InvalidTryTarget(*region, actual_type); let problem = TypeError::InvalidTryTarget(*region, actual_type, *kind);
problems.push(problem); problems.push(problem);

View file

@ -2,6 +2,7 @@
use std::{path::PathBuf, str::Utf8Error}; use std::{path::PathBuf, str::Utf8Error};
use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind}; use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind};
use roc_can::expr::TryKind;
use roc_can::{ use roc_can::{
constraint::FxCallKind, constraint::FxCallKind,
expected::{Expected, PExpected}, expected::{Expected, PExpected},
@ -48,7 +49,7 @@ pub enum TypeError {
ExpectedEffectful(Region, ExpectEffectfulReason), ExpectedEffectful(Region, ExpectEffectfulReason),
UnsuffixedEffectfulFunction(Region, FxSuffixKind), UnsuffixedEffectfulFunction(Region, FxSuffixKind),
SuffixedPureFunction(Region, FxSuffixKind), SuffixedPureFunction(Region, FxSuffixKind),
InvalidTryTarget(Region, ErrorType), InvalidTryTarget(Region, ErrorType, TryKind),
} }
impl TypeError { impl TypeError {
@ -78,7 +79,7 @@ impl TypeError {
TypeError::FxInTopLevel(_, _) => Warning, TypeError::FxInTopLevel(_, _) => Warning,
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning, TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
TypeError::SuffixedPureFunction(_, _) => Warning, TypeError::SuffixedPureFunction(_, _) => Warning,
TypeError::InvalidTryTarget(_, _) => RuntimeError, TypeError::InvalidTryTarget(_, _, _) => RuntimeError,
} }
} }
@ -100,7 +101,7 @@ impl TypeError {
| TypeError::ExpectedEffectful(region, _) | TypeError::ExpectedEffectful(region, _)
| TypeError::UnsuffixedEffectfulFunction(region, _) | TypeError::UnsuffixedEffectfulFunction(region, _)
| TypeError::SuffixedPureFunction(region, _) | TypeError::SuffixedPureFunction(region, _)
| TypeError::InvalidTryTarget(region, _) => Some(*region), | TypeError::InvalidTryTarget(region, _, _) => Some(*region),
TypeError::UnfulfilledAbility(ab, ..) => ab.region(), TypeError::UnfulfilledAbility(ab, ..) => ab.region(),
TypeError::Exhaustive(e) => Some(e.region()), TypeError::Exhaustive(e) => Some(e.region()),
TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region), TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region),

View file

@ -699,7 +699,7 @@ impl IterTokens for Loc<Expr<'_>> {
.chain(e2.iter_tokens(arena)) .chain(e2.iter_tokens(arena))
.collect_in(arena), .collect_in(arena),
Expr::Try => onetoken(Token::Keyword, region, arena), Expr::Try => onetoken(Token::Keyword, region, arena),
Expr::LowLevelTry(e1) => e1.iter_tokens(arena), Expr::LowLevelTry(e1, _) => e1.iter_tokens(arena),
Expr::Apply(e1, e2, _called_via) => (e1.iter_tokens(arena).into_iter()) Expr::Apply(e1, e2, _called_via) => (e1.iter_tokens(arena).into_iter())
.chain(e2.iter_tokens(arena)) .chain(e2.iter_tokens(arena))
.collect_in(arena), .collect_in(arena),

View file

@ -6,6 +6,7 @@ use itertools::EitherOrBoth;
use itertools::Itertools; use itertools::Itertools;
use roc_can::constraint::{ExpectEffectfulReason, FxCallKind, FxSuffixKind}; use roc_can::constraint::{ExpectEffectfulReason, FxCallKind, FxSuffixKind};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_can::expr::TryKind;
use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_collections::all::{HumanIndex, MutSet, SendMap};
use roc_collections::VecMap; use roc_collections::VecMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
@ -472,13 +473,22 @@ pub fn type_problem<'b>(
severity, severity,
}) })
} }
InvalidTryTarget(region, actual_type) => { InvalidTryTarget(region, actual_type, try_kind) => {
let stack = [ let invalid_usage_message = match try_kind {
alloc.concat([ TryKind::KeywordPrefix => alloc.concat([
alloc.reflow("This expression cannot be used as a "), alloc.reflow("This expression cannot be used as a "),
alloc.keyword("try"), alloc.keyword("try"),
alloc.reflow(" target:"), alloc.reflow(" target:"),
]), ]),
TryKind::OperatorSuffix => alloc.concat([
alloc.reflow("This expression cannot be tried with the "),
alloc.keyword("?"),
alloc.reflow(" operator:"),
]),
};
let stack = [
invalid_usage_message,
alloc.region(lines.convert_region(region), severity), alloc.region(lines.convert_region(region), severity),
alloc.concat([ alloc.concat([
alloc.reflow("I expected a "), alloc.reflow("I expected a "),