Merge branch 'main' into inline-imports

This commit is contained in:
Agus Zubiaga 2024-04-20 12:01:11 -03:00
commit d5a38a26db
No known key found for this signature in database
667 changed files with 22300 additions and 14562 deletions

View file

@ -785,9 +785,10 @@ fn can_annotation_help(
for loc_var in *loc_vars {
let var = match loc_var.value {
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
name
}
Pattern::Identifier {
ident: name,
suffixed: _,
} if name.chars().next().unwrap().is_lowercase() => name,
_ => unreachable!("I thought this was validated during parsing"),
};
let var_name = Lowercase::from(var);

View file

@ -33,7 +33,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32 => Some(lowlevel_1(Symbol::NUM_TO_U32, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U64 => Some(lowlevel_1(Symbol::NUM_TO_U64, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_U128 => Some(lowlevel_1(Symbol::NUM_TO_U128, LowLevel::NumIntCast, var_store)),
Symbol::NUM_TO_NAT => Some(lowlevel_1(Symbol::NUM_TO_NAT, LowLevel::NumIntCast, var_store)),
Symbol::NUM_INT_CAST => Some(lowlevel_1(Symbol::NUM_INT_CAST, LowLevel::NumIntCast, var_store)),
@ -50,7 +49,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
Symbol::NUM_TO_U32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U32_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U64_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_U128_CHECKED => Some(to_num_checked(Symbol::NUM_TO_U128_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_NAT_CHECKED => Some(to_num_checked(Symbol::NUM_TO_NAT_CHECKED, var_store, LowLevel::NumToIntChecked)),
Symbol::NUM_TO_F32_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F32_CHECKED, var_store, LowLevel::NumToFloatChecked)),
Symbol::NUM_TO_F64_CHECKED => Some(to_num_checked(Symbol::NUM_TO_F64_CHECKED, var_store, LowLevel::NumToFloatChecked)),
@ -118,7 +116,7 @@ map_symbol_to_lowlevel_and_arity! {
StrEndsWith; STR_ENDS_WITH; 2,
StrSplit; STR_SPLIT; 2,
StrCountUtf8Bytes; STR_COUNT_UTF8_BYTES; 1,
StrFromUtf8Range; STR_FROM_UTF8_RANGE_LOWLEVEL; 3,
StrFromUtf8; STR_FROM_UTF8_LOWLEVEL; 1,
StrToUtf8; STR_TO_UTF8; 1,
StrRepeat; STR_REPEAT; 2,
StrTrim; STR_TRIM; 1,
@ -128,11 +126,11 @@ map_symbol_to_lowlevel_and_arity! {
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,
StrReserve; STR_RESERVE; 2,
StrToNum; STR_TO_NUM; 1,
StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1,
StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListLenUsize; LIST_LEN_USIZE; 1,
ListLenU64; LIST_LEN_U64; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
ListReserve; LIST_RESERVE; 2,
ListIsUnique; LIST_IS_UNIQUE; 1,
@ -173,9 +171,9 @@ map_symbol_to_lowlevel_and_arity! {
NumLte; NUM_LTE; 2,
NumCompare; NUM_COMPARE; 2,
NumDivFrac; NUM_DIV_FRAC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC; 2,
NumDivTruncUnchecked; NUM_DIV_TRUNC_UNCHECKED; 2,
NumDivCeilUnchecked; NUM_DIV_CEIL; 2,
NumRemUnchecked; NUM_REM; 2,
NumRemUnchecked; NUM_REM_UNCHECKED; 2,
NumIsMultipleOf; NUM_IS_MULTIPLE_OF; 2,
NumAbs; NUM_ABS; 1,
NumNeg; NUM_NEG; 1,
@ -196,10 +194,6 @@ map_symbol_to_lowlevel_and_arity! {
NumAtan; NUM_ATAN; 1,
NumAcos; NUM_ACOS; 1,
NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2,
@ -210,7 +204,12 @@ map_symbol_to_lowlevel_and_arity! {
NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
I128OfDec; I128_OF_DEC; 1,
NumWithoutDecimalPoint; NUM_WITHOUT_DECIMAL_POINT; 1,
NumWithDecimalPoint; NUM_WITH_DECIMAL_POINT; 1,
NumF32ToParts; NUM_F32_TO_PARTS; 1,
NumF64ToParts; NUM_F64_TO_PARTS; 1,
NumF32FromParts; NUM_F32_FROM_PARTS; 1,
NumF64FromParts; NUM_F64_FROM_PARTS; 1,
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
@ -226,7 +225,7 @@ map_symbol_to_lowlevel_and_arity! {
/// Some builtins cannot be constructed in code gen alone, and need to be defined
/// as separate Roc defs. For example, List.get has this type:
///
/// List.get : List elem, Nat -> Result elem [OutOfBounds]*
/// List.get : List elem, U64 -> Result elem [OutOfBounds]*
///
/// Because this returns an open tag union for its Err type, it's not possible
/// for code gen to return a hardcoded value for OutOfBounds. For example,

View file

@ -549,7 +549,11 @@ fn canonicalize_claimed_ability_impl<'a>(
}
AssignedField::RequiredValue(label, _spaces, value) => {
let impl_ident = match value.value {
ast::Expr::Var { module_name, ident } => {
ast::Expr::Var {
module_name,
ident,
suffixed: _,
} => {
if module_name.is_empty() {
ident
} else {
@ -2644,9 +2648,10 @@ fn to_pending_alias_or_opaque<'a>(
for loc_var in vars.iter() {
match loc_var.value {
ast::Pattern::Identifier(name)
if name.chars().next().unwrap().is_lowercase() =>
{
ast::Pattern::Identifier {
ident: name,
suffixed: _,
} if name.chars().next().unwrap().is_lowercase() => {
let lowercase = Lowercase::from(name);
can_rigids.push(Loc {
value: lowercase,
@ -3069,7 +3074,13 @@ fn to_pending_value_def<'a>(
let typed_ident = ingested_file.name.item.extract_spaces().item;
let body_pattern = env
.arena
.alloc(typed_ident.ident.map_owned(ast::Pattern::Identifier));
.alloc(typed_ident.ident.map_owned(|ident|
ast::Pattern::Identifier {
ident,
// Agus TODO: check this
suffixed: 0
}
));
let ann_type = env.arena.alloc(typed_ident.ann);
let body_expr = Loc {
value: ast::Expr::IngestedFile(env.arena.alloc(file_path), ann_type),
@ -3095,6 +3106,7 @@ fn to_pending_value_def<'a>(
env.arena.alloc(body_expr),
))
}
Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
}
}

View file

@ -23,9 +23,13 @@ fn to_encoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
let opaque_apply_pattern = ast::Pattern::Apply(
opaque_ref,
&*env
.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
&*env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: payload,
suffixed: 0,
},
)]),
);
// Encode.toEncoder payload
@ -33,10 +37,12 @@ fn to_encoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Encode",
ident: "toEncoder",
suffixed: 0,
}),
&*env.arena.alloc([&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: payload,
suffixed: 0,
})]),
roc_module::called_via::CalledVia::Space,
));
@ -61,19 +67,23 @@ fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "decodeWith",
suffixed: 0,
}),
env.arena.alloc([
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: bytes,
suffixed: 0,
}),
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "decoder",
suffixed: 0,
}),
alloc_expr(ast::Expr::Var {
module_name: "",
ident: fmt,
suffixed: 0,
}),
]),
CalledVia::Space,
@ -84,6 +94,7 @@ fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "mapResult",
suffixed: 0,
}),
env.arena.alloc([
&*alloc_expr(call_decode_with),
@ -96,8 +107,20 @@ fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
// Decode.mapResult (Decode.decodeWith bytes Decode.decoder fmt) @Opaq
let custom_closure = ast::Expr::Closure(
env.arena.alloc([
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(bytes)),
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(fmt)),
Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: bytes,
suffixed: 0,
},
),
Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: fmt,
suffixed: 0,
},
),
]),
alloc_expr(call_map_result),
);
@ -107,6 +130,7 @@ fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "custom",
suffixed: 0,
}),
env.arena.alloc([&*alloc_expr(custom_closure)]),
CalledVia::Space,
@ -127,9 +151,13 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
let opaque_apply_pattern = ast::Pattern::Apply(
opaque_ref,
&*env
.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
&*env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: payload,
suffixed: 0,
},
)]),
);
// Hash.hash hasher payload
@ -137,15 +165,18 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Hash",
ident: "hash",
suffixed: 0,
}),
&*env.arena.alloc([
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: hasher,
suffixed: 0,
}),
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: payload,
suffixed: 0,
}),
]),
roc_module::called_via::CalledVia::Space,
@ -154,7 +185,13 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
// \hasher, @Opaq payload -> Hash.hash hasher payload
ast::Expr::Closure(
env.arena.alloc([
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(hasher)),
Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: hasher,
suffixed: 0,
},
),
Loc::at(DERIVED_REGION, opaque_apply_pattern),
]),
call_member,
@ -172,16 +209,24 @@ fn is_eq<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
// \@Opaq payload1
let opaque1 = ast::Pattern::Apply(
opaque_ref,
&*env
.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload1))]),
&*env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: payload1,
suffixed: 0,
},
)]),
);
// \@Opaq payload2
let opaque2 = ast::Pattern::Apply(
opaque_ref,
&*env
.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload2))]),
&*env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: payload2,
suffixed: 0,
},
)]),
);
// Bool.isEq payload1 payload2
@ -189,15 +234,18 @@ fn is_eq<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Bool",
ident: "isEq",
suffixed: 0,
}),
&*env.arena.alloc([
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: payload1,
suffixed: 0,
}),
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: payload2,
suffixed: 0,
}),
]),
roc_module::called_via::CalledVia::Space,
@ -224,9 +272,13 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
let opaque_apply_pattern = ast::Pattern::Apply(
opaque_ref,
&*env
.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
&*env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: payload,
suffixed: 0,
},
)]),
);
// Inspect.toInspector payload
@ -234,10 +286,12 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Inspect",
ident: "toInspector",
suffixed: 0,
}),
&*env.arena.alloc([&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: payload,
suffixed: 0,
})]),
roc_module::called_via::CalledVia::Space,
));
@ -252,6 +306,7 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Inspect",
ident: "tag",
suffixed: 0,
}),
&*env.arena.alloc([&*opaque_name, &*to_inspector_list]),
roc_module::called_via::CalledVia::Space,
@ -264,20 +319,27 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Inspect",
ident: "apply",
suffixed: 0,
}),
&*env.arena.alloc([
&*opaque_inspector,
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: fmt,
suffixed: 0,
}),
]),
roc_module::called_via::CalledVia::Space,
));
let custom_closure = alloc_expr(ast::Expr::Closure(
env.arena
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(fmt))]),
env.arena.alloc([Loc::at(
DERIVED_REGION,
ast::Pattern::Identifier {
ident: fmt,
suffixed: 0,
},
)]),
apply_opaque_inspector,
));
@ -286,6 +348,7 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
alloc_expr(ast::Expr::Var {
module_name: "Inspect",
ident: "custom",
suffixed: 0,
}),
env.arena.alloc([&*custom_closure]),
CalledVia::Space,

View file

@ -1,5 +1,6 @@
#![allow(clippy::manual_map)]
use crate::suffixed::{apply_task_await, unwrap_suffixed_expression, EUnwrapped};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::internal_error;
@ -8,8 +9,8 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral, StrSegment, ValueDef,
WhenBranch,
wrap_in_task_ok, AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral,
StrSegment, ValueDef, WhenBranch,
};
use roc_region::all::{LineInfo, Loc, Region};
@ -56,7 +57,11 @@ fn new_op_call_expr<'a>(
let args = arena.alloc([left, right]);
let loc_expr = arena.alloc(Loc {
value: Expr::Var { module_name, ident },
value: Expr::Var {
module_name,
ident,
suffixed: 0,
},
region: loc_op.region,
});
@ -92,9 +97,10 @@ fn desugar_value_def<'a>(
ann_pattern,
ann_type,
comment: *comment,
body_pattern,
body_pattern: desugar_loc_pattern(arena, body_pattern, src, line_info, module_path),
body_expr: desugar_expr(arena, body_expr, src, line_info, module_path),
},
Dbg {
condition,
preceding_comment,
@ -135,19 +141,137 @@ fn desugar_value_def<'a>(
exposed: _,
}) => *def,
IngestedFileImport(_) => *def,
Stmt(stmt_expr) => {
// desugar into a Body({}, stmt_expr)
let loc_pattern = arena.alloc(Loc::at(
stmt_expr.region,
Pattern::RecordDestructure(Collection::empty()),
));
Body(
loc_pattern,
desugar_expr(arena, stmt_expr, src, line_info, module_path),
)
}
}
}
pub fn desugar_defs<'a>(
pub fn desugar_defs_node_values<'a>(
arena: &'a Bump,
defs: &mut roc_parse::ast::Defs<'a>,
src: &'a str,
line_info: &mut Option<LineInfo>,
module_path: &str,
top_level_def: bool,
) {
for value_def in defs.value_defs.iter_mut() {
*value_def = desugar_value_def(arena, arena.alloc(*value_def), src, line_info, module_path);
}
// `desugar_defs_node_values` is called recursively in `desugar_expr` and we
// only we only want to unwrap suffixed nodes if they are a top level def.
//
// check here first so we only unwrap the expressions once, and after they have
// been desugared
if top_level_def {
for value_def in defs.value_defs.iter_mut() {
*value_def = desugar_value_def_suffixed(arena, *value_def);
}
}
}
/// For each top-level ValueDef in our module, we will unwrap any suffixed
/// expressions
///
/// e.g. `say! "hi"` desugars to `Task.await (say "hi") -> \{} -> ...`
pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>) -> ValueDef<'a> {
use ValueDef::*;
match value_def {
Body(loc_pattern, loc_expr) => {
// note called_from_def is passed as `false` as this is a top_level_def
match unwrap_suffixed_expression(arena, loc_expr, None) {
Ok(new_expr) => Body(loc_pattern, new_expr),
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => desugar_value_def_suffixed(
arena,
Body(
loc_pattern,
apply_task_await(
arena,
loc_expr.region,
sub_arg,
sub_pat,
wrap_in_task_ok(arena, sub_new),
),
),
),
Err(..) => Body(
loc_pattern,
arena.alloc(Loc::at(loc_expr.region, MalformedSuffixed(loc_expr))),
),
}
}
ann @ Annotation(_, _) => ann,
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
} => {
// note called_from_def is passed as `false` as this is a top_level_def
match unwrap_suffixed_expression(arena, body_expr, None) {
Ok(new_expr) => AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr: new_expr,
},
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => desugar_value_def_suffixed(
arena,
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr: apply_task_await(
arena,
body_expr.region,
sub_arg,
sub_pat,
wrap_in_task_ok(arena, sub_new),
),
},
),
Err(..) => AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr: arena.alloc(Loc::at(body_expr.region, MalformedSuffixed(body_expr))),
},
}
}
// TODO support desugaring of Dbg, Expect, and ExpectFx
Dbg { .. } | Expect { .. } | ExpectFx { .. } => value_def,
ModuleImport { .. } | IngestedFileImport(_) => value_def,
Stmt(..) => {
internal_error!(
"this should have been desugared into a Body(..) before this call in desugar_expr"
)
}
}
}
/// Reorder the expression tree based on operator precedence and associativity rules,
@ -169,6 +293,7 @@ pub fn desugar_expr<'a>(
| Underscore { .. }
| MalformedIdent(_, _)
| MalformedClosure
| MalformedSuffixed(..)
| PrecedenceConflict { .. }
| MultipleRecordBuilders { .. }
| UnappliedRecordBuilder { .. }
@ -370,8 +495,7 @@ pub fn desugar_expr<'a>(
),
Defs(defs, loc_ret) => {
let mut defs = (*defs).clone();
desugar_defs(arena, &mut defs, src, line_info, module_path);
desugar_defs_node_values(arena, &mut defs, src, line_info, module_path, false);
let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path);
arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret)))
@ -485,10 +609,12 @@ pub fn desugar_expr<'a>(
Negate => Var {
module_name: ModuleName::NUM,
ident: "neg",
suffixed: 0,
},
Not => Var {
module_name: ModuleName::BOOL,
ident: "not",
suffixed: 0,
},
};
let loc_fn_var = arena.alloc(Loc { region, value });
@ -586,6 +712,7 @@ pub fn desugar_expr<'a>(
let inspect_fn = Var {
module_name: ModuleName::INSPECT,
ident: "toStr",
suffixed: 0,
};
let loc_inspect_fn_var = arena.alloc(Loc {
value: inspect_fn,
@ -601,7 +728,7 @@ pub fn desugar_expr<'a>(
// line_info is an option so that we can lazily calculate it.
// That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time.
if matches!(line_info, None) {
if line_info.is_none() {
*line_info = Some(LineInfo::new(src));
}
let line_col = line_info.as_ref().unwrap().convert_pos(region.start());
@ -625,7 +752,32 @@ pub fn desugar_expr<'a>(
region: loc_expr.region,
})
}
LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"),
// Replace an empty final def with a `Task.ok {}`
EmptyDefsFinal => {
let mut apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
apply_args
.push(arena.alloc(Loc::at(loc_expr.region, Expr::Record(Collection::empty()))));
arena.alloc(Loc::at(
loc_expr.region,
Expr::Apply(
arena.alloc(Loc::at(
loc_expr.region,
Expr::Var {
module_name: ModuleName::TASK,
ident: "ok",
suffixed: 0,
},
)),
arena.alloc(apply_args),
CalledVia::BangSuffix,
),
))
}
// note this only exists after desugaring
LowLevelDbg(_, _, _) => loc_expr,
}
}
@ -711,6 +863,7 @@ fn desugar_field<'a>(
value: Var {
module_name: "",
ident: loc_str.value,
suffixed: 0,
},
region: loc_str.region,
};
@ -771,7 +924,7 @@ fn desugar_pattern<'a>(
use roc_parse::ast::Pattern::*;
match pattern {
Identifier(_)
Identifier { .. }
| Tag(_)
| OpaqueRef(_)
| NumLiteral(_)
@ -891,6 +1044,7 @@ fn record_builder_arg<'a>(
value: Expr::Var {
module_name: "",
ident: arena.alloc("#".to_owned() + label.value),
suffixed: 0,
},
});
@ -930,7 +1084,10 @@ fn record_builder_arg<'a>(
for label in apply_field_names.iter().rev() {
let name = arena.alloc("#".to_owned() + label.value);
let ident = roc_parse::ast::Pattern::Identifier(name);
let ident = roc_parse::ast::Pattern::Identifier {
ident: name,
suffixed: 0,
};
let arg_pattern = arena.alloc(Loc {
value: ident,

View file

@ -624,6 +624,9 @@ pub fn canonicalize_expr<'a>(
use Expr::*;
let (expr, output) = match expr {
&ast::Expr::EmptyDefsFinal => {
internal_error!("EmptyDefsFinal should have been desugared")
}
&ast::Expr::Num(str) => {
let answer = num_expr_from_result(var_store, finish_parsing_num(str), region, env);
@ -1051,9 +1054,11 @@ pub fn canonicalize_expr<'a>(
(expr, output)
}
}
ast::Expr::Var { module_name, ident } => {
canonicalize_var_lookup(env, var_store, scope, module_name, ident, region)
}
ast::Expr::Var {
module_name,
ident,
suffixed: _, // TODO should we use suffixed here?
} => canonicalize_var_lookup(env, var_store, scope, module_name, ident, region),
ast::Expr::Underscore(name) => {
// we parse underscores, but they are not valid expression syntax
@ -1457,6 +1462,10 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::MalformedSuffixed(..) => {
use roc_problem::can::RuntimeError::*;
(RuntimeError(MalformedSuffixed(region)), Output::default())
}
ast::Expr::MultipleRecordBuilders(sub_expr) => {
use roc_problem::can::RuntimeError::*;
@ -1685,7 +1694,8 @@ fn canonicalize_closure_body<'a>(
enum MultiPatternVariables {
OnePattern,
MultiPattern {
bound_occurrences: VecMap<Symbol, (Region, u8)>,
num_patterns: usize,
bound_occurrences: VecMap<Symbol, (Region, usize)>,
},
}
@ -1694,7 +1704,8 @@ impl MultiPatternVariables {
fn new(num_patterns: usize) -> Self {
if num_patterns > 1 {
Self::MultiPattern {
bound_occurrences: VecMap::with_capacity(2),
num_patterns,
bound_occurrences: VecMap::with_capacity(num_patterns),
}
} else {
Self::OnePattern
@ -1705,7 +1716,9 @@ impl MultiPatternVariables {
fn add_pattern(&mut self, pattern: &Loc<Pattern>) {
match self {
MultiPatternVariables::OnePattern => {}
MultiPatternVariables::MultiPattern { bound_occurrences } => {
MultiPatternVariables::MultiPattern {
bound_occurrences, ..
} => {
for (sym, region) in BindingsFromPattern::new(pattern) {
if !bound_occurrences.contains_key(&sym) {
bound_occurrences.insert(sym, (region, 0));
@ -1718,15 +1731,18 @@ impl MultiPatternVariables {
#[inline(always)]
fn get_unbound(self) -> impl Iterator<Item = (Symbol, Region)> {
let bound_occurrences = match self {
MultiPatternVariables::OnePattern => Default::default(),
MultiPatternVariables::MultiPattern { bound_occurrences } => bound_occurrences,
let (bound_occurrences, num_patterns) = match self {
MultiPatternVariables::OnePattern => (Default::default(), 1),
MultiPatternVariables::MultiPattern {
bound_occurrences,
num_patterns,
} => (bound_occurrences, num_patterns),
};
bound_occurrences
.into_iter()
.filter_map(|(sym, (region, occurs))| {
if occurs == 1 {
.filter_map(move |(sym, (region, occurs))| {
if occurs != num_patterns {
Some((sym, region))
} else {
None
@ -2524,7 +2540,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::Expr::IngestedFile(_, _)
| ast::Expr::SpaceBefore(_, _)
| ast::Expr::Str(StrLiteral::Block(_))
| ast::Expr::SpaceAfter(_, _) => false,
| ast::Expr::SpaceAfter(_, _)
| ast::Expr::EmptyDefsFinal => false,
// These can contain subexpressions, so we need to recursively check those
ast::Expr::Str(StrLiteral::Line(segments)) => {
segments.iter().all(|segment| match segment {
@ -2550,6 +2567,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
.iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MultipleRecordBuilders(loc_expr)
| ast::Expr::MalformedSuffixed(loc_expr)
| ast::Expr::UnappliedRecordBuilder(loc_expr)
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
| ast::Expr::UnaryOp(loc_expr, _)
@ -2712,10 +2730,38 @@ fn flatten_str_lines<'a>(
fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
use StrSegment::*;
let n = segments.len();
let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
Some(Interpolation(loc_expr)) => loc_expr,
Some(Interpolation(loc_expr)) => {
if n == 1 {
// We concat with the empty string to ensure a type error when loc_expr is not a string
let empty_string = Loc::at(Region::zero(), Expr::Str("".into()));
let fn_expr = Loc::at(
Region::zero(),
Expr::Var(Symbol::STR_CONCAT, var_store.fresh()),
);
let expr = Expr::Call(
Box::new((
var_store.fresh(),
fn_expr,
var_store.fresh(),
var_store.fresh(),
)),
vec![
(var_store.fresh(), empty_string),
(var_store.fresh(), loc_expr),
],
CalledVia::StringInterpolation,
);
Loc::at(Region::zero(), expr)
} else {
loc_expr
}
}
None => {
// No segments? Empty string!

View file

@ -13,6 +13,7 @@ pub mod constraint;
pub mod copy;
pub mod def;
mod derive;
pub mod desugar;
pub mod effect_module;
pub mod env;
pub mod exhaustive;
@ -20,11 +21,11 @@ pub mod expected;
pub mod expr;
pub mod module;
pub mod num;
pub mod operator;
pub mod pattern;
pub mod procedure;
pub mod scope;
pub mod string;
pub mod suffixed;
pub mod traverse;
pub use derive::DERIVED_REGION;

View file

@ -310,7 +310,8 @@ pub fn canonicalize_module_defs<'a>(
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
crate::operator::desugar_defs(arena, loc_defs, src, &mut None, module_path);
crate::desugar::desugar_defs_node_values(arena, loc_defs, src, &mut None, module_path, true);
let mut rigid_variables = RigidVariables::default();
@ -1041,14 +1042,13 @@ fn fix_values_captured_in_closure_expr(
debug_assert!(!captures.is_empty());
captured_symbols.extend(captures);
captured_symbols.swap_remove(i);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
added_captures = true;
} else {
i += 1;
}
// Always jump one, because the current element either does not have captures or
// is now one of the newly-added captures, which we don't need to check.
i += 1;
}
if added_captures {
// Re-sort, since we've added new captures.

View file

@ -198,7 +198,6 @@ fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
"i32", ParsedWidth::Int(IntLitWidth::I32)
"i64", ParsedWidth::Int(IntLitWidth::I64)
"i128", ParsedWidth::Int(IntLitWidth::I128)
"nat", ParsedWidth::Int(IntLitWidth::Nat)
"dec", ParsedWidth::Float(FloatWidth::Dec)
"f32", ParsedWidth::Float(FloatWidth::F32)
"f64", ParsedWidth::Float(FloatWidth::F64)

View file

@ -266,7 +266,10 @@ pub fn canonicalize_def_header_pattern<'a>(
match pattern {
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
Identifier(name) => {
Identifier {
ident: name,
suffixed: _,
} => {
match scope.introduce_or_shadow_ability_member(
pending_abilities_in_scope,
(*name).into(),
@ -376,12 +379,13 @@ pub fn canonicalize_pattern<'a>(
use PatternType::*;
let can_pattern = match pattern {
Identifier(name) => {
match canonicalize_pattern_symbol(env, scope, output, region, permit_shadows, name) {
Ok(symbol) => Pattern::Identifier(symbol),
Err(pattern) => pattern,
}
}
Identifier {
ident: name,
suffixed: _,
} => match canonicalize_pattern_symbol(env, scope, output, region, permit_shadows, name) {
Ok(symbol) => Pattern::Identifier(symbol),
Err(pattern) => pattern,
},
Underscore(name) => {
// An underscored identifier can't be used, but we'll still add it to the scope
// for better error messages if someone tries to use it.
@ -637,7 +641,10 @@ pub fn canonicalize_pattern<'a>(
for loc_pattern in patterns.iter() {
match loc_pattern.value {
Identifier(label) => {
Identifier {
ident: label,
suffixed: _,
} => {
match scope.introduce(label.into(), region) {
Ok(symbol) => {
output.references.insert_bound(symbol);

View file

@ -45,7 +45,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
// )? {
// let expr = Expr::Var(ident);
// // +2 for `\(` and then another +1 for `)` at the end
// // +2 for `$(` and then another +1 for `)` at the end
// let parsed_length = buf.len() + 2 + ident.len() + 1;
// // Casting should always succeed in this section, because

View file

@ -0,0 +1,881 @@
#![allow(clippy::manual_map)]
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_module::called_via::CalledVia;
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{is_loc_expr_suffixed, wrap_in_task_ok, Pattern, ValueDef, WhenBranch};
use roc_region::all::{Loc, Region};
use std::cell::Cell;
thread_local! {
// we use a thread_local here so that tests consistently give the same pattern
static SUFFIXED_ANSWER_COUNTER: Cell<usize> = Cell::new(0);
}
/// Provide an intermediate answer expression and pattern when unwrapping a
/// (sub) expression
///
/// e.g. `x = foo (bar!)` unwraps to `x = Task.await (bar) \#!a0 -> foo #!a0`
fn next_suffixed_answer_pattern(arena: &Bump) -> (Expr, Pattern) {
// Use the thread-local counter
SUFFIXED_ANSWER_COUNTER.with(|counter| {
let count = counter.get();
counter.set(count + 1);
let answer_ident = arena.alloc(format!("#!a{}", count));
(
Expr::Var {
module_name: "",
ident: answer_ident,
suffixed: 0,
},
Pattern::Identifier {
ident: answer_ident.as_str(),
suffixed: 0,
},
)
})
}
#[derive(Debug)]
pub enum EUnwrapped<'a> {
UnwrappedDefExpr(&'a Loc<Expr<'a>>),
UnwrappedSubExpr {
/// the unwrapped expression argument for Task.await
sub_arg: &'a Loc<Expr<'a>>,
/// the pattern for the closure
sub_pat: &'a Loc<Pattern<'a>>,
/// the expression to replace the unwrapped
sub_new: &'a Loc<Expr<'a>>,
},
Malformed,
}
fn init_unwrapped_err<'a>(
arena: &'a Bump,
unwrapped_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match maybe_def_pat {
Some(..) => {
// we have a def pattern, so no need to generate a new pattern
// as this should only be created in the first call from a def
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr))
}
None => {
let (answer_var, answer_pat) = next_suffixed_answer_pattern(arena);
let sub_new = arena.alloc(Loc::at(unwrapped_expr.region, answer_var));
let sub_pat = arena.alloc(Loc::at(unwrapped_expr.region, answer_pat));
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg: unwrapped_expr,
sub_pat,
sub_new,
})
}
}
}
/// Descend through the AST and unwrap each suffixed expression
/// when an expression is unwrapped, we apply a `Task.await` and
/// then descend through the AST again until there are no more suffixed
/// expressions, or we hit an error
pub fn unwrap_suffixed_expression<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
let unwrapped_expression = {
match loc_expr.value {
Expr::Var {
module_name,
ident,
suffixed,
} => {
match suffixed {
0 => Ok(loc_expr),
1 => {
let unwrapped_var = arena.alloc(Loc::at(
loc_expr.region,
Expr::Var {
module_name,
ident,
suffixed: suffixed.saturating_sub(1),
},
));
init_unwrapped_err(arena, unwrapped_var, maybe_def_pat)
}
_ => {
let unwrapped_var = arena.alloc(Loc::at(
loc_expr.region,
Expr::Var {
module_name,
ident,
suffixed: 0,
},
));
// we generate an intermediate pattern `#!a0` etc
// so we dont unwrap the definition pattern
let (mut answer_var, answer_pat) = next_suffixed_answer_pattern(arena);
// we transfer the suffix from the Var to the intermediate answer Var
// as that will need to be unwrapped in a future call
if let Expr::Var {
module_name: "",
ident: answer_ident,
suffixed: 0,
} = answer_var
{
answer_var = Expr::Var {
module_name: "",
ident: answer_ident,
suffixed: suffixed.saturating_sub(1),
}
} else {
internal_error!("expected a suffixed Var to be generated");
}
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg: unwrapped_var,
sub_pat: arena.alloc(Loc::at(unwrapped_var.region, answer_pat)),
sub_new: arena.alloc(Loc::at(unwrapped_var.region, answer_var)),
})
}
}
}
Expr::Defs(..) => unwrap_suffixed_expression_defs_help(arena, loc_expr, maybe_def_pat),
Expr::Apply(..) => {
unwrap_suffixed_expression_apply_help(arena, loc_expr, maybe_def_pat)
}
Expr::When(..) => unwrap_suffixed_expression_when_help(arena, loc_expr, maybe_def_pat),
Expr::If(..) => {
unwrap_suffixed_expression_if_then_else_help(arena, loc_expr, maybe_def_pat)
}
Expr::Closure(..) => {
unwrap_suffixed_expression_closure_help(arena, loc_expr, maybe_def_pat)
}
Expr::ParensAround(..) => {
unwrap_suffixed_expression_parens_help(arena, loc_expr, maybe_def_pat)
}
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) => {
internal_error!(
"SpaceBefore and SpaceAfter should have been removed in desugar_expr"
)
}
Expr::BinOps(..) => {
internal_error!("BinOps should have been desugared in desugar_expr")
}
// we only need to unwrap some expressions, leave the rest as is
_ => Ok(loc_expr),
}
};
// KEEP THIS HERE FOR DEBUGGING
// USEFUL TO SEE THE UNWRAPPING
// OF AST NODES AS THEY DESCEND
// if is_loc_expr_suffixed(loc_expr) {
// dbg!(&loc_expr, &unwrapped_expression);
// }
unwrapped_expression
}
pub fn unwrap_suffixed_expression_parens_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
_maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::ParensAround(sub_loc_expr) => {
// note we use `None` here as we always want to generate a new pattern from child expressions
match unwrap_suffixed_expression(arena, arena.alloc(Loc::at_zero(*sub_loc_expr)), None)
{
Ok(new_expr) => {
let new_parens = arena.alloc(Loc::at(
loc_expr.region,
ParensAround(arena.alloc(new_expr.value)),
));
Ok(new_parens)
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
internal_error!("unreachable, child expressions from ParensAround should generate UnwrappedSubExpr instead");
}
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => {
let new_parens = arena.alloc(Loc::at(
loc_expr.region,
ParensAround(arena.alloc(sub_new.value)),
));
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new: new_parens,
})
}
Err(err) => Err(err),
}
}
_ => internal_error!("unreachable, expected a ParensAround node to be passed into unwrap_suffixed_expression_parens_help"),
}
}
pub fn unwrap_suffixed_expression_closure_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
_maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::Closure(closure_args, closure_loc_ret) => {
// Check to make sure that arguments are not suffixed
let suffixed_arg_count = closure_args
.iter()
.filter(|loc_pat| loc_pat.value.is_suffixed())
.count();
if suffixed_arg_count > 0 {
debug_assert!(false,"closure arguments should not be suffixed");
return Err(EUnwrapped::Malformed);
}
// note we use `None` here as we don't want to pass a DefExpr up and
// unwrap the definition pattern for the closure
match unwrap_suffixed_expression(arena, closure_loc_ret, None) {
Ok(unwrapped_expr) => {
let new_closure = arena.alloc(Loc::at(loc_expr.region, Expr::Closure(closure_args, unwrapped_expr)));
Ok(new_closure)
}
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_closure_loc_ret = apply_task_await(arena, loc_expr.region, sub_arg, sub_pat, sub_new);
let new_closure = arena.alloc(Loc::at(loc_expr.region, Expr::Closure(closure_args, new_closure_loc_ret)));
Ok(new_closure)
}
Err(err) => {
debug_assert!(false,"the closure Defs was malformd, got {:#?}", err);
Err(EUnwrapped::Malformed)
}
}
}
_ => internal_error!("unreachable, expected a Closure node to be passed into unwrap_suffixed_expression_closure_help"),
}
}
pub fn unwrap_suffixed_expression_apply_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::Apply(function, apply_args, called_via) => {
// Any suffixed arguments will be innermost, therefore we unwrap those first
let local_args = arena.alloc_slice_copy(apply_args);
for (_, arg) in local_args.iter_mut().enumerate() {
match unwrap_suffixed_expression(arena, arg, maybe_def_pat) {
Ok(new_arg) => {
*arg = new_arg;
}
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_arg)) => {
*arg = unwrapped_arg;
let new_apply = arena.alloc(Loc::at(loc_expr.region, Apply(function, local_args, called_via)));
return Err(EUnwrapped::UnwrappedDefExpr(new_apply));
}
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new: new_arg }) => {
*arg = new_arg;
let new_apply = arena.alloc(Loc::at(loc_expr.region, Apply(function, local_args, called_via)));
return Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new: new_apply});
}
Err(err) => return Err(err),
}
}
// special case for when our Apply function is a suffixed Var (but not multiple suffixed)
if let Expr::Var { module_name, ident, suffixed } = function.value {
if suffixed == 1 {
let unwrapped_function = arena.alloc(Loc::at(
loc_expr.region,
Expr::Var {
module_name,
ident,
suffixed: suffixed - 1,
},
));
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via)));
return init_unwrapped_err(arena, new_apply, maybe_def_pat);
}
}
// function is another expression
match unwrap_suffixed_expression(arena, function, maybe_def_pat) {
Ok(new_function) => {
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(new_function, local_args, called_via)));
Ok(new_apply)
}
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_function)) => {
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via)));
Err(EUnwrapped::UnwrappedDefExpr(new_apply))
}
Err(EUnwrapped::UnwrappedSubExpr { sub_arg: unwrapped_function, sub_pat, sub_new }) => {
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(sub_new, local_args, called_via)));
Err(EUnwrapped::UnwrappedSubExpr { sub_arg: unwrapped_function, sub_pat, sub_new:new_apply})
}
Err(err) => Err(err)
}
}
_ => internal_error!("unreachable, expected an Apply node to be passed into unwrap_suffixed_expression_apply_help"),
}
}
/// Unwrap if-then-else statements
pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::If(if_thens, final_else_branch) => {
for (index, if_then) in if_thens.iter().enumerate() {
let (current_if_then_statement, current_if_then_expression) = if_then;
// unwrap suffixed (innermost) expressions e.g. `if true then doThing! then ...`
if is_loc_expr_suffixed(current_if_then_expression) {
// split if_thens around the current index
let (before, after) = roc_parse::ast::split_around(if_thens, index);
match unwrap_suffixed_expression(arena, current_if_then_expression, None) {
Ok(unwrapped_expression) => {
let mut new_if_thens = Vec::new_in(arena);
new_if_thens.extend(before);
new_if_thens.push((*current_if_then_statement, *unwrapped_expression));
new_if_thens.extend(after);
let new_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(
arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch,
),
));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
}
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => {
let unwrapped_expression = apply_task_await(
arena,
sub_arg.region,
sub_arg,
sub_pat,
wrap_in_task_ok(arena, sub_new),
);
let mut new_if_thens = Vec::new_in(arena);
new_if_thens.extend(before);
new_if_thens.push((*current_if_then_statement, *unwrapped_expression));
new_if_thens.extend(after);
let new_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(
arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch,
),
));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
}
Err(EUnwrapped::Malformed) => return Err(EUnwrapped::Malformed),
}
}
// unwrap suffixed statements e.g. `if isThing! then ...`
// note we want to split and nest if-then's so we only run Task's
// that are required
if is_loc_expr_suffixed(current_if_then_statement) {
// split if_thens around the current index
let (before, after) = roc_parse::ast::split_around(if_thens, index);
match unwrap_suffixed_expression(arena, current_if_then_statement, None) {
Ok(unwrapped_statement) => {
let mut new_if_thens = Vec::new_in(arena);
new_if_thens.push((*unwrapped_statement, *current_if_then_expression));
new_if_thens.extend(after);
let new_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(
arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch,
),
));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
}
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => {
if before.is_empty() {
let mut new_if_thens = Vec::new_in(arena);
new_if_thens.extend(before);
new_if_thens.push((*sub_new, *current_if_then_expression));
new_if_thens.extend(after);
let new_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(
arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch,
),
));
let unwrapped_if_then = apply_task_await(
arena,
sub_arg.region,
sub_arg,
sub_pat,
new_if,
);
return unwrap_suffixed_expression(
arena,
unwrapped_if_then,
maybe_def_pat,
);
} else {
let mut after_if_thens = Vec::new_in(arena);
after_if_thens.push((*sub_new, *current_if_then_expression));
after_if_thens.extend(after);
let after_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(
arena.alloc_slice_copy(after_if_thens.as_slice()),
final_else_branch,
),
));
let after_if_then = apply_task_await(
arena,
sub_arg.region,
sub_arg,
sub_pat,
after_if,
);
let before_if_then = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(before, after_if_then),
));
return unwrap_suffixed_expression(
arena,
before_if_then,
maybe_def_pat,
);
}
}
Err(EUnwrapped::Malformed) => return Err(EUnwrapped::Malformed),
}
}
}
// check the final_else_branch
match unwrap_suffixed_expression(arena, final_else_branch, None) {
Ok(unwrapped_final_else) => {
return Ok(arena.alloc(Loc::at(
loc_expr.region,
Expr::If(if_thens, unwrapped_final_else),
)));
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
}
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg,
sub_pat,
sub_new,
}) => {
let unwrapped_final_else = apply_task_await(
arena,
sub_arg.region,
sub_arg,
sub_pat,
wrap_in_task_ok(arena, sub_new),
);
let new_if = arena.alloc(Loc::at(
loc_expr.region,
Expr::If(if_thens, unwrapped_final_else),
));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
}
Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed),
}
}
_ => internal_error!("unreachable, expected an If expression to desugar"),
}
}
pub fn unwrap_suffixed_expression_when_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::When(condition, branches) => {
// first unwrap any when branches values
// e.g.
// when foo is
// [] -> line! "bar"
// _ -> line! "baz"
for (branch_index, WhenBranch{value: branch_loc_expr,patterns, guard}) in branches.iter().enumerate() {
// if the branch isn't suffixed we can leave it alone
if is_loc_expr_suffixed(branch_loc_expr) {
let unwrapped_branch_value = match unwrap_suffixed_expression(arena, branch_loc_expr, None) {
Ok(unwrapped_branch_value) => unwrapped_branch_value,
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => apply_task_await(arena, branch_loc_expr.region, sub_arg, sub_pat, sub_new),
Err(..) => return Err(EUnwrapped::Malformed),
};
let new_branch = WhenBranch{value: *unwrapped_branch_value, patterns, guard: *guard};
let mut new_branches = Vec::new_in(arena);
let (before, rest) = branches.split_at(branch_index);
let after = &rest[1..];
new_branches.extend_from_slice(before);
new_branches.push(arena.alloc(new_branch));
new_branches.extend_from_slice(after);
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(condition, arena.alloc_slice_copy(new_branches.as_slice()))));
return unwrap_suffixed_expression(arena, new_when, maybe_def_pat);
}
}
// then unwrap the when condition
match unwrap_suffixed_expression(arena, condition, None) {
Ok(unwrapped_condition) => {
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(unwrapped_condition, branches)));
Ok(new_when)
}
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(sub_new, branches)));
let applied_task_await = apply_task_await(arena,loc_expr.region,sub_arg,sub_pat,new_when);
Ok(applied_task_await)
}
Err(EUnwrapped::UnwrappedDefExpr(..))
| Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed)
}
}
_ => internal_error!("unreachable, expected a When node to be passed into unwrap_suffixed_expression_defs_help"),
}
}
pub fn unwrap_suffixed_expression_defs_help<'a>(
arena: &'a Bump,
loc_expr: &'a Loc<Expr<'a>>,
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value {
Expr::Defs(defs, loc_ret) => {
let mut local_defs = defs.clone();
let tags = local_defs.tags.clone();
// try an unwrap each def, if none can be unwrapped, then try to unwrap the loc_ret
for (tag_index, type_or_value_def_index) in tags.iter().enumerate() {
use ValueDef::*;
let mut current_value_def = match type_or_value_def_index.split() {
Ok(..) => {
// ignore type definitions
continue;
},
Err(value_index) => *local_defs.value_defs.get(value_index.index()).unwrap(),
};
let maybe_suffixed_value_def = match current_value_def {
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None,
AnnotatedBody { body_pattern, body_expr, .. } => Some((body_pattern, body_expr)),
Body (def_pattern, def_expr, .. ) => Some((def_pattern, def_expr)),
};
match maybe_suffixed_value_def {
None => {
// We can't unwrap this def type, continue
},
Some((def_pattern, def_expr)) => {
match unwrap_suffixed_expression(arena, def_expr, Some(def_pattern)) {
Ok(unwrapped_def) => {
current_value_def.replace_expr(unwrapped_def);
local_defs.replace_with_value_def(tag_index, current_value_def, def_expr.region);
}
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr)) => {
let split_defs = local_defs.split_defs_around(tag_index);
let before_empty = split_defs.before.is_empty();
let after_empty = split_defs.after.is_empty();
if before_empty && after_empty {
// NIL before, NIL after -> SINGLE DEF
let next_expr = match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat) {
Ok(next_expr) => next_expr,
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
// We need to apply Task.ok here as the defs final expression was unwrapped
apply_task_await(arena,def_expr.region,sub_arg,sub_pat,wrap_in_task_ok(arena, sub_new))
}
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
return Err(EUnwrapped::Malformed);
},
};
return unwrap_suffixed_expression(arena, apply_task_await(arena,def_expr.region,unwrapped_expr,def_pattern,next_expr), maybe_def_pat);
} else if before_empty {
// NIL before, SOME after -> FIRST DEF
let new_defs = arena.alloc(Loc::at(def_expr.region, Defs(arena.alloc(split_defs.after), loc_ret)));
let next_expr = match unwrap_suffixed_expression(arena,new_defs,maybe_def_pat){
Ok(next_expr) => next_expr,
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
apply_task_await(arena, def_expr.region, sub_arg, sub_pat, sub_new)
}
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
return Err(EUnwrapped::Malformed);
},
};
return unwrap_suffixed_expression(arena, apply_task_await(arena,def_expr.region,unwrapped_expr,def_pattern,next_expr), maybe_def_pat);
} else if after_empty {
// SOME before, NIL after -> LAST DEF
match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat){
Ok(new_loc_ret) => {
let applied_task_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_task_await)));
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
},
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_loc_ret = apply_task_await(arena,def_expr.region,sub_arg,sub_pat,sub_new);
let applied_task_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_task_await)));
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
// TODO confirm this is correct with test case
return Err(EUnwrapped::Malformed);
}
Err(EUnwrapped::Malformed) => {
return Err(EUnwrapped::Malformed);
},
}
} else {
// SOME before, SOME after -> MIDDLE DEF
let after_defs = arena.alloc(Loc::at(def_expr.region, Defs(arena.alloc(split_defs.after), loc_ret)));
match unwrap_suffixed_expression(arena,after_defs,maybe_def_pat){
Ok(new_loc_ret) => {
let applied_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_await)));
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
},
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_loc_ret = apply_task_await(arena, def_expr.region, sub_arg, sub_pat, sub_new);
let applied_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_await)));
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
}
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
return Err(EUnwrapped::Malformed);
},
};
}
}
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_body_def = ValueDef::Body(def_pattern, sub_new);
local_defs.replace_with_value_def(tag_index,new_body_def, sub_new.region);
let new_defs_expr = arena.alloc(Loc::at(def_expr.region,Defs(arena.alloc(local_defs), loc_ret)));
let replaced_def = apply_task_await(arena,def_expr.region,sub_arg,sub_pat,new_defs_expr);
return unwrap_suffixed_expression(arena,replaced_def,maybe_def_pat);
}
Err(err) => return Err(err)
}
}
}
}
// try to unwrap the loc_ret
match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat){
Ok(new_loc_ret) => {
Ok(arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(local_defs), new_loc_ret))))
},
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
let new_loc_ret = apply_task_await(arena, loc_expr.region,sub_arg,sub_pat,sub_new);
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(local_defs), new_loc_ret)));
unwrap_suffixed_expression(arena, new_defs, maybe_def_pat)
}
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
// TODO confirm this is correct with test case
Err(EUnwrapped::Malformed)
}
Err(EUnwrapped::Malformed) => {
Err(EUnwrapped::Malformed)
},
}
},
_ => internal_error!("unreachable, expected a Defs node to be passed into unwrap_suffixed_expression_defs_help"),
}
}
/// Helper for `Task.await (loc_arg) \loc_pat -> loc_new`
pub fn apply_task_await<'a>(
arena: &'a Bump,
region: Region,
loc_arg: &'a Loc<Expr<'a>>,
loc_pat: &'a Loc<Pattern<'a>>,
loc_new: &'a Loc<Expr<'a>>,
) -> &'a Loc<Expr<'a>> {
// If the pattern and the new are the same then we don't need to unwrap anything
// e.g. `Task.await foo \{} -> Task.ok {}` is the same as `foo`
if is_matching_empty_record(loc_pat, loc_new) {
return loc_arg;
}
// If the pattern and the new are matching answers then we don't need to unwrap anything
// e.g. `Task.await foo \#!a1 -> Task.ok #!a1` is the same as `foo`
if is_matching_intermediate_answer(loc_pat, loc_new) {
return loc_arg;
}
let mut task_await_apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
// apply the unwrapped suffixed expression
task_await_apply_args.push(loc_arg);
// apply the closure
let mut closure_pattern = Vec::new_in(arena);
closure_pattern.push(*loc_pat);
task_await_apply_args.push(arena.alloc(Loc::at(
region,
Closure(arena.alloc_slice_copy(closure_pattern.as_slice()), loc_new),
)));
arena.alloc(Loc::at(
region,
Apply(
arena.alloc(Loc {
region,
value: Var {
module_name: ModuleName::TASK,
ident: "await",
suffixed: 0,
},
}),
arena.alloc(task_await_apply_args),
CalledVia::BangSuffix,
),
))
}
fn extract_wrapped_task_ok_value<'a>(loc_expr: &'a Loc<Expr<'a>>) -> Option<&'a Loc<Expr<'a>>> {
match loc_expr.value {
Expr::Apply(function, arguments, _) => match function.value {
Var {
module_name, ident, ..
} if module_name == ModuleName::TASK && ident == "ok" => arguments.first().copied(),
_ => None,
},
_ => None,
}
}
fn is_matching_empty_record<'a>(
loc_pat: &'a Loc<Pattern<'a>>,
loc_expr: &'a Loc<Expr<'a>>,
) -> bool {
let is_empty_record = match extract_wrapped_task_ok_value(loc_expr) {
Some(task_expr) => match task_expr.value {
Expr::Record(collection) => collection.is_empty(),
_ => false,
},
None => false,
};
let is_pattern_empty_record = match loc_pat.value {
Pattern::RecordDestructure(collection) => collection.is_empty(),
_ => false,
};
is_empty_record && is_pattern_empty_record
}
fn is_matching_intermediate_answer<'a>(
loc_pat: &'a Loc<Pattern<'a>>,
loc_expr: &'a Loc<Expr<'a>>,
) -> bool {
let pat_ident = match loc_pat.value {
Pattern::Identifier { ident, .. } => Some(ident),
_ => None,
};
let exp_ident = match extract_wrapped_task_ok_value(loc_expr) {
Some(task_expr) => match task_expr.value {
Expr::Var {
module_name, ident, ..
} if module_name.is_empty() && ident.starts_with('#') => Some(ident),
_ => None,
},
None => None,
};
match (pat_ident, exp_ident) {
(Some(a), Some(b)) => a == b,
_ => false,
}
}

View file

@ -13,7 +13,7 @@ use crate::{
},
pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
#[derive(Clone)]
pub enum DeclarationInfo<'a> {
Value {
loc_symbol: Loc<Symbol>,
@ -164,7 +164,7 @@ pub fn walk_decls<V: Visitor>(visitor: &mut V, decls: &Declarations) {
}
}
fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: DeclarationInfo<'_>) {
use DeclarationInfo::*;
match decl {