mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge branch 'main' into wrapped
This commit is contained in:
commit
793a95264d
765 changed files with 14005 additions and 2968 deletions
|
@ -74,19 +74,29 @@ comptime {
|
|||
}
|
||||
}
|
||||
|
||||
fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*anyopaque {
|
||||
// We store an extra usize which is the size of the full allocation.
|
||||
const full_size = size + @sizeOf(usize);
|
||||
var raw_ptr = (std.testing.allocator.alloc(u8, full_size) catch unreachable).ptr;
|
||||
@as([*]usize, @alignCast(@ptrCast(raw_ptr)))[0] = full_size;
|
||||
raw_ptr += @sizeOf(usize);
|
||||
const ptr = @as(?*anyopaque, @ptrCast(raw_ptr));
|
||||
fn testing_roc_alloc(size: usize, nominal_alignment: u32) callconv(.C) ?*anyopaque {
|
||||
const real_alignment = 16;
|
||||
if (nominal_alignment > real_alignment) {
|
||||
@panic("alignments larger than that of usize are not currently supported");
|
||||
}
|
||||
// We store an extra usize which is the size of the data plus the size of the size, directly before the data.
|
||||
// We need enough clocks of the alignment size to fit this (usually this will be one)
|
||||
const size_of_size = @sizeOf(usize);
|
||||
const alignments_needed = size_of_size / real_alignment + comptime if (size_of_size % real_alignment == 0) 0 else 1;
|
||||
const extra_bytes = alignments_needed * size_of_size;
|
||||
|
||||
const full_size = size + extra_bytes;
|
||||
const whole_ptr = (std.testing.allocator.alignedAlloc(u8, real_alignment, full_size) catch unreachable).ptr;
|
||||
const written_to_size = size + size_of_size;
|
||||
@as([*]align(real_alignment) usize, @ptrCast(whole_ptr))[extra_bytes - size_of_size] = written_to_size;
|
||||
|
||||
const data_ptr = @as(?*anyopaque, @ptrCast(whole_ptr + extra_bytes));
|
||||
|
||||
if (DEBUG_TESTING_ALLOC and builtin.target.cpu.arch != .wasm32) {
|
||||
std.debug.print("+ alloc {*}: {} bytes\n", .{ ptr, size });
|
||||
std.debug.print("+ alloc {*}: {} bytes\n", .{ data_ptr, size });
|
||||
}
|
||||
|
||||
return ptr;
|
||||
return data_ptr;
|
||||
}
|
||||
|
||||
fn testing_roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*anyopaque {
|
||||
|
@ -107,9 +117,16 @@ fn testing_roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, _: u
|
|||
}
|
||||
|
||||
fn testing_roc_dealloc(c_ptr: *anyopaque, _: u32) callconv(.C) void {
|
||||
const raw_ptr = @as([*]u8, @ptrCast(c_ptr)) - @sizeOf(usize);
|
||||
const full_size = @as([*]usize, @alignCast(@ptrCast(raw_ptr)))[0];
|
||||
const slice = raw_ptr[0..full_size];
|
||||
const alignment = 16;
|
||||
const size_of_size = @sizeOf(usize);
|
||||
const alignments_needed = size_of_size / alignment + comptime if (size_of_size % alignment == 0) 0 else 1;
|
||||
const extra_bytes = alignments_needed * size_of_size;
|
||||
const byte_array = @as([*]u8, @ptrCast(c_ptr)) - extra_bytes;
|
||||
const allocation_ptr = @as([*]align(alignment) u8, @alignCast(byte_array));
|
||||
const offset_from_allocation_to_size = extra_bytes - size_of_size;
|
||||
const size_of_data_and_size = @as([*]usize, @alignCast(@ptrCast(allocation_ptr)))[offset_from_allocation_to_size];
|
||||
const full_size = size_of_data_and_size + offset_from_allocation_to_size;
|
||||
const slice = allocation_ptr[0..full_size];
|
||||
|
||||
if (DEBUG_TESTING_ALLOC and builtin.target.cpu.arch != .wasm32) {
|
||||
std.debug.print("💀 dealloc {*}\n", .{slice.ptr});
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::sync::Arc;
|
|||
use crate::abilities::SpecializationId;
|
||||
use crate::exhaustive::{ExhaustiveContext, SketchedRows};
|
||||
use crate::expected::{Expected, PExpected};
|
||||
use crate::expr::TryKind;
|
||||
use roc_collections::soa::{index_push_new, slice_extend_new};
|
||||
use roc_module::ident::{IdentSuffix, TagName};
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
|
@ -31,6 +32,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 +89,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 +106,6 @@ impl Constraints {
|
|||
Category::List,
|
||||
Category::Str,
|
||||
Category::Character,
|
||||
Category::Return,
|
||||
]);
|
||||
|
||||
pattern_categories.extend([
|
||||
|
@ -138,6 +140,7 @@ impl Constraints {
|
|||
cycles,
|
||||
fx_call_constraints,
|
||||
fx_suffix_constraints,
|
||||
try_target_constraints: result_type_constraints,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,6 +638,27 @@ 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,
|
||||
kind: TryKind,
|
||||
) -> Constraint {
|
||||
let constraint = TryTargetConstraint {
|
||||
target_type_index: result_type_index,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
region,
|
||||
kind,
|
||||
};
|
||||
|
||||
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 +684,7 @@ impl Constraints {
|
|||
| Constraint::Lookup(..)
|
||||
| Constraint::Pattern(..)
|
||||
| Constraint::ExpectEffectful(..)
|
||||
| Constraint::TryTarget(_)
|
||||
| Constraint::FxCall(_)
|
||||
| Constraint::FxSuffix(_)
|
||||
| Constraint::FlexToPure(_)
|
||||
|
@ -843,6 +868,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 +936,15 @@ 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,
|
||||
pub kind: TryKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Cycle {
|
||||
pub def_names: Slice<(Symbol, Region)>,
|
||||
|
@ -1000,6 +1036,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,24 @@ 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,
|
||||
kind,
|
||||
} => 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),
|
||||
kind: *kind,
|
||||
},
|
||||
|
||||
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!(),
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ use roc_module::called_via::{BinOp, CalledVia};
|
|||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{
|
||||
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, StrLiteral,
|
||||
StrSegment, TypeAnnotation, ValueDef, WhenBranch,
|
||||
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, ResultTryKind,
|
||||
StrLiteral, StrSegment, TryTarget, TypeAnnotation, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -44,7 +44,10 @@ 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, ResultTryKind::KeywordPrefix),
|
||||
);
|
||||
}
|
||||
Apply(&Loc { value: Try, .. }, arguments, _called_via) => {
|
||||
let try_fn = desugar_expr(env, scope, arguments.first().unwrap());
|
||||
|
@ -58,13 +61,73 @@ 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,
|
||||
Expr::Apply(try_fn, args.into_bump_slice(), CalledVia::Try),
|
||||
)),
|
||||
return Loc::at(
|
||||
region,
|
||||
Expr::LowLevelTry(
|
||||
env.arena.alloc(Loc::at(
|
||||
region,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
|
@ -207,7 +270,9 @@ fn desugar_value_def<'a>(
|
|||
|
||||
let desugared_expr = desugar_expr(env, scope, stmt_expr);
|
||||
|
||||
if !is_expr_suffixed(&desugared_expr.value) {
|
||||
if !is_expr_suffixed(&desugared_expr.value)
|
||||
&& !matches!(&desugared_expr.value, LowLevelTry(_, _))
|
||||
{
|
||||
env.problems.push(Problem::StmtAfterExpr(stmt_expr.region));
|
||||
|
||||
return ValueDef::StmtAfterExpr;
|
||||
|
@ -457,23 +522,32 @@ pub fn desugar_expr<'a>(
|
|||
|
||||
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 {
|
||||
expr: sub_expr,
|
||||
target,
|
||||
} => {
|
||||
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_expr = env.arena.alloc(new_sub_loc_expr.value);
|
||||
|
||||
env.arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
TrySuffix {
|
||||
expr: new_sub_expr,
|
||||
target: *target,
|
||||
},
|
||||
))
|
||||
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);
|
||||
|
||||
// desugar the sub_expression, but leave the TrySuffix as this will
|
||||
// be unwrapped later in desugar_value_def_suffixed
|
||||
env.arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
TrySuffix {
|
||||
expr: new_sub_expr,
|
||||
target: *target,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
RecordAccess(sub_expr, paths) => {
|
||||
let region = loc_expr.region;
|
||||
|
@ -957,13 +1031,52 @@ 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, 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) => {
|
||||
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
@ -1104,10 +1217,21 @@ pub fn desugar_expr<'a>(
|
|||
// Allow naked dbg, necessary for piping values into dbg with the `Pizza` binop
|
||||
loc_expr
|
||||
}
|
||||
DbgStmt(condition, continuation) => {
|
||||
DbgStmt {
|
||||
first: condition,
|
||||
extra_args,
|
||||
continuation,
|
||||
} => {
|
||||
let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition));
|
||||
let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation));
|
||||
|
||||
if let Some(last) = extra_args.last() {
|
||||
let args_region = Region::span_across(&condition.region, &last.region);
|
||||
env.problem(Problem::OverAppliedDbg {
|
||||
region: args_region,
|
||||
});
|
||||
}
|
||||
|
||||
env.arena.alloc(Loc {
|
||||
value: *desugar_dbg_stmt(env, desugared_condition, desugared_continuation),
|
||||
region: loc_expr.region,
|
||||
|
@ -1123,77 +1247,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,
|
||||
|
|
|
@ -19,14 +19,16 @@ use roc_module::called_via::CalledVia;
|
|||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
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::pattern::PatternType::*;
|
||||
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,16 @@ 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,
|
||||
kind: TryKind,
|
||||
},
|
||||
|
||||
Return {
|
||||
return_value: Box<Loc<Expr>>,
|
||||
return_var: Variable,
|
||||
|
@ -337,6 +349,12 @@ pub enum Expr {
|
|||
RuntimeError(RuntimeError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TryKind {
|
||||
KeywordPrefix,
|
||||
OperatorSuffix,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ExpectLookup {
|
||||
pub symbol: Symbol,
|
||||
|
@ -403,14 +421,115 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_any_early_returns(&self) -> bool {
|
||||
match self {
|
||||
Self::Num { .. }
|
||||
| Self::Int { .. }
|
||||
| Self::Float { .. }
|
||||
| Self::Str { .. }
|
||||
| Self::IngestedFile { .. }
|
||||
| Self::SingleQuote { .. }
|
||||
| Self::Var { .. }
|
||||
| Self::AbilityMember { .. }
|
||||
| Self::ParamsVar { .. }
|
||||
| Self::Closure(..)
|
||||
| Self::EmptyRecord
|
||||
| Self::RecordAccessor(_)
|
||||
| Self::ZeroArgumentTag { .. }
|
||||
| Self::OpaqueWrapFunction(_)
|
||||
| Self::RuntimeError(..) => false,
|
||||
Self::Return { .. } | Self::Try { .. } => true,
|
||||
Self::List { loc_elems, .. } => loc_elems
|
||||
.iter()
|
||||
.any(|elem| elem.value.contains_any_early_returns()),
|
||||
Self::When {
|
||||
loc_cond, branches, ..
|
||||
} => {
|
||||
loc_cond.value.contains_any_early_returns()
|
||||
|| branches.iter().any(|branch| {
|
||||
branch
|
||||
.guard
|
||||
.as_ref()
|
||||
.is_some_and(|guard| guard.value.contains_any_early_returns())
|
||||
|| branch.value.value.contains_any_early_returns()
|
||||
})
|
||||
}
|
||||
Self::If {
|
||||
branches,
|
||||
final_else,
|
||||
..
|
||||
} => {
|
||||
final_else.value.contains_any_early_returns()
|
||||
|| branches.iter().any(|(cond, then)| {
|
||||
cond.value.contains_any_early_returns()
|
||||
|| then.value.contains_any_early_returns()
|
||||
})
|
||||
}
|
||||
Self::LetRec(defs, expr, _cycle_mark) => {
|
||||
expr.value.contains_any_early_returns()
|
||||
|| defs
|
||||
.iter()
|
||||
.any(|def| def.loc_expr.value.contains_any_early_returns())
|
||||
}
|
||||
Self::LetNonRec(def, expr) => {
|
||||
def.loc_expr.value.contains_any_early_returns()
|
||||
|| expr.value.contains_any_early_returns()
|
||||
}
|
||||
Self::Call(_func, args, _called_via) => args
|
||||
.iter()
|
||||
.any(|(_var, arg_expr)| arg_expr.value.contains_any_early_returns()),
|
||||
Self::RunLowLevel { args, .. } | Self::ForeignCall { args, .. } => args
|
||||
.iter()
|
||||
.any(|(_var, arg_expr)| arg_expr.contains_any_early_returns()),
|
||||
Self::Tuple { elems, .. } => elems
|
||||
.iter()
|
||||
.any(|(_var, loc_elem)| loc_elem.value.contains_any_early_returns()),
|
||||
Self::Record { fields, .. } => fields
|
||||
.iter()
|
||||
.any(|(_field_name, field)| field.loc_expr.value.contains_any_early_returns()),
|
||||
Self::RecordAccess { loc_expr, .. } => loc_expr.value.contains_any_early_returns(),
|
||||
Self::TupleAccess { loc_expr, .. } => loc_expr.value.contains_any_early_returns(),
|
||||
Self::RecordUpdate { updates, .. } => {
|
||||
updates.iter().any(|(_field_name, field_update)| {
|
||||
field_update.loc_expr.value.contains_any_early_returns()
|
||||
})
|
||||
}
|
||||
Self::ImportParams(_module_id, _region, params) => params
|
||||
.as_ref()
|
||||
.is_some_and(|(_var, p)| p.contains_any_early_returns()),
|
||||
Self::Tag { arguments, .. } => arguments
|
||||
.iter()
|
||||
.any(|(_var, arg)| arg.value.contains_any_early_returns()),
|
||||
Self::OpaqueRef { argument, .. } => argument.1.value.contains_any_early_returns(),
|
||||
Self::Crash { msg, .. } => msg.value.contains_any_early_returns(),
|
||||
Self::Dbg {
|
||||
loc_message,
|
||||
loc_continuation,
|
||||
..
|
||||
} => {
|
||||
loc_message.value.contains_any_early_returns()
|
||||
|| loc_continuation.value.contains_any_early_returns()
|
||||
}
|
||||
Self::Expect {
|
||||
loc_condition,
|
||||
loc_continuation,
|
||||
..
|
||||
} => {
|
||||
loc_condition.value.contains_any_early_returns()
|
||||
|| loc_continuation.value.contains_any_early_returns()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores exhaustiveness-checking metadata for a closure argument that may
|
||||
|
@ -445,7 +564,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,
|
||||
|
@ -1245,7 +1364,7 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
(loc_expr.value, output)
|
||||
}
|
||||
ast::Expr::DbgStmt(_, _) => {
|
||||
ast::Expr::DbgStmt { .. } => {
|
||||
internal_error!("DbgStmt should have been desugared by now")
|
||||
}
|
||||
ast::Expr::LowLevelDbg((source_location, source), message, continuation) => {
|
||||
|
@ -1285,6 +1404,32 @@ pub fn canonicalize_expr<'a>(
|
|||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::LowLevelTry(loc_expr, kind) => {
|
||||
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(),
|
||||
kind: match kind {
|
||||
ResultTryKind::KeywordPrefix => TryKind::KeywordPrefix,
|
||||
ResultTryKind::OperatorSuffix => TryKind::OperatorSuffix,
|
||||
},
|
||||
},
|
||||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::Return(return_expr, after_return) => {
|
||||
let mut output = Output::default();
|
||||
|
||||
|
@ -1309,7 +1454,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 {
|
||||
|
@ -2251,6 +2398,31 @@ 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,
|
||||
kind,
|
||||
} => {
|
||||
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,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
LetRec(defs, loc_expr, mark) => {
|
||||
let mut new_defs = Vec::with_capacity(defs.len());
|
||||
|
||||
|
@ -2545,8 +2717,9 @@ 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::DbgStmt { .. }
|
||||
| ast::Expr::LowLevelDbg(_, _, _)
|
||||
| ast::Expr::Return(_, _)
|
||||
| ast::Expr::When(_, _)
|
||||
|
@ -3301,7 +3474,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>)>,
|
||||
}
|
||||
|
@ -3443,6 +3616,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,
|
||||
|
|
|
@ -273,6 +273,14 @@ pub fn canonicalize_def_header_pattern<'a>(
|
|||
region,
|
||||
) {
|
||||
Ok((symbol, shadowing_ability_member)) => {
|
||||
if name.contains("__") {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
|
||||
MalformedPatternProblem::BadIdent(
|
||||
roc_parse::ident::BadIdent::TooManyUnderscores(region.start()),
|
||||
),
|
||||
region,
|
||||
)));
|
||||
}
|
||||
let can_pattern = match shadowing_ability_member {
|
||||
// A fresh identifier.
|
||||
None => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -164,7 +164,7 @@ pub fn unwrap_suffixed_expression<'a>(
|
|||
// USEFUL TO SEE THE UNWRAPPING
|
||||
// OF AST NODES AS THEY DESCEND
|
||||
// if is_expr_suffixed(&loc_expr.value) {
|
||||
// dbg!(&maybe_def_pat, &loc_expr, &unwrapped_expression);
|
||||
// eprintln!("{:?}, {:?}, {:?}", &maybe_def_pat, &loc_expr, &unwrapped_expression);
|
||||
// }
|
||||
|
||||
unwrapped_expression
|
||||
|
|
|
@ -400,6 +400,17 @@ 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: _,
|
||||
kind: _,
|
||||
} => {
|
||||
visitor.visit_expr(&result_expr.value, result_expr.region, *result_var);
|
||||
}
|
||||
Expr::Return {
|
||||
return_value,
|
||||
return_var,
|
||||
|
|
|
@ -143,79 +143,70 @@ Defs {
|
|||
ident: "i",
|
||||
},
|
||||
],
|
||||
@132-172 Apply(
|
||||
@132-172 Var {
|
||||
module_name: "Result",
|
||||
ident: "try",
|
||||
},
|
||||
[
|
||||
@132-152 Apply(
|
||||
@132-152 Var {
|
||||
module_name: "",
|
||||
ident: "inc",
|
||||
},
|
||||
[
|
||||
@132-133 Var {
|
||||
module_name: "",
|
||||
ident: "i",
|
||||
@113-189 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@132-172,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Body(
|
||||
@113-117 Identifier {
|
||||
ident: "newi",
|
||||
},
|
||||
],
|
||||
BinOp(
|
||||
Pizza,
|
||||
),
|
||||
),
|
||||
@132-172 Closure(
|
||||
[
|
||||
@132-152 Identifier {
|
||||
ident: "#!0_arg",
|
||||
},
|
||||
],
|
||||
@132-172 Apply(
|
||||
@132-172 Var {
|
||||
module_name: "Result",
|
||||
ident: "try",
|
||||
},
|
||||
[
|
||||
@132-172 LowLevelTry(
|
||||
@132-172 Apply(
|
||||
@132-172 Var {
|
||||
@169-172 Var {
|
||||
module_name: "",
|
||||
ident: "inc",
|
||||
},
|
||||
[
|
||||
@132-152 Var {
|
||||
module_name: "",
|
||||
ident: "#!0_arg",
|
||||
},
|
||||
],
|
||||
BinOp(
|
||||
Pizza,
|
||||
),
|
||||
),
|
||||
@132-172 Closure(
|
||||
[
|
||||
@113-117 Identifier {
|
||||
ident: "newi",
|
||||
},
|
||||
],
|
||||
@182-189 Apply(
|
||||
@182-184 Tag(
|
||||
"Ok",
|
||||
@132-152 LowLevelTry(
|
||||
@132-152 Apply(
|
||||
@149-152 Var {
|
||||
module_name: "",
|
||||
ident: "inc",
|
||||
},
|
||||
[
|
||||
@132-133 Var {
|
||||
module_name: "",
|
||||
ident: "i",
|
||||
},
|
||||
],
|
||||
Try,
|
||||
),
|
||||
OperatorSuffix,
|
||||
),
|
||||
[
|
||||
@185-189 Var {
|
||||
module_name: "",
|
||||
ident: "newi",
|
||||
},
|
||||
],
|
||||
Space,
|
||||
),
|
||||
],
|
||||
Try,
|
||||
),
|
||||
],
|
||||
QuestionSuffix,
|
||||
OperatorSuffix,
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
@182-189 Apply(
|
||||
@182-184 Tag(
|
||||
"Ok",
|
||||
),
|
||||
],
|
||||
QuestionSuffix,
|
||||
[
|
||||
@185-189 Var {
|
||||
module_name: "",
|
||||
ident: "newi",
|
||||
},
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -802,6 +802,106 @@ 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(), 2);
|
||||
|
||||
assert_str_value(&cond_args[0].1.value, "123");
|
||||
|
||||
let ok_tag = assert_try_expr(&cond_args[1].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]
|
||||
fn try_desugar_plain_prefix() {
|
||||
let src = indoc!(
|
||||
|
@ -816,64 +916,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 +939,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 +962,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]
|
||||
|
@ -1126,6 +1073,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) {
|
||||
match pattern {
|
||||
Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), name),
|
||||
|
@ -1142,6 +1099,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,80 @@ 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,
|
||||
kind,
|
||||
} => {
|
||||
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,
|
||||
*kind,
|
||||
);
|
||||
|
||||
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 +1122,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 +1194,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);
|
||||
|
@ -1438,14 +1537,16 @@ pub fn constrain_expr(
|
|||
return_value.region,
|
||||
));
|
||||
|
||||
constrain_expr(
|
||||
let return_con = constrain_expr(
|
||||
types,
|
||||
constraints,
|
||||
env,
|
||||
return_value.region,
|
||||
&return_value.value,
|
||||
expected_return_value,
|
||||
)
|
||||
);
|
||||
|
||||
constraints.exists([*return_var], return_con)
|
||||
}
|
||||
Tag {
|
||||
tag_union_var: variant_var,
|
||||
|
@ -2072,43 +2173,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);
|
||||
|
||||
|
@ -2457,7 +2534,7 @@ fn constrain_when_branch_help(
|
|||
types,
|
||||
constraints,
|
||||
env,
|
||||
region,
|
||||
when_branch.value.region,
|
||||
&when_branch.value.value,
|
||||
expr_expected,
|
||||
);
|
||||
|
@ -2964,32 +3041,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);
|
||||
|
@ -3435,12 +3496,18 @@ fn constrain_let_def(
|
|||
)
|
||||
});
|
||||
|
||||
// Ignored def must be effectful, otherwise it's dead code
|
||||
let effectful_constraint = Constraint::ExpectEffectful(
|
||||
fx_var,
|
||||
ExpectEffectfulReason::Ignored,
|
||||
def.loc_pattern.region,
|
||||
);
|
||||
let effectful_constraint = if def.loc_expr.value.contains_any_early_returns() {
|
||||
// If the statement has early returns, it doesn't need to be effectful to
|
||||
// potentially affect the output of the containing function
|
||||
Constraint::True
|
||||
} else {
|
||||
// If there are no early returns, it must be effectful or else it's dead code
|
||||
Constraint::ExpectEffectful(
|
||||
fx_var,
|
||||
ExpectEffectfulReason::Ignored,
|
||||
def.loc_pattern.region,
|
||||
)
|
||||
};
|
||||
|
||||
let enclosing_fx_constraint = constraints.fx_call(
|
||||
fx_var,
|
||||
|
@ -3527,9 +3594,14 @@ fn constrain_stmt_def(
|
|||
generalizable,
|
||||
);
|
||||
|
||||
// Stmt expr must be effectful, otherwise it's dead code
|
||||
let effectful_constraint =
|
||||
Constraint::ExpectEffectful(fx_var, ExpectEffectfulReason::Stmt, region);
|
||||
let effectful_constraint = if def.loc_expr.value.contains_any_early_returns() {
|
||||
// If the statement has early returns, it doesn't need to be effectful to
|
||||
// potentially affect the output of the containing function
|
||||
Constraint::True
|
||||
} else {
|
||||
// If there are no early returns, it must be effectful or else it's dead code
|
||||
Constraint::ExpectEffectful(fx_var, ExpectEffectfulReason::Stmt, region)
|
||||
};
|
||||
|
||||
let fx_call_kind = match fn_name {
|
||||
None => FxCallKind::Stmt,
|
||||
|
@ -4012,35 +4084,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);
|
||||
|
@ -4393,6 +4452,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
|
|||
| RecordUpdate { .. }
|
||||
| Expect { .. }
|
||||
| Dbg { .. }
|
||||
| Try { .. }
|
||||
| Return { .. }
|
||||
| RuntimeError(..)
|
||||
| ZeroArgumentTag { .. }
|
||||
|
@ -4584,37 +4644,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);
|
||||
|
|
|
@ -15,3 +15,4 @@ roc_region.workspace = true
|
|||
roc_error_macros.workspace = true
|
||||
|
||||
bumpalo.workspace = true
|
||||
soa.workspace = true
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,13 @@
|
|||
use roc_parse::ast::{Collection, CommentOrNewline, ExtractSpaces};
|
||||
use roc_parse::{
|
||||
ast::{Collection, CommentOrNewline, ExtractSpaces},
|
||||
expr::merge_spaces,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
annotation::{is_collection_multiline, Formattable, Newlines},
|
||||
spaces::{fmt_comments_only, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Braces {
|
||||
Round,
|
||||
|
@ -13,26 +15,36 @@ pub enum Braces {
|
|||
Curly,
|
||||
}
|
||||
|
||||
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
impl Braces {
|
||||
pub fn start(self) -> char {
|
||||
match self {
|
||||
Braces::Round => '(',
|
||||
Braces::Curly => '{',
|
||||
Braces::Square => '[',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(self) -> char {
|
||||
match self {
|
||||
Braces::Round => ')',
|
||||
Braces::Curly => '}',
|
||||
Braces::Square => ']',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable + std::fmt::Debug>(
|
||||
buf: &mut Buf<'buf>,
|
||||
|
||||
indent: u16,
|
||||
braces: Braces,
|
||||
items: Collection<'a, T>,
|
||||
newline: Newlines,
|
||||
) where
|
||||
<T as ExtractSpaces<'a>>::Item: Formattable,
|
||||
<T as ExtractSpaces<'a>>::Item: Formattable + std::fmt::Debug,
|
||||
{
|
||||
let start = match braces {
|
||||
Braces::Round => '(',
|
||||
Braces::Curly => '{',
|
||||
Braces::Square => '[',
|
||||
};
|
||||
|
||||
let end = match braces {
|
||||
Braces::Round => ')',
|
||||
Braces::Curly => '}',
|
||||
Braces::Square => ']',
|
||||
};
|
||||
let start = braces.start();
|
||||
let end = braces.end();
|
||||
|
||||
if is_collection_multiline(&items) {
|
||||
let braces_indent = indent;
|
||||
|
@ -43,10 +55,21 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
buf.indent(braces_indent);
|
||||
buf.push(start);
|
||||
|
||||
let mut last_after: &[CommentOrNewline<'_>] = &[];
|
||||
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
let is_first_item = index == 0;
|
||||
let item = item.extract_spaces();
|
||||
let is_only_newlines = item.before.iter().all(|s| s.is_newline());
|
||||
let last_after_was_only_newlines = last_after.iter().all(|s| s.is_newline());
|
||||
|
||||
if !last_after.is_empty() {
|
||||
if last_after.iter().any(|s| s.is_newline()) {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
fmt_comments_only(buf, last_after.iter(), NewlineAt::None, item_indent);
|
||||
}
|
||||
|
||||
if item.before.is_empty() || is_only_newlines {
|
||||
buf.ensure_ends_with_newline();
|
||||
|
@ -64,13 +87,14 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
if item
|
||||
.before
|
||||
.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
|
||||
&& last_after_was_only_newlines
|
||||
{
|
||||
// If there's a comment, and it's not on the first item,
|
||||
// and it's preceded by at least one blank line, maintain 1 blank line.
|
||||
// (We already ensured that it ends in a newline, so this will turn that
|
||||
// into a blank line.)
|
||||
|
||||
buf.newline();
|
||||
buf.ensure_ends_with_blank_line();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,32 +115,30 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
buf.indent(item_indent);
|
||||
buf.push(',');
|
||||
|
||||
if !item.after.is_empty() {
|
||||
if item.after.iter().any(|s| s.is_newline()) {
|
||||
buf.newline();
|
||||
}
|
||||
last_after = item.after;
|
||||
}
|
||||
|
||||
fmt_comments_only(buf, item.after.iter(), NewlineAt::None, item_indent);
|
||||
let final_comments = if !last_after.is_empty() {
|
||||
if last_after.iter().any(|s| s.is_newline()) {
|
||||
buf.newline();
|
||||
}
|
||||
}
|
||||
|
||||
if items.final_comments().iter().any(|s| s.is_newline()) {
|
||||
buf.newline();
|
||||
}
|
||||
merge_spaces(buf.text.bump(), last_after, items.final_comments())
|
||||
} else {
|
||||
if items.final_comments().iter().any(|s| s.is_newline()) {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
if items
|
||||
.final_comments()
|
||||
.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
|
||||
items.final_comments()
|
||||
};
|
||||
|
||||
if has_comments(final_comments)
|
||||
&& final_comments.starts_with(&[CommentOrNewline::Newline, CommentOrNewline::Newline])
|
||||
{
|
||||
buf.newline();
|
||||
buf.ensure_ends_with_blank_line();
|
||||
}
|
||||
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
items.final_comments().iter(),
|
||||
NewlineAt::None,
|
||||
item_indent,
|
||||
);
|
||||
fmt_comments_only(buf, final_comments.iter(), NewlineAt::None, item_indent);
|
||||
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(braces_indent);
|
||||
|
@ -144,3 +166,13 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
|||
|
||||
buf.push(end);
|
||||
}
|
||||
|
||||
fn has_comments(spaces: &[CommentOrNewline<'_>]) -> bool {
|
||||
for space in spaces {
|
||||
match space {
|
||||
CommentOrNewline::Newline => {}
|
||||
CommentOrNewline::LineComment(_) | CommentOrNewline::DocComment(_) => return true,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
|
||||
use crate::annotation::{
|
||||
ann_lift_spaces, ann_lift_spaces_after, is_collection_multiline, ty_is_outdentable,
|
||||
Formattable, Newlines, Parens,
|
||||
};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
|
||||
use crate::expr::{
|
||||
expr_lift_and_lower, expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before,
|
||||
fmt_str_literal, is_str_multiline, sub_expr_requests_parens,
|
||||
};
|
||||
use crate::pattern::{fmt_pattern, pattern_lift_spaces};
|
||||
use crate::pattern::{pattern_lift_spaces_before, starts_with_inline_comment};
|
||||
use crate::spaces::{
|
||||
fmt_comments_only, fmt_default_newline, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT,
|
||||
};
|
||||
use crate::Buf;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
||||
ModuleImportParams, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
AbilityMember, CommentOrNewline, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword,
|
||||
ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport,
|
||||
ModuleImport, ModuleImportParams, Pattern, Spaceable, Spaces, SpacesAfter, SpacesBefore,
|
||||
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use roc_parse::expr::merge_spaces;
|
||||
use roc_parse::header::Keyword;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
|
@ -25,21 +37,28 @@ impl<'a> Formattable for Defs<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let mut prev_spaces = true;
|
||||
let arena = buf.text.bump();
|
||||
|
||||
for (index, def) in self.defs().enumerate() {
|
||||
let spaces_before = &self.spaces[self.space_before[index].indices()];
|
||||
let spaces_after = &self.spaces[self.space_after[index].indices()];
|
||||
|
||||
let def = def_lift_spaces(buf.text.bump(), def);
|
||||
|
||||
let spaces_before = merge_spaces(arena, spaces_before, def.before);
|
||||
let spaces_after = merge_spaces(arena, def.after, spaces_after);
|
||||
|
||||
if prev_spaces {
|
||||
fmt_spaces(buf, spaces_before.iter(), indent);
|
||||
} else {
|
||||
fmt_default_newline(buf, spaces_before, indent);
|
||||
}
|
||||
|
||||
match def {
|
||||
match def.item {
|
||||
Ok(type_def) => type_def.format(buf, indent),
|
||||
Err(value_def) => value_def.format(buf, indent),
|
||||
}
|
||||
|
@ -51,6 +70,339 @@ impl<'a> Formattable for Defs<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn def_lift_spaces<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
def: Result<&'a TypeDef<'b>, &'a ValueDef<'b>>,
|
||||
) -> Spaces<'a, Result<TypeDef<'a>, ValueDef<'a>>> {
|
||||
match def {
|
||||
Ok(td) => {
|
||||
let td = tydef_lift_spaces(arena, *td);
|
||||
Spaces {
|
||||
before: td.before,
|
||||
item: Ok(td.item),
|
||||
after: td.after,
|
||||
}
|
||||
}
|
||||
Err(vd) => {
|
||||
let vd = valdef_lift_spaces(arena, *vd);
|
||||
Spaces {
|
||||
before: vd.before,
|
||||
item: Err(vd.item),
|
||||
after: vd.after,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lift_spaces_after<'a, 'b: 'a, T: 'b + ExtractSpaces<'a> + Spaceable<'a>>(
|
||||
arena: &'a Bump,
|
||||
item: T,
|
||||
) -> SpacesAfter<'a, <T as ExtractSpaces<'a>>::Item>
|
||||
where
|
||||
<T as ExtractSpaces<'a>>::Item: Spaceable<'a>,
|
||||
{
|
||||
let spaces = item.extract_spaces();
|
||||
|
||||
SpacesAfter {
|
||||
item: spaces.item.maybe_before(arena, spaces.before),
|
||||
after: spaces.after,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tydef_lift_spaces<'a, 'b: 'a>(arena: &'a Bump, def: TypeDef<'b>) -> Spaces<'a, TypeDef<'a>> {
|
||||
match def {
|
||||
TypeDef::Alias { header, ann } => {
|
||||
let ann_lifted = ann_lift_spaces_after(arena, &ann.value);
|
||||
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: TypeDef::Alias {
|
||||
header,
|
||||
ann: Loc::at(ann.region, ann_lifted.item),
|
||||
},
|
||||
after: ann_lifted.after,
|
||||
}
|
||||
}
|
||||
TypeDef::Opaque {
|
||||
header,
|
||||
typ,
|
||||
derived,
|
||||
} => {
|
||||
if let Some(derived) = derived {
|
||||
let derived_lifted = lift_spaces_after(arena, derived.value);
|
||||
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: TypeDef::Opaque {
|
||||
header,
|
||||
typ,
|
||||
derived: Some(Loc::at(derived.region, derived_lifted.item)),
|
||||
},
|
||||
after: derived_lifted.after,
|
||||
}
|
||||
} else {
|
||||
let typ_lifted = ann_lift_spaces_after(arena, &typ.value);
|
||||
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: TypeDef::Opaque {
|
||||
header,
|
||||
typ: Loc::at(typ.region, typ_lifted.item),
|
||||
derived,
|
||||
},
|
||||
after: typ_lifted.after,
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDef::Ability {
|
||||
header: _,
|
||||
loc_implements: _,
|
||||
members: _,
|
||||
} => {
|
||||
// TODO: if the fuzzer ever generates examples where it's important to lift spaces from the members,
|
||||
// we'll need to implement this. I'm not sure that's possible, though.
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: def,
|
||||
after: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valdef_lift_spaces<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
def: ValueDef<'b>,
|
||||
) -> Spaces<'a, ValueDef<'a>> {
|
||||
match def {
|
||||
ValueDef::Annotation(pat, ann) => {
|
||||
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
|
||||
let ann_lifted = ann_lift_spaces_after(arena, &ann.value);
|
||||
|
||||
Spaces {
|
||||
before: pat_lifted.before,
|
||||
item: ValueDef::Annotation(
|
||||
Loc::at(pat.region, pat_lifted.item),
|
||||
Loc::at(ann.region, ann_lifted.item),
|
||||
),
|
||||
after: ann_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::Body(pat, expr) => {
|
||||
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
|
||||
let expr_lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &expr.value);
|
||||
|
||||
Spaces {
|
||||
before: pat_lifted.before,
|
||||
item: ValueDef::Body(
|
||||
arena.alloc(Loc::at(pat.region, pat_lifted.item)),
|
||||
arena.alloc(Loc::at(expr.region, expr_lifted.item)),
|
||||
),
|
||||
after: expr_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
lines_between,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
let ann_pattern_lifted = pattern_lift_spaces_before(arena, &ann_pattern.value);
|
||||
let ann_type_lifted = ann_lift_spaces_after(arena, &ann_type.value);
|
||||
let body_pattern_lifted = pattern_lift_spaces_before(arena, &body_pattern.value);
|
||||
let body_expr_lifted =
|
||||
expr_lift_spaces_after(Parens::NotNeeded, arena, &body_expr.value);
|
||||
|
||||
let lines_between = merge_spaces(
|
||||
arena,
|
||||
ann_type_lifted.after,
|
||||
merge_spaces(arena, lines_between, body_pattern_lifted.before),
|
||||
);
|
||||
|
||||
Spaces {
|
||||
before: ann_pattern_lifted.before,
|
||||
item: ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(Loc::at(ann_pattern.region, ann_pattern_lifted.item)),
|
||||
ann_type: arena.alloc(Loc::at(ann_type.region, ann_type_lifted.item)),
|
||||
lines_between,
|
||||
body_pattern: arena
|
||||
.alloc(Loc::at(body_pattern.region, body_pattern_lifted.item)),
|
||||
body_expr: arena.alloc(Loc::at(body_expr.region, body_expr_lifted.item)),
|
||||
},
|
||||
after: body_expr_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::Dbg {
|
||||
condition,
|
||||
preceding_comment,
|
||||
} => {
|
||||
let condition_lifted =
|
||||
expr_lift_spaces_after(Parens::NotNeeded, arena, &condition.value);
|
||||
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: ValueDef::Dbg {
|
||||
condition: arena.alloc(Loc::at(condition.region, condition_lifted.item)),
|
||||
preceding_comment,
|
||||
},
|
||||
after: condition_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::Expect {
|
||||
condition,
|
||||
preceding_comment,
|
||||
} => {
|
||||
let condition_lifted =
|
||||
expr_lift_spaces_after(Parens::NotNeeded, arena, &condition.value);
|
||||
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: ValueDef::Expect {
|
||||
condition: arena.alloc(Loc::at(condition.region, condition_lifted.item)),
|
||||
preceding_comment,
|
||||
},
|
||||
after: condition_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::ModuleImport(module_import) => {
|
||||
// Module imports begin with 'import', and end with either a ImportAlias or Collection
|
||||
// No spaces in sight!
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: ValueDef::ModuleImport(module_import),
|
||||
after: &[],
|
||||
}
|
||||
}
|
||||
ValueDef::IngestedFileImport(mut ingested_file_import) => {
|
||||
// Ingested file imports begin with 'import', but can end with a TypeAnnotation, which can have spaces
|
||||
let after = if let Some(ann) = &mut ingested_file_import.annotation {
|
||||
let lifted = ann_lift_spaces_after(arena, &ann.annotation.value);
|
||||
ann.annotation.value = lifted.item;
|
||||
lifted.after
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: ValueDef::IngestedFileImport(ingested_file_import),
|
||||
after,
|
||||
}
|
||||
}
|
||||
ValueDef::Stmt(expr) => {
|
||||
let expr_lifted = expr_lift_spaces(Parens::NotNeeded, arena, &expr.value);
|
||||
Spaces {
|
||||
before: expr_lifted.before,
|
||||
item: ValueDef::Stmt(arena.alloc(Loc::at(expr.region, expr_lifted.item))),
|
||||
after: expr_lifted.after,
|
||||
}
|
||||
}
|
||||
ValueDef::StmtAfterExpr => Spaces {
|
||||
before: &[],
|
||||
item: ValueDef::StmtAfterExpr,
|
||||
after: &[],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valdef_lift_spaces_before<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
def: ValueDef<'b>,
|
||||
) -> SpacesBefore<'a, ValueDef<'a>> {
|
||||
match def {
|
||||
ValueDef::Annotation(pat, ann) => {
|
||||
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
|
||||
|
||||
SpacesBefore {
|
||||
before: pat_lifted.before,
|
||||
item: ValueDef::Annotation(Loc::at(pat.region, pat_lifted.item), ann),
|
||||
}
|
||||
}
|
||||
ValueDef::Body(pat, expr) => {
|
||||
let pat_lifted = pattern_lift_spaces_before(arena, &pat.value);
|
||||
|
||||
SpacesBefore {
|
||||
before: pat_lifted.before,
|
||||
item: ValueDef::Body(arena.alloc(Loc::at(pat.region, pat_lifted.item)), expr),
|
||||
}
|
||||
}
|
||||
ValueDef::AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
lines_between,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
let ann_pattern_lifted = pattern_lift_spaces_before(arena, &ann_pattern.value);
|
||||
let ann_type_lifted = ann_lift_spaces_after(arena, &ann_type.value);
|
||||
let body_pattern_lifted = pattern_lift_spaces_before(arena, &body_pattern.value);
|
||||
|
||||
let lines_between = merge_spaces(
|
||||
arena,
|
||||
ann_type_lifted.after,
|
||||
merge_spaces(arena, lines_between, body_pattern_lifted.before),
|
||||
);
|
||||
|
||||
SpacesBefore {
|
||||
before: ann_pattern_lifted.before,
|
||||
item: ValueDef::AnnotatedBody {
|
||||
ann_pattern: arena.alloc(Loc::at(ann_pattern.region, ann_pattern_lifted.item)),
|
||||
ann_type: arena.alloc(Loc::at(ann_type.region, ann_type_lifted.item)),
|
||||
lines_between,
|
||||
body_pattern: arena
|
||||
.alloc(Loc::at(body_pattern.region, body_pattern_lifted.item)),
|
||||
body_expr,
|
||||
},
|
||||
}
|
||||
}
|
||||
ValueDef::Dbg {
|
||||
condition,
|
||||
preceding_comment,
|
||||
} => SpacesBefore {
|
||||
before: &[],
|
||||
item: ValueDef::Dbg {
|
||||
condition,
|
||||
preceding_comment,
|
||||
},
|
||||
},
|
||||
ValueDef::Expect {
|
||||
condition,
|
||||
preceding_comment,
|
||||
} => SpacesBefore {
|
||||
before: &[],
|
||||
item: ValueDef::Expect {
|
||||
condition,
|
||||
preceding_comment,
|
||||
},
|
||||
},
|
||||
ValueDef::ModuleImport(module_import) => {
|
||||
// Module imports always start with 'import', no spaces
|
||||
SpacesBefore {
|
||||
before: &[],
|
||||
item: ValueDef::ModuleImport(module_import),
|
||||
}
|
||||
}
|
||||
ValueDef::IngestedFileImport(ingested_file_import) => {
|
||||
// Similarly, ingested file imports always start with 'import', no spaces
|
||||
SpacesBefore {
|
||||
before: &[],
|
||||
item: ValueDef::IngestedFileImport(ingested_file_import),
|
||||
}
|
||||
}
|
||||
ValueDef::Stmt(expr) => {
|
||||
let expr_lifted = expr_lift_spaces_before(Parens::NotNeeded, arena, &expr.value);
|
||||
SpacesBefore {
|
||||
before: expr_lifted.before,
|
||||
item: ValueDef::Stmt(arena.alloc(Loc::at(expr.region, expr_lifted.item))),
|
||||
}
|
||||
}
|
||||
ValueDef::StmtAfterExpr => SpacesBefore {
|
||||
before: &[],
|
||||
item: ValueDef::StmtAfterExpr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for TypeDef<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::TypeDef::*;
|
||||
|
@ -66,34 +418,23 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
use roc_parse::ast::TypeDef::*;
|
||||
|
||||
match self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => {
|
||||
Alias { header, ann } => {
|
||||
header.format(buf, indent);
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
|
||||
let need_parens = matches!(var.value, Pattern::Apply(..));
|
||||
|
||||
if need_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
buf.indent(indent);
|
||||
|
||||
if need_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
|
||||
buf.push_str(" :");
|
||||
buf.spaces(1);
|
||||
|
||||
ann.format(buf, indent)
|
||||
let ann = ann_lift_spaces(buf.text.bump(), &ann.value);
|
||||
|
||||
let inner_indent = if ty_is_outdentable(&ann.item) {
|
||||
indent
|
||||
} else {
|
||||
indent + INDENT
|
||||
};
|
||||
fmt_comments_only(buf, ann.before.iter(), NewlineAt::Bottom, inner_indent);
|
||||
ann.item.format(buf, inner_indent);
|
||||
fmt_spaces(buf, ann.after.iter(), indent);
|
||||
}
|
||||
Opaque {
|
||||
header,
|
||||
|
@ -113,7 +454,15 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
|
||||
let make_multiline = ann.is_multiline() || has_abilities_multiline;
|
||||
|
||||
fmt_general_def(header, buf, indent, ":=", &ann.value, newlines);
|
||||
fmt_general_def(
|
||||
header,
|
||||
Parens::NotNeeded,
|
||||
buf,
|
||||
indent,
|
||||
":=",
|
||||
&ann.value,
|
||||
newlines,
|
||||
);
|
||||
|
||||
if let Some(has_abilities) = has_abilities {
|
||||
buf.spaces(1);
|
||||
|
@ -127,23 +476,17 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
}
|
||||
}
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
header,
|
||||
loc_implements: _,
|
||||
members,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
buf.indent(indent);
|
||||
}
|
||||
header.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
buf.spaces(1);
|
||||
buf.push_str(roc_parse::keyword::IMPLEMENTS);
|
||||
buf.spaces(1);
|
||||
|
||||
if !self.is_multiline() {
|
||||
debug_assert_eq!(members.len(), 1);
|
||||
buf.spaces(1);
|
||||
members[0].format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
|
@ -175,15 +518,68 @@ impl<'a> Formattable for TypeHeader<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.name.value);
|
||||
|
||||
let vars_indent = if self.vars.iter().any(|v| v.is_multiline()) {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
let mut last_after: &[CommentOrNewline<'_>] = &[];
|
||||
let mut last_multiline = false;
|
||||
|
||||
for var in self.vars.iter() {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
buf.indent(indent);
|
||||
let var = pattern_lift_spaces(buf.text.bump(), &var.value);
|
||||
|
||||
let before = if !last_after.is_empty() {
|
||||
merge_spaces(buf.text.bump(), last_after, var.before)
|
||||
} else {
|
||||
var.before
|
||||
};
|
||||
|
||||
if !before.is_empty() {
|
||||
if !var.item.is_multiline() {
|
||||
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, vars_indent)
|
||||
} else {
|
||||
fmt_spaces(buf, before.iter(), vars_indent);
|
||||
}
|
||||
}
|
||||
|
||||
buf.ensure_ends_with_whitespace();
|
||||
|
||||
last_after = var.after;
|
||||
last_multiline = var.item.is_multiline();
|
||||
|
||||
let need_parens = matches!(var.item, Pattern::Apply(..));
|
||||
|
||||
if need_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
fmt_pattern(buf, &var.item, vars_indent, Parens::NotNeeded);
|
||||
|
||||
buf.indent(vars_indent);
|
||||
|
||||
if need_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
|
||||
if !last_after.is_empty() {
|
||||
if starts_with_inline_comment(last_after.iter()) {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
if !last_multiline {
|
||||
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, last_after.iter(), indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +609,7 @@ impl<'a> Formattable for ModuleImport<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
|
@ -228,6 +625,7 @@ impl<'a> Formattable for ModuleImport<'a> {
|
|||
|
||||
let indent = if !before_name.is_empty()
|
||||
|| (params.is_multiline() && exposed.is_some())
|
||||
|| params.map(|p| !p.before.is_empty()).unwrap_or(false)
|
||||
|| alias.is_multiline()
|
||||
|| exposed.map_or(false, |e| e.keyword.is_multiline())
|
||||
{
|
||||
|
@ -280,6 +678,7 @@ impl<'a> Formattable for IngestedFileImport<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
|
@ -315,6 +714,7 @@ impl<'a> Formattable for ImportedModuleName<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
|
@ -394,6 +794,7 @@ impl<'a> Formattable for IngestedFileAnnotation<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
|
@ -431,8 +832,14 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
use roc_parse::ast::ValueDef::*;
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
let pat_parens = if ann_pattern_needs_parens(&loc_pattern.value) {
|
||||
Parens::InApply
|
||||
} else {
|
||||
Parens::NotNeeded
|
||||
};
|
||||
fmt_general_def(
|
||||
loc_pattern,
|
||||
pat_parens,
|
||||
buf,
|
||||
indent,
|
||||
":",
|
||||
|
@ -441,7 +848,7 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
);
|
||||
}
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
|
||||
fmt_body(buf, true, &loc_pattern.value, &loc_expr.value, indent);
|
||||
}
|
||||
Dbg { condition, .. } => fmt_dbg_in_def(buf, condition, self.is_multiline(), indent),
|
||||
Expect { condition, .. } => fmt_expect(buf, condition, self.is_multiline(), indent),
|
||||
|
@ -452,12 +859,25 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines);
|
||||
let pat_parens = if ann_pattern_needs_parens(&ann_pattern.value) {
|
||||
Parens::InApply
|
||||
} else {
|
||||
Parens::NotNeeded
|
||||
};
|
||||
fmt_general_def(
|
||||
ann_pattern,
|
||||
pat_parens,
|
||||
buf,
|
||||
indent,
|
||||
":",
|
||||
&ann_type.value,
|
||||
newlines,
|
||||
);
|
||||
|
||||
fmt_annotated_body_comment(buf, indent, lines_between);
|
||||
|
||||
buf.newline();
|
||||
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
|
||||
fmt_body(buf, false, &body_pattern.value, &body_expr.value, indent);
|
||||
}
|
||||
ModuleImport(module_import) => module_import.format(buf, indent),
|
||||
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
|
||||
|
@ -467,15 +887,27 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn ann_pattern_needs_parens(value: &Pattern<'_>) -> bool {
|
||||
match value.extract_spaces().item {
|
||||
Pattern::Tag(_) => true,
|
||||
Pattern::Apply(func, _args) if matches!(func.extract_spaces().item, Pattern::Tag(..)) => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_general_def<L: Formattable>(
|
||||
lhs: L,
|
||||
lhs_parens: Parens,
|
||||
buf: &mut Buf,
|
||||
|
||||
indent: u16,
|
||||
sep: &str,
|
||||
rhs: &TypeAnnotation,
|
||||
newlines: Newlines,
|
||||
) {
|
||||
lhs.format(buf, indent);
|
||||
lhs.format_with_options(buf, lhs_parens, Newlines::Yes, indent);
|
||||
buf.indent(indent);
|
||||
|
||||
if rhs.is_multiline() {
|
||||
|
@ -483,20 +915,25 @@ fn fmt_general_def<L: Formattable>(
|
|||
buf.push_str(sep);
|
||||
buf.spaces(1);
|
||||
|
||||
let should_outdent = should_outdent(rhs);
|
||||
let rhs_lifted = ann_lift_spaces(buf.text.bump(), rhs);
|
||||
|
||||
if should_outdent {
|
||||
match rhs {
|
||||
TypeAnnotation::SpaceBefore(sub_def, _) => {
|
||||
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
}
|
||||
_ => {
|
||||
rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
}
|
||||
}
|
||||
if ty_is_outdentable(&rhs_lifted.item) && rhs_lifted.before.iter().all(|s| s.is_newline()) {
|
||||
rhs_lifted
|
||||
.item
|
||||
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
} else {
|
||||
rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
|
||||
buf.ensure_ends_with_newline();
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
rhs_lifted.before.iter(),
|
||||
NewlineAt::Bottom,
|
||||
indent + INDENT,
|
||||
);
|
||||
rhs_lifted
|
||||
.item
|
||||
.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
|
||||
}
|
||||
fmt_comments_only(buf, rhs_lifted.after.iter(), NewlineAt::Bottom, indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
buf.push_str(sep);
|
||||
|
@ -505,28 +942,6 @@ fn fmt_general_def<L: Formattable>(
|
|||
}
|
||||
}
|
||||
|
||||
fn should_outdent(mut rhs: &TypeAnnotation) -> bool {
|
||||
loop {
|
||||
match rhs {
|
||||
TypeAnnotation::SpaceBefore(sub_def, spaces) => {
|
||||
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
|
||||
if !is_only_newlines || !sub_def.is_multiline() {
|
||||
return false;
|
||||
}
|
||||
rhs = sub_def;
|
||||
}
|
||||
TypeAnnotation::Where(ann, _clauses) => {
|
||||
if !ann.is_multiline() {
|
||||
return false;
|
||||
}
|
||||
rhs = &ann.value;
|
||||
}
|
||||
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dbg_in_def<'a>(buf: &mut Buf, condition: &'a Loc<Expr<'a>>, _: bool, indent: u16) {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
|
@ -608,21 +1023,44 @@ pub fn fmt_annotated_body_comment<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
|
||||
pub fn fmt_body<'a>(
|
||||
buf: &mut Buf,
|
||||
allow_simplify_empty_record_destructure: bool,
|
||||
pattern: &'a Pattern<'a>,
|
||||
body: &'a Expr<'a>,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let pattern_extracted = pattern.extract_spaces();
|
||||
// Check if this is an assignment into the unit value
|
||||
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = pattern {
|
||||
collection.is_empty()
|
||||
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = pattern_extracted.item
|
||||
{
|
||||
allow_simplify_empty_record_destructure
|
||||
&& collection.is_empty()
|
||||
&& pattern_extracted.before.iter().all(|s| s.is_newline())
|
||||
&& pattern_extracted.after.iter().all(|s| s.is_newline())
|
||||
&& !matches!(body.extract_spaces().item, Expr::Defs(..))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Don't format the `{} =` for defs with this pattern
|
||||
if !is_unit_assignment {
|
||||
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str(" =");
|
||||
if is_unit_assignment {
|
||||
return body.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
}
|
||||
|
||||
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
|
||||
if pattern.is_multiline() {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
buf.push_str("=");
|
||||
|
||||
let body = expr_lift_and_lower(Parens::NotNeeded, buf.text.bump(), body);
|
||||
|
||||
if body.is_multiline() {
|
||||
match body {
|
||||
Expr::SpaceBefore(sub_def, spaces) => {
|
||||
|
@ -634,7 +1072,10 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
|
|||
_ => false,
|
||||
};
|
||||
|
||||
if should_outdent {
|
||||
if is_unit_assignment {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
} else if should_outdent {
|
||||
buf.spaces(1);
|
||||
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
} else {
|
||||
|
@ -646,6 +1087,32 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
|
|||
);
|
||||
}
|
||||
}
|
||||
Expr::Apply(
|
||||
Loc {
|
||||
value: Expr::Str(StrLiteral::Block(..)),
|
||||
..
|
||||
},
|
||||
..,
|
||||
) => {
|
||||
buf.spaces(1);
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
}
|
||||
Expr::Str(s) => {
|
||||
if is_str_multiline(&s) {
|
||||
buf.ensure_ends_with_newline();
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
}
|
||||
_ if starts_with_block_string_literal(&body) => {
|
||||
buf.ensure_ends_with_newline();
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
}
|
||||
Expr::When(..) => {
|
||||
buf.ensure_ends_with_newline();
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
}
|
||||
Expr::Defs(..) | Expr::BinOps(_, _) | Expr::Backpassing(..) => {
|
||||
// Binop chains always get a newline. Otherwise you can have things like:
|
||||
//
|
||||
|
@ -662,9 +1129,15 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
|
|||
buf.newline();
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
}
|
||||
Expr::When(..) | Expr::Str(StrLiteral::Block(_)) => {
|
||||
buf.ensure_ends_with_newline();
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||
Expr::ParensAround(&Expr::SpaceBefore(sub_def, _)) => {
|
||||
let needs_indent = !sub_expr_requests_parens(sub_def);
|
||||
let indent = if needs_indent {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
buf.spaces(1);
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
}
|
||||
_ => {
|
||||
buf.spaces(1);
|
||||
|
@ -677,6 +1150,18 @@ pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>,
|
|||
}
|
||||
}
|
||||
|
||||
pub fn starts_with_block_string_literal(expr: &Expr<'_>) -> bool {
|
||||
match expr {
|
||||
Expr::Str(s) => is_str_multiline(s),
|
||||
Expr::SpaceAfter(inner, _) | Expr::SpaceBefore(inner, _) => {
|
||||
starts_with_block_string_literal(inner)
|
||||
}
|
||||
Expr::Apply(inner, _, _) => starts_with_block_string_literal(&inner.value),
|
||||
Expr::TrySuffix { target: _, expr } => starts_with_block_string_literal(expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for AbilityMember<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.name.value.is_multiline() || self.typ.is_multiline()
|
||||
|
@ -687,6 +1172,7 @@ impl<'a> Formattable for AbilityMember<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
let Spaces { before, item, .. } = self.name.value.extract_spaces();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -84,6 +84,7 @@ impl<V: Formattable> Formattable for Option<V> {
|
|||
buf: &mut Buf,
|
||||
parens: crate::annotation::Parens,
|
||||
newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
if let Some(v) = self {
|
||||
|
@ -109,6 +110,7 @@ impl<'a> Formattable for ProvidesTo<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: crate::annotation::Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
self.provides_keyword.format(buf, indent);
|
||||
|
@ -128,6 +130,7 @@ impl<'a> Formattable for PlatformRequires<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: crate::annotation::Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_requires(buf, self, indent);
|
||||
|
@ -144,6 +147,7 @@ impl<'a, V: Formattable> Formattable for Spaces<'a, V> {
|
|||
buf: &mut Buf,
|
||||
parens: crate::annotation::Parens,
|
||||
newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_default_spaces(buf, self.before, indent);
|
||||
|
@ -280,6 +284,7 @@ impl<'a> Formattable for TypedIdent<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
|
@ -316,6 +321,7 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
|
|||
buf: &mut Buf,
|
||||
parens: crate::annotation::Parens,
|
||||
newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
match self {
|
||||
|
@ -337,6 +343,7 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
|
|||
fn fmt_imports<'a>(
|
||||
buf: &mut Buf,
|
||||
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
|
||||
|
@ -346,6 +353,7 @@ fn fmt_provides<'a>(
|
|||
buf: &mut Buf,
|
||||
loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
|
||||
|
@ -367,6 +375,7 @@ fn fmt_to(buf: &mut Buf, to: To, indent: u16) {
|
|||
fn fmt_exposes<N: Formattable + Copy + core::fmt::Debug>(
|
||||
buf: &mut Buf,
|
||||
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
|
||||
|
@ -445,6 +454,7 @@ impl<'a> Formattable for PackageEntry<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_packages_entry(buf, self, indent);
|
||||
|
@ -461,6 +471,7 @@ impl<'a> Formattable for ImportsEntry<'a> {
|
|||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_imports_entry(buf, self, indent);
|
||||
|
|
|
@ -18,18 +18,41 @@ pub struct Buf<'a> {
|
|||
spaces_to_flush: usize,
|
||||
newlines_to_flush: usize,
|
||||
beginning_of_line: bool,
|
||||
line_indent: u16,
|
||||
flags: MigrationFlags,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MigrationFlags {
|
||||
pub(crate) snakify: bool,
|
||||
}
|
||||
|
||||
impl MigrationFlags {
|
||||
pub fn new(snakify: bool) -> Self {
|
||||
MigrationFlags { snakify }
|
||||
}
|
||||
|
||||
pub fn at_least_one_active(&self) -> bool {
|
||||
self.snakify
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Buf<'a> {
|
||||
pub fn new_in(arena: &'a Bump) -> Buf<'a> {
|
||||
pub fn new_in(arena: &'a Bump, flags: MigrationFlags) -> Buf<'a> {
|
||||
Buf {
|
||||
text: String::new_in(arena),
|
||||
line_indent: 0,
|
||||
spaces_to_flush: 0,
|
||||
newlines_to_flush: 0,
|
||||
beginning_of_line: true,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> MigrationFlags {
|
||||
self.flags
|
||||
}
|
||||
|
||||
pub fn as_str(&'a self) -> &'a str {
|
||||
self.text.as_str()
|
||||
}
|
||||
|
@ -40,11 +63,18 @@ impl<'a> Buf<'a> {
|
|||
|
||||
pub fn indent(&mut self, indent: u16) {
|
||||
if self.beginning_of_line {
|
||||
self.line_indent = indent;
|
||||
self.spaces_to_flush = indent as usize;
|
||||
}
|
||||
self.beginning_of_line = false;
|
||||
}
|
||||
|
||||
pub fn cur_line_indent(&self) -> u16 {
|
||||
debug_assert!(!self.beginning_of_line, "cur_line_indent before indent");
|
||||
self.line_indent
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn push(&mut self, ch: char) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
debug_assert!(
|
||||
|
@ -61,6 +91,7 @@ impl<'a> Buf<'a> {
|
|||
self.text.push(ch);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn push_str_allow_spaces(&mut self, s: &str) {
|
||||
debug_assert!(
|
||||
!self.beginning_of_line,
|
||||
|
@ -73,6 +104,7 @@ impl<'a> Buf<'a> {
|
|||
self.text.push_str(s);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn push_str(&mut self, s: &str) {
|
||||
debug_assert!(
|
||||
!self.beginning_of_line,
|
||||
|
@ -129,6 +161,12 @@ impl<'a> Buf<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ensure_ends_with_whitespace(&mut self) {
|
||||
if !self.text.is_empty() && self.newlines_to_flush == 0 && self.spaces_to_flush == 0 {
|
||||
self.spaces_to_flush = 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_spaces(&mut self) {
|
||||
for _ in 0..self.newlines_to_flush {
|
||||
self.text.push('\n');
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
use crate::annotation::{Formattable, Newlines, Parens};
|
||||
use crate::expr::{fmt_str_literal, format_sq_literal, is_str_multiline};
|
||||
use crate::expr::{
|
||||
expr_is_multiline, expr_lift_spaces_after, fmt_str_literal, format_sq_literal, is_str_multiline,
|
||||
};
|
||||
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Base, CommentOrNewline, Pattern, PatternAs};
|
||||
use bumpalo::Bump;
|
||||
use roc_parse::ast::{
|
||||
Base, CommentOrNewline, Pattern, PatternAs, Spaceable, Spaces, SpacesAfter, SpacesBefore,
|
||||
};
|
||||
use roc_parse::expr::merge_spaces;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
pub fn fmt_pattern<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, indent: u16, parens: Parens) {
|
||||
pattern.format_with_options(buf, parens, Newlines::No, indent);
|
||||
|
@ -54,7 +61,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
|
||||
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
|
||||
|
||||
Pattern::OptionalField(_, expr) => expr.is_multiline(),
|
||||
Pattern::OptionalField(_, expr) => expr_is_multiline(&expr.value, true),
|
||||
|
||||
Pattern::As(pattern, pattern_as) => pattern.is_multiline() || pattern_as.is_multiline(),
|
||||
Pattern::ListRest(opt_pattern_as) => match opt_pattern_as {
|
||||
|
@ -86,222 +93,347 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
|
||||
use self::Pattern::*;
|
||||
fn format_with_options(&self, buf: &mut Buf, parens: Parens, _newlines: Newlines, indent: u16) {
|
||||
fmt_pattern_inner(self, buf, parens, indent, self.is_multiline(), false);
|
||||
}
|
||||
}
|
||||
|
||||
match self {
|
||||
Identifier { ident: string } => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
Tag(name) | OpaqueRef(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
}
|
||||
Apply(loc_pattern, loc_arg_patterns) => {
|
||||
buf.indent(indent);
|
||||
// Sometimes, an Apply pattern needs parens around it.
|
||||
// In particular when an Apply's argument is itself an Apply (> 0) arguments
|
||||
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
|
||||
fn fmt_pattern_inner(
|
||||
pat: &Pattern<'_>,
|
||||
buf: &mut Buf,
|
||||
parens: Parens,
|
||||
|
||||
let indent_more = if self.is_multiline() {
|
||||
indent + INDENT
|
||||
indent: u16,
|
||||
outer_is_multiline: bool,
|
||||
force_newline_at_start: bool,
|
||||
) -> bool {
|
||||
let me = pattern_lift_spaces(buf.text.bump(), pat);
|
||||
|
||||
let mut was_multiline = me.item.is_multiline();
|
||||
|
||||
if !me.before.is_empty() {
|
||||
if !outer_is_multiline {
|
||||
was_multiline |= me.before.iter().any(|s| s.is_comment());
|
||||
fmt_comments_only(buf, me.before.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
was_multiline |= true;
|
||||
fmt_spaces(buf, me.before.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
if force_newline_at_start {
|
||||
buf.ensure_ends_with_newline();
|
||||
}
|
||||
|
||||
let is_multiline = me.item.is_multiline();
|
||||
|
||||
fmt_pattern_only(me, buf, indent, parens, is_multiline);
|
||||
|
||||
if !me.after.is_empty() {
|
||||
if starts_with_inline_comment(me.after.iter()) {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
if !outer_is_multiline {
|
||||
was_multiline |= me.before.iter().any(|s| s.is_comment());
|
||||
fmt_comments_only(buf, me.after.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
was_multiline |= true;
|
||||
fmt_spaces(buf, me.after.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
was_multiline
|
||||
}
|
||||
|
||||
fn fmt_pattern_only(
|
||||
me: Spaces<'_, Pattern<'_>>,
|
||||
buf: &mut Buf<'_>,
|
||||
indent: u16,
|
||||
parens: Parens,
|
||||
is_multiline: bool,
|
||||
) {
|
||||
match me.item {
|
||||
Pattern::Identifier { ident: string } => {
|
||||
buf.indent(indent);
|
||||
snakify_camel_ident(buf, string);
|
||||
}
|
||||
Pattern::Tag(name) | Pattern::OpaqueRef(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
}
|
||||
Pattern::Apply(loc_pattern, loc_arg_patterns) => {
|
||||
buf.indent(indent);
|
||||
// Sometimes, an Apply pattern needs parens around it.
|
||||
// In particular when an Apply's argument is itself an Apply (> 0) arguments
|
||||
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
|
||||
|
||||
let indent_more = if is_multiline {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
if parens {
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
let pat = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
|
||||
|
||||
if !pat.before.is_empty() {
|
||||
if !is_multiline {
|
||||
fmt_comments_only(buf, pat.before.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
if parens {
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
|
||||
for loc_arg in loc_arg_patterns.iter() {
|
||||
buf.spaces(1);
|
||||
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent_more);
|
||||
}
|
||||
|
||||
if parens {
|
||||
buf.push(')');
|
||||
fmt_spaces(buf, pat.before.iter(), indent);
|
||||
}
|
||||
}
|
||||
RecordDestructure(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("{");
|
||||
|
||||
if !loc_patterns.is_empty() {
|
||||
buf.spaces(1);
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
loc_pattern.format(buf, indent);
|
||||
fmt_pattern_inner(&pat.item, buf, Parens::InApply, indent, is_multiline, false);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
if !pat.after.is_empty() {
|
||||
if !is_multiline {
|
||||
fmt_comments_only(buf, pat.after.iter(), NewlineAt::Bottom, indent_more)
|
||||
} else {
|
||||
fmt_spaces(buf, pat.after.iter(), indent_more);
|
||||
}
|
||||
}
|
||||
|
||||
let mut add_newlines = false;
|
||||
|
||||
for loc_arg in loc_arg_patterns.iter() {
|
||||
buf.spaces(1);
|
||||
let was_multiline = fmt_pattern_inner(
|
||||
&loc_arg.value,
|
||||
buf,
|
||||
Parens::InApply,
|
||||
indent_more,
|
||||
is_multiline,
|
||||
add_newlines,
|
||||
);
|
||||
add_newlines |= was_multiline;
|
||||
}
|
||||
|
||||
if parens {
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Pattern::RecordDestructure(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("{");
|
||||
|
||||
if !loc_patterns.is_empty() {
|
||||
buf.spaces(1);
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
let item = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
|
||||
|
||||
if !item.before.is_empty() {
|
||||
if !is_multiline {
|
||||
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, item.before.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
fmt_pattern_inner(
|
||||
&item.item,
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
indent,
|
||||
is_multiline,
|
||||
false,
|
||||
);
|
||||
|
||||
let is_multiline = item.item.is_multiline();
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
}
|
||||
|
||||
if !item.after.is_empty() {
|
||||
if starts_with_inline_comment(item.after.iter()) {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
if !is_multiline {
|
||||
fmt_comments_only(buf, item.after.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, item.after.iter(), indent);
|
||||
}
|
||||
}
|
||||
if it.peek().is_some() {
|
||||
buf.ensure_ends_with_whitespace();
|
||||
}
|
||||
}
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str("}");
|
||||
}
|
||||
|
||||
Pattern::RequiredField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
snakify_camel_ident(buf, name);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
fmt_pattern_inner(
|
||||
&loc_pattern.value,
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
indent,
|
||||
is_multiline,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
Pattern::OptionalField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
snakify_camel_ident(buf, name);
|
||||
buf.push_str(" ?");
|
||||
buf.spaces(1);
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
|
||||
Pattern::NumLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
Pattern::NonBase10Literal {
|
||||
base,
|
||||
string,
|
||||
is_negative,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
if is_negative {
|
||||
buf.push('-');
|
||||
}
|
||||
|
||||
match base {
|
||||
Base::Hex => buf.push_str("0x"),
|
||||
Base::Octal => buf.push_str("0o"),
|
||||
Base::Binary => buf.push_str("0b"),
|
||||
Base::Decimal => { /* nothing */ }
|
||||
}
|
||||
|
||||
buf.push_str(string);
|
||||
}
|
||||
Pattern::FloatLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
Pattern::StrLiteral(literal) => fmt_str_literal(buf, literal, indent),
|
||||
Pattern::SingleQuote(string) => {
|
||||
buf.indent(indent);
|
||||
format_sq_literal(buf, string);
|
||||
}
|
||||
Pattern::Underscore(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
buf.push_str(name);
|
||||
}
|
||||
Pattern::Tuple(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("(");
|
||||
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
fmt_pattern_inner(
|
||||
&loc_pattern.value,
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
indent,
|
||||
is_multiline,
|
||||
false,
|
||||
);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.indent(indent);
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push_str("}");
|
||||
}
|
||||
|
||||
RequiredField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
buf.indent(indent);
|
||||
buf.push_str(")");
|
||||
}
|
||||
Pattern::List(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("[");
|
||||
|
||||
OptionalField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
buf.push_str(" ?");
|
||||
buf.spaces(1);
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
fmt_pattern_inner(
|
||||
&loc_pattern.value,
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
indent,
|
||||
is_multiline,
|
||||
false,
|
||||
);
|
||||
|
||||
&NumLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
&NonBase10Literal {
|
||||
base,
|
||||
string,
|
||||
is_negative,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
if is_negative {
|
||||
buf.push('-');
|
||||
}
|
||||
|
||||
match base {
|
||||
Base::Hex => buf.push_str("0x"),
|
||||
Base::Octal => buf.push_str("0o"),
|
||||
Base::Binary => buf.push_str("0b"),
|
||||
Base::Decimal => { /* nothing */ }
|
||||
}
|
||||
|
||||
buf.push_str(string);
|
||||
}
|
||||
&FloatLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
StrLiteral(literal) => fmt_str_literal(buf, *literal, indent),
|
||||
SingleQuote(string) => {
|
||||
buf.indent(indent);
|
||||
format_sq_literal(buf, string);
|
||||
}
|
||||
Underscore(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
buf.push_str(name);
|
||||
}
|
||||
Tuple(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("(");
|
||||
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
loc_pattern.format(buf, indent);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
|
||||
buf.push_str(")");
|
||||
}
|
||||
List(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("[");
|
||||
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
loc_pattern.format(buf, indent);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
|
||||
buf.push_str("]");
|
||||
}
|
||||
ListRest(opt_pattern_as) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("..");
|
||||
|
||||
if let Some((list_rest_spaces, pattern_as)) = opt_pattern_as {
|
||||
// these spaces "belong" to the `..`, which can never be multiline
|
||||
fmt_comments_only(buf, list_rest_spaces.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
pattern_as.format(buf, indent + INDENT);
|
||||
if it.peek().is_some() {
|
||||
buf.indent(indent);
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
|
||||
As(pattern, pattern_as) => {
|
||||
let needs_parens = parens == Parens::InAsPattern;
|
||||
buf.indent(indent);
|
||||
buf.push_str("]");
|
||||
}
|
||||
Pattern::ListRest(opt_pattern_as) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("..");
|
||||
|
||||
if needs_parens {
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
fmt_pattern(buf, &pattern.value, indent, parens);
|
||||
if let Some((list_rest_spaces, pattern_as)) = opt_pattern_as {
|
||||
// these spaces "belong" to the `..`, which can never be multiline
|
||||
fmt_comments_only(buf, list_rest_spaces.iter(), NewlineAt::Bottom, indent);
|
||||
|
||||
pattern_as.format(buf, indent + INDENT);
|
||||
|
||||
if needs_parens {
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Space
|
||||
SpaceBefore(sub_pattern, spaces) => {
|
||||
if !sub_pattern.is_multiline() {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
Pattern::As(pattern, pattern_as) => {
|
||||
let needs_parens = parens == Parens::InAsPattern;
|
||||
|
||||
sub_pattern.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
SpaceAfter(sub_pattern, spaces) => {
|
||||
sub_pattern.format_with_options(buf, parens, newlines, indent);
|
||||
|
||||
if starts_with_inline_comment(spaces.iter()) {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
if !sub_pattern.is_multiline() {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed
|
||||
Malformed(string) | MalformedIdent(string, _) => {
|
||||
if needs_parens {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
buf.push('(');
|
||||
}
|
||||
QualifiedIdentifier { module_name, ident } => {
|
||||
buf.indent(indent);
|
||||
if !module_name.is_empty() {
|
||||
buf.push_str(module_name);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
buf.push_str(ident);
|
||||
fmt_pattern(buf, &pattern.value, indent, parens);
|
||||
|
||||
pattern_as.format(buf, indent + INDENT);
|
||||
|
||||
if needs_parens {
|
||||
buf.indent(indent);
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
|
||||
Pattern::SpaceBefore(..) | Pattern::SpaceAfter(..) => {
|
||||
unreachable!("handled by lift_spaces")
|
||||
}
|
||||
|
||||
// Malformed
|
||||
Pattern::Malformed(string) | Pattern::MalformedIdent(string, _) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
Pattern::QualifiedIdentifier { module_name, ident } => {
|
||||
buf.indent(indent);
|
||||
if !module_name.is_empty() {
|
||||
buf.push_str(module_name);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
snakify_camel_ident(buf, ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
|
||||
pub fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
|
||||
spaces: I,
|
||||
) -> bool {
|
||||
matches!(
|
||||
|
@ -309,3 +441,192 @@ fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a
|
|||
Some(CommentOrNewline::LineComment(_))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pattern_lift_spaces<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
pat: &Pattern<'b>,
|
||||
) -> Spaces<'a, Pattern<'a>> {
|
||||
match pat {
|
||||
Pattern::Apply(func, args) => {
|
||||
let func_lifted = pattern_lift_spaces(arena, &func.value);
|
||||
let args = arena.alloc_slice_copy(args);
|
||||
let (before, func, after) = if let Some(last) = args.last_mut() {
|
||||
let last_lifted = pattern_lift_spaces(arena, &last.value);
|
||||
if last_lifted.before.is_empty() {
|
||||
*last = Loc::at(last.region, last_lifted.item)
|
||||
} else {
|
||||
*last = Loc::at(
|
||||
last.region,
|
||||
Pattern::SpaceBefore(arena.alloc(last_lifted.item), last_lifted.before),
|
||||
);
|
||||
}
|
||||
|
||||
let f = if func_lifted.after.is_empty() {
|
||||
func_lifted.item
|
||||
} else {
|
||||
Pattern::SpaceAfter(arena.alloc(func_lifted.item), func_lifted.after)
|
||||
};
|
||||
|
||||
(
|
||||
func_lifted.before,
|
||||
Loc::at(func.region, f),
|
||||
last_lifted.after,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
func_lifted.before,
|
||||
Loc::at(func.region, func_lifted.item),
|
||||
func_lifted.after,
|
||||
)
|
||||
};
|
||||
Spaces {
|
||||
before,
|
||||
item: Pattern::Apply(arena.alloc(func), args),
|
||||
after,
|
||||
}
|
||||
}
|
||||
Pattern::OptionalField(name, expr) => {
|
||||
let lifted = expr_lift_spaces_after(Parens::NotNeeded, arena, &expr.value);
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: Pattern::OptionalField(name, arena.alloc(Loc::at(expr.region, lifted.item))),
|
||||
after: lifted.after,
|
||||
}
|
||||
}
|
||||
Pattern::RequiredField(name, pat) => {
|
||||
let lifted = pattern_lift_spaces_after(arena, &pat.value);
|
||||
Spaces {
|
||||
before: &[],
|
||||
item: Pattern::RequiredField(name, arena.alloc(Loc::at(pat.region, lifted.item))),
|
||||
after: lifted.after,
|
||||
}
|
||||
}
|
||||
Pattern::SpaceBefore(expr, spaces) => {
|
||||
let mut inner = pattern_lift_spaces(arena, expr);
|
||||
inner.before = merge_spaces(arena, spaces, inner.before);
|
||||
inner
|
||||
}
|
||||
Pattern::SpaceAfter(expr, spaces) => {
|
||||
let mut inner = pattern_lift_spaces(arena, expr);
|
||||
inner.after = merge_spaces(arena, inner.after, spaces);
|
||||
inner
|
||||
}
|
||||
_ => Spaces {
|
||||
before: &[],
|
||||
item: *pat,
|
||||
after: &[],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pattern_lift_spaces_before<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
pat: &Pattern<'b>,
|
||||
) -> SpacesBefore<'a, Pattern<'a>> {
|
||||
let lifted = pattern_lift_spaces(arena, pat);
|
||||
SpacesBefore {
|
||||
before: lifted.before,
|
||||
item: lifted.item.maybe_after(arena, lifted.after),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pattern_lift_spaces_after<'a, 'b: 'a>(
|
||||
arena: &'a Bump,
|
||||
pat: &Pattern<'b>,
|
||||
) -> SpacesAfter<'a, Pattern<'a>> {
|
||||
let lifted = pattern_lift_spaces(arena, pat);
|
||||
SpacesAfter {
|
||||
item: lifted.item.maybe_before(arena, lifted.before),
|
||||
after: lifted.after,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert camelCase identifier to snake case
|
||||
fn snakify_camel_ident(buf: &mut Buf, string: &str) {
|
||||
let chars: Vec<char> = string.chars().collect();
|
||||
if !buf.flags().snakify || (string.contains('_') && !string.ends_with('_')) {
|
||||
buf.push_str(string);
|
||||
return;
|
||||
}
|
||||
let mut index = 0;
|
||||
let len = chars.len();
|
||||
|
||||
while index < len {
|
||||
let prev = if index == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(chars[index - 1])
|
||||
};
|
||||
let c = chars[index];
|
||||
let next = chars.get(index + 1);
|
||||
let boundary = match (prev, c, next) {
|
||||
// LUU, LUN, and LUL (simplified to LU_)
|
||||
(Some(p), curr, _) if !p.is_ascii_uppercase() && curr.is_ascii_uppercase() => true,
|
||||
// UUL
|
||||
(Some(p), curr, Some(n))
|
||||
if p.is_ascii_uppercase()
|
||||
&& curr.is_ascii_uppercase()
|
||||
&& n.is_ascii_lowercase() =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
// those are boundary transitions - should push _ and curr
|
||||
if boundary {
|
||||
buf.push('_');
|
||||
}
|
||||
buf.push(c.to_ascii_lowercase());
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod snakify_test {
|
||||
use bumpalo::Bump;
|
||||
|
||||
use super::snakify_camel_ident;
|
||||
use crate::{Buf, MigrationFlags};
|
||||
|
||||
fn check_snakify(arena: &Bump, original: &str) -> String {
|
||||
let flags = MigrationFlags::new(true);
|
||||
let mut buf = Buf::new_in(arena, flags);
|
||||
buf.indent(0);
|
||||
snakify_camel_ident(&mut buf, original);
|
||||
buf.text.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snakify_camel_ident() {
|
||||
let arena = Bump::new();
|
||||
assert_eq!(check_snakify(&arena, "A"), "a");
|
||||
assert_eq!(check_snakify(&arena, "Ba"), "ba");
|
||||
assert_eq!(check_snakify(&arena, "aB"), "a_b");
|
||||
assert_eq!(check_snakify(&arena, "aBa"), "a_ba");
|
||||
assert_eq!(check_snakify(&arena, "mBB"), "m_bb");
|
||||
assert_eq!(check_snakify(&arena, "NbA"), "nb_a");
|
||||
assert_eq!(check_snakify(&arena, "doIT"), "do_it");
|
||||
assert_eq!(check_snakify(&arena, "ROC"), "roc");
|
||||
assert_eq!(
|
||||
check_snakify(&arena, "someHTTPRequest"),
|
||||
"some_http_request"
|
||||
);
|
||||
assert_eq!(check_snakify(&arena, "usingXML"), "using_xml");
|
||||
assert_eq!(check_snakify(&arena, "some123"), "some123");
|
||||
assert_eq!(
|
||||
check_snakify(&arena, "theHTTPStatus404"),
|
||||
"the_http_status404"
|
||||
);
|
||||
assert_eq!(
|
||||
check_snakify(&arena, "inThe99thPercentile"),
|
||||
"in_the99th_percentile"
|
||||
);
|
||||
assert_eq!(
|
||||
check_snakify(&arena, "all400SeriesErrorCodes"),
|
||||
"all400_series_error_codes",
|
||||
);
|
||||
assert_eq!(check_snakify(&arena, "number4Yellow"), "number4_yellow");
|
||||
assert_eq!(check_snakify(&arena, "useCases4Cobol"), "use_cases4_cobol");
|
||||
assert_eq!(check_snakify(&arena, "c3PO"), "c3_po")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,43 @@ pub fn fmt_default_newline(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u
|
|||
}
|
||||
}
|
||||
|
||||
pub enum SpacesNewlineMode {
|
||||
Normal,
|
||||
SkipNewlinesAtStart,
|
||||
SkipNewlinesAtEnd,
|
||||
SkipNewlinesAtBoth,
|
||||
}
|
||||
|
||||
pub fn fmt_spaces_with_newline_mode(
|
||||
buf: &mut Buf<'_>,
|
||||
mut spaces: &[CommentOrNewline<'_>],
|
||||
indent: u16,
|
||||
mode: SpacesNewlineMode,
|
||||
) {
|
||||
if matches!(
|
||||
mode,
|
||||
SpacesNewlineMode::SkipNewlinesAtStart | SpacesNewlineMode::SkipNewlinesAtBoth
|
||||
) {
|
||||
let skip_count = spaces
|
||||
.iter()
|
||||
.take_while(|s| *s == &CommentOrNewline::Newline)
|
||||
.count();
|
||||
spaces = &spaces[skip_count..];
|
||||
}
|
||||
if matches!(
|
||||
mode,
|
||||
SpacesNewlineMode::SkipNewlinesAtEnd | SpacesNewlineMode::SkipNewlinesAtBoth
|
||||
) {
|
||||
let skip_count = spaces
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|s| *s == &CommentOrNewline::Newline)
|
||||
.count();
|
||||
spaces = &spaces[..spaces.len() - skip_count];
|
||||
}
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
/// Like fmt_spaces, but disallows two consecutive newlines.
|
||||
pub fn fmt_spaces_no_blank_lines<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
|
||||
where
|
||||
|
|
|
@ -19,7 +19,7 @@ macro_rules! run_jit_function_raw {
|
|||
let result = main();
|
||||
|
||||
if !$errors.is_empty() {
|
||||
dbg!(&$errors);
|
||||
eprintln!("{:?}", &$errors);
|
||||
|
||||
assert_eq!(
|
||||
$errors,
|
||||
|
|
|
@ -12,8 +12,8 @@ use crate::llvm::refcounting::{
|
|||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::types::{BasicType, BasicTypeEnum, StructType};
|
||||
use inkwell::values::{
|
||||
BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue,
|
||||
PointerValue, StructValue,
|
||||
BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue, FunctionValue,
|
||||
InstructionValue, IntValue, PointerValue, StructValue,
|
||||
};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_error_macros::internal_error;
|
||||
|
@ -726,13 +726,18 @@ pub fn build_compare_wrapper<'a, 'ctx>(
|
|||
"load_opaque",
|
||||
);
|
||||
|
||||
let closure_data =
|
||||
env.builder
|
||||
.new_build_load(closure_type, closure_cast, "load_opaque");
|
||||
let closure_data: BasicMetadataValueEnum =
|
||||
if layout_interner.is_passed_by_reference(closure_data_repr) {
|
||||
closure_cast.into()
|
||||
} else {
|
||||
env.builder
|
||||
.new_build_load(closure_type, closure_cast, "load_opaque")
|
||||
.into()
|
||||
};
|
||||
|
||||
env.arena
|
||||
.alloc([value1.into(), value2.into(), closure_data.into()])
|
||||
as &[_]
|
||||
.alloc([value1.into(), value2.into(), closure_data])
|
||||
as &[BasicMetadataValueEnum]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1147,15 +1147,37 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
|||
)
|
||||
}
|
||||
NumIntCast => {
|
||||
arguments!(arg);
|
||||
arguments_with_layouts!((arg, arg_layout));
|
||||
|
||||
let to = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
|
||||
.into_int_type();
|
||||
let to_signed = intwidth_from_layout(layout).is_signed();
|
||||
let from_signed = intwidth_from_layout(arg_layout).is_signed();
|
||||
let extend = intwidth_from_layout(layout).stack_size()
|
||||
> intwidth_from_layout(arg_layout).stack_size();
|
||||
//Examples given with sizes of 32, 16, and 8
|
||||
let result = match (from_signed, to_signed, extend) {
|
||||
//I16 -> I32
|
||||
(true, true, true) => {
|
||||
env.builder
|
||||
.build_int_s_extend(arg.into_int_value(), to, "inc_cast")
|
||||
}
|
||||
//U16 -> X32
|
||||
(false, _, true) => {
|
||||
env.builder
|
||||
.build_int_z_extend(arg.into_int_value(), to, "inc_cast")
|
||||
},
|
||||
//I16 -> U32
|
||||
(true,false,true)
|
||||
//Any case where it is not an extension, also perhaps warn here?
|
||||
| (_, _, false) => {
|
||||
Ok(env.builder
|
||||
.new_build_int_cast_sign_flag(arg.into_int_value(), to, to_signed, "inc_cast"))
|
||||
}
|
||||
};
|
||||
|
||||
env.builder
|
||||
.new_build_int_cast_sign_flag(arg.into_int_value(), to, to_signed, "inc_cast")
|
||||
.into()
|
||||
let Ok(value) = result else { todo!() };
|
||||
value.into()
|
||||
}
|
||||
NumToFloatCast => {
|
||||
arguments_with_layouts!((arg, arg_layout));
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -6019,25 +6021,6 @@ All branches in an `if` must have the same type!
|
|||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
closure_underscore_ident,
|
||||
indoc!(
|
||||
r"
|
||||
\the_answer -> 100
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── NAMING PROBLEM in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
I am trying to parse an identifier here:
|
||||
|
||||
4│ \the_answer -> 100
|
||||
^
|
||||
|
||||
Underscores are not allowed in identifiers. Use camelCase instead!
|
||||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
#[ignore]
|
||||
double_binop,
|
||||
|
@ -6626,7 +6609,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 +10241,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 +10398,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 +10434,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
|
||||
^^
|
||||
|
||||
|
@ -10697,26 +10683,26 @@ All branches in an `if` must have the same type!
|
|||
);
|
||||
|
||||
test_report!(
|
||||
underscore_in_middle_of_identifier,
|
||||
call_with_declared_identifier_with_more_than_one_underscore,
|
||||
indoc!(
|
||||
r"
|
||||
f = \x, y, z -> x + y + z
|
||||
f__arg = \x, y, z -> x + y + z
|
||||
|
||||
\a, _b -> f a var_name 1
|
||||
\a, b -> f__arg a b 1
|
||||
"
|
||||
),
|
||||
|golden| pretty_assertions::assert_eq!(
|
||||
golden,
|
||||
indoc!(
|
||||
r"
|
||||
── SYNTAX PROBLEM in /code/proj/Main.roc ───────────────────────────────────────
|
||||
r"── NAMING PROBLEM in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
Underscores are not allowed in identifier names:
|
||||
I am trying to parse an identifier here:
|
||||
|
||||
6│ \a, _b -> f a var_name 1
|
||||
^^^^^^^^
|
||||
4│ f__arg = \x, y, z -> x + y + z
|
||||
^^^^^^
|
||||
|
||||
I recommend using camelCase. It's the standard style in Roc code!
|
||||
Snake case is allowed here, but only a single consecutive underscore
|
||||
should be used.
|
||||
"
|
||||
),
|
||||
)
|
||||
|
@ -14556,7 +14542,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,27 +14620,121 @@ All branches in an `if` must have the same type!
|
|||
myFunction 3
|
||||
"#
|
||||
),
|
||||
@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│ x
|
||||
|
||||
This returns a value of type:
|
||||
|
||||
Str
|
||||
|
||||
But I expected the function to have return type:
|
||||
|
||||
Num *
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
try_in_bare_statement,
|
||||
indoc!(
|
||||
r#"
|
||||
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
validateNum = \num ->
|
||||
if num > 5 then
|
||||
Ok {}
|
||||
else
|
||||
Err TooBig
|
||||
|
||||
main! = \{} ->
|
||||
Effect.putLine! "hello"
|
||||
|
||||
# this returns {}, so it's ignored
|
||||
try validateNum 10
|
||||
|
||||
# this returns a value, so we are incorrectly
|
||||
# dropping the parsed value
|
||||
try List.get [1, 2, 3] 5
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This `return` statement doesn't match the return type of its enclosing
|
||||
function:
|
||||
|
||||
5│ if x == 5 then
|
||||
6│> return "abc"
|
||||
7│ else
|
||||
8│ x
|
||||
|
||||
This returns a value of type:
|
||||
|
||||
Str
|
||||
|
||||
But I expected the function to have return type:
|
||||
|
||||
── IGNORED RESULT in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
The result of this expression is ignored:
|
||||
|
||||
19│ try List.get [1, 2, 3] 5
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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!(
|
||||
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!(
|
||||
|
@ -14668,28 +14748,200 @@ All branches in an `if` must have the same type!
|
|||
myFunction 3
|
||||
"#
|
||||
),
|
||||
@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!(
|
||||
try_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
|
||||
try List.get [1, 2, 3] 5
|
||||
|
||||
# ignored, OK
|
||||
_ = try List.get [1, 2, 3] 5
|
||||
_ignored = try List.get [1, 2, 3] 5
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
── IGNORED RESULT in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
This `return` statement doesn't match the return type of its enclosing
|
||||
function:
|
||||
The result of this expression is ignored:
|
||||
|
||||
5│ if x == 5 then
|
||||
6│ return "abc"
|
||||
7│ else
|
||||
8│ return 123
|
||||
^^^^^^^^^^
|
||||
9│ try List.get [1, 2, 3] 5
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This returns a value of type:
|
||||
Standalone statements are required to produce an empty record, but the
|
||||
type of this one is:
|
||||
|
||||
Num *
|
||||
|
||||
But I expected the function to have return type:
|
||||
If you still want to ignore it, assign it to `_`, like this:
|
||||
|
||||
Str
|
||||
_ = File.delete! "data.json"
|
||||
"###
|
||||
);
|
||||
|
||||
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!(
|
||||
r#"
|
||||
app [main!] { pf: platform "../../../../../crates/cli/tests/test-projects/test-platform-effects-zig/main.roc" }
|
||||
|
||||
import pf.Effect
|
||||
|
||||
main! = \{} ->
|
||||
Effect.putLine! "hello"
|
||||
|
||||
Num.toStr 123
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
── IGNORED RESULT in /code/proj/Main.roc ───────────────────────────────────────
|
||||
|
||||
The result of this call to `Num.toStr` is ignored:
|
||||
|
||||
8│ Num.toStr 123
|
||||
^^^^^^^^^
|
||||
|
||||
Standalone statements are required to produce an empty record, but the
|
||||
type of this one is:
|
||||
|
||||
Str
|
||||
|
||||
If you still want to ignore it, assign it to `_`, like this:
|
||||
|
||||
_ = File.delete! "data.json"
|
||||
|
||||
── LEFTOVER STATEMENT in /code/proj/Main.roc ───────────────────────────────────
|
||||
|
||||
This statement does not produce any effects:
|
||||
|
||||
8│ Num.toStr 123
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Standalone statements are only useful if they call effectful
|
||||
functions.
|
||||
|
||||
Did you forget to use its result? If not, feel free to remove it.
|
||||
"#
|
||||
);
|
||||
|
||||
test_report!(
|
||||
no_early_return_in_ignored_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"
|
||||
|
||||
_ignored = Num.toStr 123
|
||||
|
||||
Ok {}
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
── UNNECESSARY DEFINITION in /code/proj/Main.roc ───────────────────────────────
|
||||
|
||||
This assignment doesn't introduce any new variables:
|
||||
|
||||
8│ _ignored = Num.toStr 123
|
||||
^^^^^^^^
|
||||
|
||||
Since it doesn't call any effectful functions, this assignment cannot
|
||||
affect the program's behavior. If you don't need to use the value on
|
||||
the right-hand side, consider removing the assignment.
|
||||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
mismatch_early_return_annotated_function,
|
||||
indoc!(
|
||||
|
@ -14726,6 +14978,225 @@ All branches in an `if` must have the same type!
|
|||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
function_with_early_return_generalizes,
|
||||
indoc!(
|
||||
r#"
|
||||
parseItemsWith = \parser ->
|
||||
when List.mapTry ["123", "456"] parser is
|
||||
Ok ok -> Ok ok
|
||||
Err err ->
|
||||
return Err err
|
||||
|
||||
u64Nums = parseItemsWith Str.toU64
|
||||
u8Nums = parseItemsWith Str.toU8
|
||||
|
||||
"$(Inspect.toStr u64Nums) $(Inspect.toStr u8Nums)"
|
||||
"#
|
||||
),
|
||||
@"" // no errors
|
||||
);
|
||||
|
||||
test_report!(
|
||||
keyword_try_with_non_result_target,
|
||||
indoc!(
|
||||
r#"
|
||||
invalidTry = \{} ->
|
||||
nonResult = "abc"
|
||||
x = try nonResult
|
||||
|
||||
Ok (x * 2)
|
||||
|
||||
invalidTry {}
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
── INVALID TRY TARGET in /code/proj/Main.roc ───────────────────────────────────
|
||||
|
||||
This expression cannot be used as a `try` target:
|
||||
|
||||
6│ x = try 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?
|
||||
"
|
||||
);
|
||||
|
||||
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?
|
||||
"
|
||||
);
|
||||
|
||||
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!(
|
||||
keyword_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!(
|
||||
keyword_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!(
|
||||
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!(
|
||||
leftover_statement,
|
||||
indoc!(
|
||||
|
|
|
@ -373,6 +373,17 @@ 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: _,
|
||||
kind: _,
|
||||
} => {
|
||||
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,86 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Try {
|
||||
result_expr,
|
||||
result_var,
|
||||
return_var,
|
||||
ok_payload_var,
|
||||
err_payload_var,
|
||||
err_ext_var,
|
||||
kind: _,
|
||||
} => {
|
||||
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,
|
||||
|
|
|
@ -28,12 +28,26 @@ pub struct Spaces<'a, T> {
|
|||
pub after: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> ExtractSpaces<'a> for Spaces<'a, T> {
|
||||
type Item = T;
|
||||
|
||||
fn extract_spaces(&self) -> Spaces<'a, T> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct SpacesBefore<'a, T> {
|
||||
pub before: &'a [CommentOrNewline<'a>],
|
||||
pub item: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct SpacesAfter<'a, T> {
|
||||
pub after: &'a [CommentOrNewline<'a>],
|
||||
pub item: T,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Spaced<'a, T> {
|
||||
Item(T),
|
||||
|
@ -401,6 +415,12 @@ pub enum TryTarget {
|
|||
Result,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ResultTryKind {
|
||||
KeywordPrefix,
|
||||
OperatorSuffix,
|
||||
}
|
||||
|
||||
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
||||
///
|
||||
/// 1. It uses Bump::alloc for all allocations, which returns a reference.
|
||||
|
@ -491,13 +511,19 @@ pub enum Expr<'a> {
|
|||
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
|
||||
|
||||
Dbg,
|
||||
DbgStmt(&'a Loc<Expr<'a>>, &'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>>),
|
||||
DbgStmt {
|
||||
first: &'a Loc<Expr<'a>>,
|
||||
extra_args: &'a [&'a Loc<Expr<'a>>],
|
||||
continuation: &'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>>, ResultTryKind),
|
||||
|
||||
// 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(...), ...)
|
||||
|
@ -671,9 +697,18 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
|
|||
Expr::OpaqueRef(_) => false,
|
||||
Expr::Backpassing(_, _, _) => false, // TODO: we might want to check this?
|
||||
Expr::Dbg => false,
|
||||
Expr::DbgStmt(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
|
||||
Expr::DbgStmt {
|
||||
first,
|
||||
extra_args,
|
||||
continuation,
|
||||
} => {
|
||||
is_expr_suffixed(&first.value)
|
||||
|| extra_args.iter().any(|a| is_expr_suffixed(&a.value))
|
||||
|| is_expr_suffixed(&continuation.value)
|
||||
}
|
||||
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))
|
||||
|
@ -930,16 +965,26 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
|
|||
expr_stack.push(&a.value);
|
||||
expr_stack.push(&b.value);
|
||||
}
|
||||
DbgStmt(condition, cont) => {
|
||||
DbgStmt {
|
||||
first,
|
||||
extra_args,
|
||||
continuation,
|
||||
} => {
|
||||
expr_stack.reserve(2);
|
||||
expr_stack.push(&condition.value);
|
||||
expr_stack.push(&cont.value);
|
||||
expr_stack.push(&first.value);
|
||||
for arg in extra_args.iter() {
|
||||
expr_stack.push(&arg.value);
|
||||
}
|
||||
expr_stack.push(&continuation.value);
|
||||
}
|
||||
LowLevelDbg(_, condition, cont) => {
|
||||
expr_stack.reserve(2);
|
||||
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);
|
||||
|
@ -2311,6 +2356,7 @@ impl_extract_spaces!(Tag);
|
|||
impl_extract_spaces!(AssignedField<T>);
|
||||
impl_extract_spaces!(TypeAnnotation);
|
||||
impl_extract_spaces!(ImplementsAbility);
|
||||
impl_extract_spaces!(ImplementsAbilities);
|
||||
|
||||
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
|
||||
type Item = T;
|
||||
|
@ -2476,9 +2522,10 @@ impl<'a> Malformed for Expr<'a> {
|
|||
Defs(defs, body) => defs.is_malformed() || body.is_malformed(),
|
||||
Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(),
|
||||
Dbg => false,
|
||||
DbgStmt(condition, continuation) => condition.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(),
|
||||
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(),
|
||||
|
|
|
@ -182,26 +182,25 @@ fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Suffix<'a>>, EExpr
|
|||
/// pattern later
|
||||
fn loc_term_or_underscore_or_conditional<'a>(
|
||||
options: ExprParseOptions,
|
||||
allow_conditional: bool,
|
||||
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
||||
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
||||
if allow_conditional {
|
||||
match loc_conditional(options).parse(arena, state.clone(), min_indent) {
|
||||
Ok((_, expr, state)) => return Ok((MadeProgress, expr, state)),
|
||||
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
|
||||
Err((NoProgress, _)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
loc_term_or_underscore(options).parse(arena, state, min_indent)
|
||||
}
|
||||
}
|
||||
fn loc_conditional<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
||||
one_of!(
|
||||
loc_expr_in_parens_etc_help(),
|
||||
loc(specialize_err(EExpr::If, if_expr_help(options))),
|
||||
loc(specialize_err(EExpr::When, when::when_expr_help(options))),
|
||||
loc(specialize_err(EExpr::Str, string_like_literal_help())),
|
||||
loc(specialize_err(
|
||||
EExpr::Number,
|
||||
positive_number_literal_help()
|
||||
)),
|
||||
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
||||
loc(crash_kw()),
|
||||
loc(specialize_err(EExpr::Dbg, dbg_kw())),
|
||||
loc(try_kw()),
|
||||
loc(underscore_expression()),
|
||||
loc(record_literal_help()),
|
||||
loc(specialize_err(EExpr::List, list_literal_help())),
|
||||
ident_seq(),
|
||||
)
|
||||
.trace("term_or_underscore_or_conditional")
|
||||
}
|
||||
|
||||
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
|
||||
|
@ -217,6 +216,7 @@ fn loc_term_or_underscore<'a>(
|
|||
positive_number_literal_help()
|
||||
)),
|
||||
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
||||
loc(crash_kw()),
|
||||
loc(specialize_err(EExpr::Dbg, dbg_kw())),
|
||||
loc(try_kw()),
|
||||
loc(underscore_expression()),
|
||||
|
@ -294,12 +294,21 @@ fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|||
|
||||
fn loc_possibly_negative_or_negated_term<'a>(
|
||||
options: ExprParseOptions,
|
||||
allow_negate: bool,
|
||||
allow_conditional: bool,
|
||||
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
||||
let parse_unary_negate = move |arena, state: State<'a>, min_indent: u32| {
|
||||
let initial = state.clone();
|
||||
|
||||
let (_, (loc_op, loc_expr), state) =
|
||||
and(loc(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
|
||||
if !allow_negate {
|
||||
return Err((NoProgress, EExpr::UnaryNegate(state.pos())));
|
||||
}
|
||||
|
||||
let (_, (loc_op, loc_expr), state) = and(
|
||||
loc(unary_negate()),
|
||||
loc_possibly_negative_or_negated_term(options, true, false),
|
||||
)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
|
||||
|
||||
|
@ -307,20 +316,26 @@ fn loc_possibly_negative_or_negated_term<'a>(
|
|||
};
|
||||
|
||||
one_of![
|
||||
parse_unary_negate,
|
||||
parse_unary_negate.trace("negate_expr"),
|
||||
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
|
||||
loc(specialize_err(EExpr::Number, number_literal_help())),
|
||||
// loc(specialize_err(EExpr::Number, number_literal_help())),
|
||||
loc(map_with_arena(
|
||||
and(
|
||||
loc(byte(b'!', EExpr::Start)),
|
||||
space0_before_e(loc_term(options), EExpr::IndentStart)
|
||||
loc(unary_not()).trace("not"),
|
||||
space0_before_e(
|
||||
loc_possibly_negative_or_negated_term(options, true, false),
|
||||
EExpr::IndentStart
|
||||
)
|
||||
.trace("not_expr")
|
||||
),
|
||||
|arena: &'a Bump, (loc_op, loc_expr): (Loc<_>, _)| {
|
||||
Expr::UnaryOp(arena.alloc(loc_expr), Loc::at(loc_op.region, UnaryOp::Not))
|
||||
}
|
||||
)),
|
||||
loc_term_or_underscore_or_conditional(options)
|
||||
))
|
||||
.trace("not_expr"),
|
||||
loc_term_or_underscore_or_conditional(options, allow_conditional)
|
||||
]
|
||||
.trace("loc_possibly_negative_or_negated_term")
|
||||
}
|
||||
|
||||
fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|
||||
|
@ -328,18 +343,22 @@ fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|
|||
}
|
||||
|
||||
fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
||||
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| {
|
||||
move |_arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
||||
// a minus is unary iff
|
||||
//
|
||||
// - it is preceded by whitespace (spaces, newlines, comments)
|
||||
// - it is not followed by whitespace
|
||||
let followed_by_whitespace = state
|
||||
// - it is not followed by >, making ->
|
||||
let followed_by_illegal_char = state
|
||||
.bytes()
|
||||
.get(1)
|
||||
.map(|c| c.is_ascii_whitespace() || *c == b'#')
|
||||
.map(|c| c.is_ascii_whitespace() || *c == b'#' || *c == b'>')
|
||||
.unwrap_or(false);
|
||||
|
||||
if state.bytes().starts_with(b"-") && !followed_by_whitespace {
|
||||
if state.bytes().starts_with(b"-")
|
||||
&& !followed_by_illegal_char
|
||||
&& state.column() >= min_indent
|
||||
{
|
||||
// the negate is only unary if it is not followed by whitespace
|
||||
let state = state.advance(1);
|
||||
Ok((MadeProgress, (), state))
|
||||
|
@ -350,6 +369,19 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
|||
}
|
||||
}
|
||||
|
||||
fn unary_not<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
||||
move |_arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
||||
let followed_by_equals = state.bytes().get(1).map(|c| *c == b'=').unwrap_or(false);
|
||||
|
||||
if state.bytes().starts_with(b"!") && !followed_by_equals && state.column() >= min_indent {
|
||||
let state = state.advance(1);
|
||||
Ok((MadeProgress, (), state))
|
||||
} else {
|
||||
Err((NoProgress, EExpr::UnaryNot(state.pos())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point for parsing an expression.
|
||||
fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
||||
one_of![
|
||||
|
@ -378,10 +410,11 @@ fn parse_expr_operator_chain<'a>(
|
|||
) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
||||
let line_indent = state.line_indent();
|
||||
|
||||
let (_, expr, state) =
|
||||
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
||||
let (_, expr, state) = loc_possibly_negative_or_negated_term(options, true, true)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let mut initial_state = state.clone();
|
||||
let mut end = state.pos();
|
||||
|
||||
let (spaces_before_op, state) =
|
||||
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
||||
|
@ -402,10 +435,13 @@ fn parse_expr_operator_chain<'a>(
|
|||
let call_min_indent = line_indent + 1;
|
||||
|
||||
loop {
|
||||
let allow_negate = state.pos() > end;
|
||||
let parser = skip_first(
|
||||
crate::blankspace::check_indent(EExpr::IndentEnd),
|
||||
loc_term_or_underscore(options),
|
||||
);
|
||||
loc_possibly_negative_or_negated_term(options, allow_negate, false),
|
||||
)
|
||||
.trace("term_or_underscore");
|
||||
end = state.pos();
|
||||
match parser.parse(arena, state.clone(), call_min_indent) {
|
||||
Err((MadeProgress, f)) => return Err((MadeProgress, f)),
|
||||
Err((NoProgress, _)) => {
|
||||
|
@ -459,7 +495,7 @@ fn parse_expr_after_apply<'a>(
|
|||
before_op: State<'a>,
|
||||
initial_state: State<'a>,
|
||||
) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
||||
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
|
||||
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
|
||||
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
||||
Ok((_, loc_op, state)) => {
|
||||
expr_state.consume_spaces(arena);
|
||||
|
@ -572,11 +608,11 @@ fn parse_stmt_operator_chain<'a>(
|
|||
) -> Result<(Progress, Stmt<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
||||
let line_indent = state.line_indent();
|
||||
|
||||
let (_, expr, state) =
|
||||
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
||||
let (_, expr, state) = loc_possibly_negative_or_negated_term(options, true, true)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let mut initial_state = state.clone();
|
||||
let end = state.pos();
|
||||
let mut end = state.pos();
|
||||
|
||||
let (spaces_before_op, state) =
|
||||
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
||||
|
@ -597,10 +633,12 @@ fn parse_stmt_operator_chain<'a>(
|
|||
let call_min_indent = line_indent + 1;
|
||||
|
||||
loop {
|
||||
let allow_negate = state.pos() > end;
|
||||
let parser = skip_first(
|
||||
crate::blankspace::check_indent(EExpr::IndentEnd),
|
||||
loc_term_or_underscore(options),
|
||||
loc_possibly_negative_or_negated_term(options, allow_negate, false),
|
||||
);
|
||||
end = state.pos();
|
||||
match parser.parse(arena, state.clone(), call_min_indent) {
|
||||
Err((MadeProgress, f)) => return Err((MadeProgress, f)),
|
||||
Ok((
|
||||
|
@ -845,13 +883,13 @@ fn numeric_negate_expression<'a, T>(
|
|||
let region = Region::new(start, expr.region.end());
|
||||
|
||||
let new_expr = match expr.value {
|
||||
Expr::Num(string) => {
|
||||
Expr::Num(string) if !string.starts_with('-') => {
|
||||
let new_string =
|
||||
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
|
||||
|
||||
Expr::Num(new_string)
|
||||
}
|
||||
Expr::Float(string) => {
|
||||
Expr::Float(string) if !string.starts_with('-') => {
|
||||
let new_string =
|
||||
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
|
||||
|
||||
|
@ -860,11 +898,11 @@ fn numeric_negate_expression<'a, T>(
|
|||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
is_negative: false,
|
||||
} => {
|
||||
// don't include the minus sign here; it will not be parsed right
|
||||
Expr::NonBase10Int {
|
||||
is_negative: !is_negative,
|
||||
is_negative: true,
|
||||
string,
|
||||
base,
|
||||
}
|
||||
|
@ -1151,9 +1189,7 @@ fn parse_stmt_alias_or_opaque<'a>(
|
|||
AliasOrOpaque::Alias => {
|
||||
let (_, signature, state) = alias_signature().parse(arena, state, min_indent)?;
|
||||
|
||||
// TODO: this code used to be broken and it dropped the spaces after the operator.
|
||||
// The formatter is not expecting this, so let's keep it as is for now.
|
||||
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
||||
let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
||||
|
||||
let header = TypeHeader {
|
||||
name: Loc::at(expr.region, name),
|
||||
|
@ -1172,9 +1208,7 @@ fn parse_stmt_alias_or_opaque<'a>(
|
|||
let (_, (signature, derived), state) =
|
||||
opaque_signature().parse(arena, state, indented_more)?;
|
||||
|
||||
// TODO: this code used to be broken and it dropped the spaces after the operator.
|
||||
// The formatter is not expecting this, so let's keep it as is for now.
|
||||
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
||||
let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
||||
|
||||
let header = TypeHeader {
|
||||
name: Loc::at(expr.region, name),
|
||||
|
@ -1560,10 +1594,10 @@ fn parse_after_binop<'a>(
|
|||
mut expr_state: ExprState<'a>,
|
||||
loc_op: Loc<BinOp>,
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
match loc_possibly_negative_or_negated_term(options).parse(
|
||||
match loc_possibly_negative_or_negated_term(options, true, true).parse(
|
||||
arena,
|
||||
state.clone(),
|
||||
call_min_indent,
|
||||
min_indent,
|
||||
) {
|
||||
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
||||
Ok((_, mut new_expr, state)) => {
|
||||
|
@ -1855,7 +1889,7 @@ fn parse_expr_end<'a>(
|
|||
Err((NoProgress, _)) => {
|
||||
let before_op = state.clone();
|
||||
// try an operator
|
||||
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
|
||||
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
|
||||
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
||||
Ok((_, loc_op, state)) => {
|
||||
expr_state.consume_spaces(arena);
|
||||
|
@ -1899,7 +1933,7 @@ fn parse_stmt_after_apply<'a>(
|
|||
initial_state: State<'a>,
|
||||
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
||||
let before_op = state.clone();
|
||||
match loc(operator()).parse(arena, state.clone(), min_indent) {
|
||||
match loc(operator()).parse(arena, state.clone(), call_min_indent) {
|
||||
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
||||
Ok((_, loc_op, state)) => {
|
||||
expr_state.consume_spaces(arena);
|
||||
|
@ -2172,8 +2206,9 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
| Expr::If { .. }
|
||||
| Expr::When(_, _)
|
||||
| Expr::Dbg
|
||||
| Expr::DbgStmt(_, _)
|
||||
| Expr::DbgStmt { .. }
|
||||
| Expr::LowLevelDbg(_, _, _)
|
||||
| Expr::LowLevelTry(_, _)
|
||||
| Expr::Return(_, _)
|
||||
| Expr::MalformedSuffixed(..)
|
||||
| Expr::PrecedenceConflict { .. }
|
||||
|
@ -2589,7 +2624,8 @@ fn if_branch<'a>() -> impl Parser<'a, (Loc<Expr<'a>>, Loc<Expr<'a>>), EIf<'a>> {
|
|||
specialize_err_ref(EIf::Condition, loc_expr(true)),
|
||||
EIf::IndentCondition,
|
||||
EIf::IndentThenToken,
|
||||
),
|
||||
)
|
||||
.trace("if_condition"),
|
||||
parser::keyword(keyword::THEN, EIf::Then),
|
||||
),
|
||||
map_with_arena(
|
||||
|
@ -2607,6 +2643,7 @@ fn if_branch<'a>() -> impl Parser<'a, (Loc<Expr<'a>>, Loc<Expr<'a>>), EIf<'a>> {
|
|||
),
|
||||
parser::keyword(keyword::ELSE, EIf::Else),
|
||||
)
|
||||
.trace("if_branch")
|
||||
}
|
||||
|
||||
fn expect_help<'a>(
|
||||
|
@ -2686,18 +2723,19 @@ fn try_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|||
|
||||
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
|
||||
skip_second(
|
||||
skip_first(
|
||||
indented_seq_skip_first(
|
||||
parser::keyword(keyword::IMPORT, EImport::Import),
|
||||
increment_min_indent(one_of!(import_body(), import_ingested_file_body())),
|
||||
one_of!(import_body(), import_ingested_file_body()),
|
||||
),
|
||||
require_newline_or_eof(EImport::EndNewline),
|
||||
)
|
||||
}
|
||||
|
||||
fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
|
||||
move |arena: &'a Bump, state, min_indent| {
|
||||
let (_, _, state) =
|
||||
parser::keyword(keyword::IF, EIf::If).parse(arena, state, min_indent)?;
|
||||
(move |arena: &'a Bump, state, min_indent| {
|
||||
let (_, _, state) = parser::keyword(keyword::IF, EIf::If)
|
||||
.trace("if_kw")
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let if_indent = state.line_indent();
|
||||
|
||||
|
@ -2706,8 +2744,9 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
|
|||
let mut loop_state = state;
|
||||
|
||||
let state_final_else = loop {
|
||||
let (_, (cond, then_branch), state) =
|
||||
if_branch().parse(arena, loop_state, min_indent)?;
|
||||
let (_, (cond, then_branch), state) = if_branch()
|
||||
.parse(arena, loop_state, min_indent)
|
||||
.map_err(|(_p, err)| (MadeProgress, err))?;
|
||||
|
||||
branches.push((cond, then_branch));
|
||||
|
||||
|
@ -2727,8 +2766,12 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
|
|||
}
|
||||
};
|
||||
|
||||
let has_newline_next = require_newline_or_eof(EExpr::IndentEnd)
|
||||
.parse(arena, state_final_else.clone(), min_indent)
|
||||
.is_ok();
|
||||
|
||||
let else_indent = state_final_else.line_indent();
|
||||
let indented_else = else_indent > if_indent;
|
||||
let indented_else = else_indent > if_indent && has_newline_next;
|
||||
|
||||
let min_indent = if !indented_else {
|
||||
else_indent + 1
|
||||
|
@ -2760,7 +2803,8 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
|
|||
};
|
||||
|
||||
Ok((MadeProgress, expr, state))
|
||||
}
|
||||
})
|
||||
.trace("if")
|
||||
}
|
||||
|
||||
/// Parse a block of statements (parser combinator version of `parse_block`)
|
||||
|
@ -3086,16 +3130,13 @@ fn stmts_to_defs<'a>(
|
|||
_,
|
||||
) = e
|
||||
{
|
||||
if args.len() != 1 {
|
||||
// TODO: this should be done in can, not parsing!
|
||||
return Err(EExpr::Dbg(
|
||||
EExpect::DbgArity(sp_stmt.item.region.start()),
|
||||
sp_stmt.item.region.start(),
|
||||
));
|
||||
}
|
||||
let condition = &args[0];
|
||||
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
|
||||
let e = Expr::DbgStmt(condition, arena.alloc(rest));
|
||||
let e = Expr::DbgStmt {
|
||||
first: condition,
|
||||
extra_args: &args[1..],
|
||||
continuation: arena.alloc(rest),
|
||||
};
|
||||
|
||||
let e = if sp_stmt.before.is_empty() {
|
||||
e
|
||||
|
@ -3160,7 +3201,7 @@ fn stmts_to_defs<'a>(
|
|||
)),
|
||||
) = (td, stmts.get(i + 1).map(|s| (s.before, s.item.value)))
|
||||
{
|
||||
if spaces_middle.len() <= 1
|
||||
if (spaces_middle.len() <= 1 && !ends_with_spaces_conservative(&ann_type.value))
|
||||
|| header
|
||||
.vars
|
||||
.first()
|
||||
|
@ -3211,7 +3252,11 @@ fn stmts_to_defs<'a>(
|
|||
if exprify_dbg {
|
||||
let e = if i + 1 < stmts.len() {
|
||||
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
|
||||
Expr::DbgStmt(arena.alloc(condition), arena.alloc(rest))
|
||||
Expr::DbgStmt {
|
||||
first: arena.alloc(condition),
|
||||
extra_args: &[],
|
||||
continuation: arena.alloc(rest),
|
||||
}
|
||||
} else {
|
||||
Expr::Apply(
|
||||
arena.alloc(Loc {
|
||||
|
@ -3244,7 +3289,8 @@ fn stmts_to_defs<'a>(
|
|||
)),
|
||||
) = (vd, stmts.get(i + 1).map(|s| (s.before, s.item.value)))
|
||||
{
|
||||
if spaces_middle.len() <= 1 || ann_pattern.value.equivalent(&loc_pattern.value)
|
||||
if (spaces_middle.len() <= 1 && !ends_with_spaces_conservative(&ann_type.value))
|
||||
|| ann_pattern.value.equivalent(&loc_pattern.value)
|
||||
{
|
||||
let region = Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
||||
|
||||
|
@ -3277,6 +3323,64 @@ fn stmts_to_defs<'a>(
|
|||
Ok((defs, last_expr))
|
||||
}
|
||||
|
||||
fn ends_with_spaces_conservative(ty: &TypeAnnotation<'_>) -> bool {
|
||||
match ty {
|
||||
TypeAnnotation::Function(_, _, res) => ends_with_spaces_conservative(&res.value),
|
||||
TypeAnnotation::Apply(_, _, args) => args
|
||||
.last()
|
||||
.map_or(false, |a| ends_with_spaces_conservative(&a.value)),
|
||||
TypeAnnotation::As(_, _, type_header) => type_header
|
||||
.vars
|
||||
.last()
|
||||
.map_or(false, |v| pat_ends_with_spaces_conservative(&v.value)),
|
||||
TypeAnnotation::Record { fields: _, ext }
|
||||
| TypeAnnotation::Tuple { elems: _, ext }
|
||||
| TypeAnnotation::TagUnion { ext, tags: _ } => {
|
||||
ext.map_or(false, |e| ends_with_spaces_conservative(&e.value))
|
||||
}
|
||||
TypeAnnotation::BoundVariable(_) | TypeAnnotation::Inferred | TypeAnnotation::Wildcard => {
|
||||
false
|
||||
}
|
||||
TypeAnnotation::Where(_, clauses) => clauses.last().map_or(false, |c| {
|
||||
c.value
|
||||
.abilities
|
||||
.last()
|
||||
.map_or(false, |a| ends_with_spaces_conservative(&a.value))
|
||||
}),
|
||||
TypeAnnotation::SpaceBefore(inner, _) => ends_with_spaces_conservative(inner),
|
||||
TypeAnnotation::SpaceAfter(_, _) => true,
|
||||
TypeAnnotation::Malformed(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn pat_ends_with_spaces_conservative(pat: &Pattern<'_>) -> bool {
|
||||
match pat {
|
||||
Pattern::Identifier { .. }
|
||||
| Pattern::QualifiedIdentifier { .. }
|
||||
| Pattern::Tag(_)
|
||||
| Pattern::NumLiteral(_)
|
||||
| Pattern::FloatLiteral(_)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::SingleQuote(_)
|
||||
| Pattern::Tuple(_)
|
||||
| Pattern::List(_)
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::ListRest(_)
|
||||
| Pattern::As(_, _)
|
||||
| Pattern::OpaqueRef(_) => false,
|
||||
Pattern::Apply(_, args) => args
|
||||
.last()
|
||||
.map_or(false, |a| pat_ends_with_spaces_conservative(&a.value)),
|
||||
Pattern::RecordDestructure(_) => false,
|
||||
Pattern::RequiredField(_, _) => unreachable!(),
|
||||
Pattern::OptionalField(_, _) => unreachable!(),
|
||||
Pattern::SpaceBefore(inner, _) => pat_ends_with_spaces_conservative(inner),
|
||||
Pattern::SpaceAfter(_, _) => true,
|
||||
Pattern::Malformed(_) | Pattern::MalformedIdent(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a type alias and a value definition, join them into a AnnotatedBody
|
||||
pub fn join_alias_to_body<'a>(
|
||||
arena: &'a Bump,
|
||||
|
@ -3753,26 +3857,6 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
|||
)
|
||||
}
|
||||
|
||||
fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
||||
map(crate::number_literal::number_literal(), |literal| {
|
||||
use crate::number_literal::NumLiteral::*;
|
||||
|
||||
match literal {
|
||||
Num(s) => Expr::Num(s),
|
||||
Float(s) => Expr::Float(s),
|
||||
NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const BINOP_CHAR_SET: &[u8] = b"+-/*=.<>:&|^?%!";
|
||||
|
||||
const BINOP_CHAR_MASK: [bool; 125] = {
|
||||
|
@ -3799,9 +3883,9 @@ enum OperatorOrDef {
|
|||
}
|
||||
|
||||
fn bin_op<'a>(check_for_defs: bool) -> impl Parser<'a, BinOp, EExpr<'a>> {
|
||||
move |_, state: State<'a>, _m| {
|
||||
move |_, state: State<'a>, min_indent| {
|
||||
let start = state.pos();
|
||||
let (_, op, state) = operator_help(EExpr::Start, EExpr::BadOperator, state)?;
|
||||
let (_, op, state) = operator_help(EExpr::Start, EExpr::BadOperator, state, min_indent)?;
|
||||
let err_progress = if check_for_defs {
|
||||
MadeProgress
|
||||
} else {
|
||||
|
@ -3822,7 +3906,8 @@ fn bin_op<'a>(check_for_defs: bool) -> impl Parser<'a, BinOp, EExpr<'a>> {
|
|||
}
|
||||
|
||||
fn operator<'a>() -> impl Parser<'a, OperatorOrDef, EExpr<'a>> {
|
||||
(move |_, state, _m| operator_help(EExpr::Start, EExpr::BadOperator, state)).trace("operator")
|
||||
(move |_, state, min_indent| operator_help(EExpr::Start, EExpr::BadOperator, state, min_indent))
|
||||
.trace("operator")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -3830,6 +3915,7 @@ fn operator_help<'a, F, G, E>(
|
|||
to_expectation: F,
|
||||
to_error: G,
|
||||
mut state: State<'a>,
|
||||
min_indent: u32,
|
||||
) -> ParseResult<'a, OperatorOrDef, E>
|
||||
where
|
||||
F: Fn(Position) -> E,
|
||||
|
@ -3855,7 +3941,21 @@ where
|
|||
match chomped {
|
||||
"" => Err((NoProgress, to_expectation(state.pos()))),
|
||||
"+" => good!(OperatorOrDef::BinOp(BinOp::Plus), 1),
|
||||
"-" => good!(OperatorOrDef::BinOp(BinOp::Minus), 1),
|
||||
"-" => {
|
||||
// A unary minus must only match if we are at the correct indent level; indent level doesn't
|
||||
// matter for the rest of the operators.
|
||||
|
||||
// Note that a unary minus is distinguished by not having a space after it
|
||||
let has_whitespace = matches!(
|
||||
state.bytes().get(1),
|
||||
Some(b' ' | b'#' | b'\n' | b'\r' | b'\t') | None
|
||||
);
|
||||
if !has_whitespace && state.column() < min_indent {
|
||||
return Err((NoProgress, to_expectation(state.pos())));
|
||||
}
|
||||
|
||||
good!(OperatorOrDef::BinOp(BinOp::Minus), 1)
|
||||
}
|
||||
"*" => good!(OperatorOrDef::BinOp(BinOp::Star), 1),
|
||||
"/" => good!(OperatorOrDef::BinOp(BinOp::Slash), 1),
|
||||
"%" => good!(OperatorOrDef::BinOp(BinOp::Percent), 1),
|
||||
|
@ -3883,6 +3983,10 @@ where
|
|||
}
|
||||
"<-" => good!(OperatorOrDef::Backpassing, 2),
|
||||
"!" => Err((NoProgress, to_error("!", state.pos()))),
|
||||
"&" => {
|
||||
// makes no progress, so it does not interfere with record updaters / `&foo`
|
||||
Err((NoProgress, to_error("&", state.pos())))
|
||||
}
|
||||
_ => bad_made_progress!(chomped),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,6 +231,7 @@ pub enum BadIdent {
|
|||
|
||||
UnderscoreAlone(Position),
|
||||
UnderscoreInMiddle(Position),
|
||||
TooManyUnderscores(Position),
|
||||
UnderscoreAtStart {
|
||||
position: Position,
|
||||
/// If this variable was already declared in a pattern (e.g. \_x -> _x),
|
||||
|
@ -252,11 +253,21 @@ fn is_alnum(ch: char) -> bool {
|
|||
}
|
||||
|
||||
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
chomp_part(char::is_lowercase, is_alnum, true, buffer)
|
||||
chomp_part(
|
||||
char::is_lowercase,
|
||||
is_plausible_ident_continue,
|
||||
true,
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
chomp_part(char::is_uppercase, is_alnum, false, buffer)
|
||||
chomp_part(
|
||||
char::is_uppercase,
|
||||
is_plausible_ident_continue,
|
||||
false,
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
fn chomp_anycase_part(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
|
@ -265,7 +276,12 @@ fn chomp_anycase_part(buffer: &[u8]) -> Result<&str, Progress> {
|
|||
let allow_bang =
|
||||
char::from_utf8_slice_start(buffer).map_or(false, |(leading, _)| leading.is_lowercase());
|
||||
|
||||
chomp_part(char::is_alphabetic, is_alnum, allow_bang, buffer)
|
||||
chomp_part(
|
||||
char::is_alphabetic,
|
||||
is_plausible_ident_continue,
|
||||
allow_bang,
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
|
||||
fn chomp_integer_part(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
|
@ -429,7 +445,14 @@ fn chomp_opaque_ref(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
|
|||
Err(bad_ident(pos.bump_column(width as u32)))
|
||||
} else {
|
||||
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) };
|
||||
Ok(value)
|
||||
if value.contains('_') {
|
||||
// we don't allow underscores in the middle of a type identifier
|
||||
// but still parse them (and generate a malformed identifier)
|
||||
// to give good error messages for this case
|
||||
Err(BadIdent::UnderscoreInMiddle(pos.bump_column(width as u32)))
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Err(bad_ident(pos.bump_column(1))),
|
||||
|
@ -486,7 +509,7 @@ fn chomp_identifier_chain<'a>(
|
|||
}
|
||||
|
||||
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
if ch.is_alphabetic() || ch.is_ascii_digit() {
|
||||
if ch.is_alphabetic() || ch.is_ascii_digit() || ch == '_' {
|
||||
chomped += width;
|
||||
} else if ch == '!' && !first_is_uppercase {
|
||||
chomped += width;
|
||||
|
@ -556,19 +579,20 @@ fn chomp_identifier_chain<'a>(
|
|||
BadIdent::WeirdDotAccess(pos.bump_column(chomped as u32 + width)),
|
||||
)),
|
||||
}
|
||||
} else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) {
|
||||
// we don't allow underscores in the middle of an identifier
|
||||
// but still parse them (and generate a malformed identifier)
|
||||
// to give good error messages for this case
|
||||
Err((
|
||||
chomped as u32 + 1,
|
||||
BadIdent::UnderscoreInMiddle(pos.bump_column(chomped as u32 + 1)),
|
||||
))
|
||||
} else if first_is_uppercase {
|
||||
// just one segment, starting with an uppercase letter; that's a tag
|
||||
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
|
||||
Ok((chomped as u32, Ident::Tag(value)))
|
||||
if value.contains('_') {
|
||||
// we don't allow underscores in the middle of a tag identifier
|
||||
// but still parse them (and generate a malformed identifier)
|
||||
// to give good error messages for this case
|
||||
Err((
|
||||
chomped as u32,
|
||||
BadIdent::UnderscoreInMiddle(pos.bump_column(chomped as u32)),
|
||||
))
|
||||
} else {
|
||||
Ok((chomped as u32, Ident::Tag(value)))
|
||||
}
|
||||
} else {
|
||||
// just one segment, starting with a lowercase letter; that's a normal identifier
|
||||
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
|
@ -689,3 +713,121 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, Accessor<'a>>) -
|
|||
Ok(chomped as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_ident_parses<'a>(arena: &'a Bump, ident: &str, expected: Ident<'a>) {
|
||||
let s = State::new(ident.as_bytes());
|
||||
let (_, id, _) = parse_ident(arena, s, 0).unwrap();
|
||||
assert_eq!(id, expected);
|
||||
}
|
||||
|
||||
fn assert_ident_parses_tag(arena: &Bump, ident: &str) {
|
||||
assert_ident_parses(arena, ident, Ident::Tag(ident));
|
||||
}
|
||||
fn assert_ident_parses_opaque(arena: &Bump, ident: &str) {
|
||||
assert_ident_parses(arena, ident, Ident::OpaqueRef(ident));
|
||||
}
|
||||
fn assert_ident_parses_simple_access(arena: &Bump, ident: &str) {
|
||||
assert_ident_parses(
|
||||
arena,
|
||||
ident,
|
||||
Ident::Access {
|
||||
module_name: "",
|
||||
parts: arena.alloc([Accessor::RecordField(ident)]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_ident_parses_malformed(arena: &Bump, ident: &str, pos: Position) {
|
||||
assert_ident_parses(
|
||||
arena,
|
||||
ident,
|
||||
Ident::Malformed(ident, BadIdent::UnderscoreInMiddle(pos)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ident_lowercase_camel() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_simple_access(&arena, "hello");
|
||||
assert_ident_parses_simple_access(&arena, "hello23");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorld");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorld23");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag_");
|
||||
assert_ident_parses_simple_access(&arena, "helloworldthisisquiteatag_");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag23");
|
||||
assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag23_");
|
||||
assert_ident_parses_simple_access(&arena, "helloworldthisisquiteatag23_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ident_lowercase_snake() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_simple_access(&arena, "hello_world");
|
||||
assert_ident_parses_simple_access(&arena, "hello_world23");
|
||||
assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_var");
|
||||
assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_var_");
|
||||
assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_var23");
|
||||
assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_var23_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tag_camel() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_tag(&arena, "Hello");
|
||||
assert_ident_parses_tag(&arena, "Hello23");
|
||||
assert_ident_parses_tag(&arena, "HelloWorld");
|
||||
assert_ident_parses_tag(&arena, "HelloWorld23");
|
||||
assert_ident_parses_tag(&arena, "HelloWorldThisIsQuiteATag");
|
||||
assert_ident_parses_tag(&arena, "HelloWorldThisIsQuiteATag23");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tag_snake_is_malformed() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_malformed(&arena, "Hello_World", Position { offset: 11 });
|
||||
assert_ident_parses_malformed(&arena, "Hello_World23", Position { offset: 13 });
|
||||
assert_ident_parses_malformed(
|
||||
&arena,
|
||||
"Hello_World_This_Is_Quite_A_Tag",
|
||||
Position { offset: 31 },
|
||||
);
|
||||
assert_ident_parses_malformed(
|
||||
&arena,
|
||||
"Hello_World_This_Is_Quite_A_Tag23",
|
||||
Position { offset: 33 },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_opaque_ref_camel() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_opaque(&arena, "@Hello");
|
||||
assert_ident_parses_opaque(&arena, "@Hello23");
|
||||
assert_ident_parses_opaque(&arena, "@HelloWorld");
|
||||
assert_ident_parses_opaque(&arena, "@HelloWorld23");
|
||||
assert_ident_parses_opaque(&arena, "@HelloWorldThisIsQuiteARef");
|
||||
assert_ident_parses_opaque(&arena, "@HelloWorldThisIsQuiteARef23");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_opaque_ref_snake_is_malformed() {
|
||||
let arena = Bump::new();
|
||||
assert_ident_parses_malformed(&arena, "@Hello_World", Position { offset: 12 });
|
||||
assert_ident_parses_malformed(&arena, "@Hello_World23", Position { offset: 14 });
|
||||
assert_ident_parses_malformed(
|
||||
&arena,
|
||||
"@Hello_World_This_Is_Quite_A_Ref",
|
||||
Position { offset: 32 },
|
||||
);
|
||||
assert_ident_parses_malformed(
|
||||
&arena,
|
||||
"@Hello_World_This_Is_Quite_A_Ref23",
|
||||
Position { offset: 34 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -396,10 +396,22 @@ impl<'a> Normalize<'a> for ValueDef<'a> {
|
|||
|
||||
match *self {
|
||||
Annotation(a, b) => Annotation(a.normalize(arena), b.normalize(arena)),
|
||||
Body(a, b) => Body(
|
||||
arena.alloc(a.normalize(arena)),
|
||||
arena.alloc(b.normalize(arena)),
|
||||
),
|
||||
Body(a, b) => {
|
||||
let a = a.normalize(arena);
|
||||
let b = b.normalize(arena);
|
||||
|
||||
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = a.value {
|
||||
collection.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_unit_assignment {
|
||||
Stmt(arena.alloc(b))
|
||||
} else {
|
||||
Body(arena.alloc(a), arena.alloc(b))
|
||||
}
|
||||
}
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
|
@ -560,26 +572,6 @@ impl<'a> Normalize<'a> for StrLiteral<'a> {
|
|||
match *self {
|
||||
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
|
||||
StrLiteral::Line(t) => {
|
||||
let mut needs_merge = false;
|
||||
let mut last_was_mergable = false;
|
||||
for segment in t.iter() {
|
||||
let mergable = matches!(
|
||||
segment,
|
||||
StrSegment::Plaintext(_)
|
||||
| StrSegment::Unicode(_)
|
||||
| StrSegment::EscapedChar(_)
|
||||
);
|
||||
if mergable && last_was_mergable {
|
||||
needs_merge = true;
|
||||
break;
|
||||
}
|
||||
last_was_mergable = mergable;
|
||||
}
|
||||
|
||||
if !needs_merge {
|
||||
return StrLiteral::Line(t.normalize(arena));
|
||||
}
|
||||
|
||||
let mut new_segments = Vec::new_in(arena);
|
||||
let mut last_text = String::new_in(arena);
|
||||
|
||||
|
@ -713,39 +705,29 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
|||
arena.alloc(b.normalize(arena)),
|
||||
),
|
||||
Expr::Crash => Expr::Crash,
|
||||
Expr::Defs(a, b) => {
|
||||
let mut defs = a.clone();
|
||||
defs.space_before = vec![Default::default(); defs.len()];
|
||||
defs.space_after = vec![Default::default(); defs.len()];
|
||||
defs.regions = vec![Region::zero(); defs.len()];
|
||||
defs.spaces.clear();
|
||||
|
||||
for type_def in defs.type_defs.iter_mut() {
|
||||
*type_def = type_def.normalize(arena);
|
||||
}
|
||||
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = value_def.normalize(arena);
|
||||
}
|
||||
|
||||
Expr::Defs(arena.alloc(defs), arena.alloc(b.normalize(arena)))
|
||||
}
|
||||
Expr::Defs(a, b) => fold_defs(arena, a.defs(), b.value.normalize(arena)),
|
||||
Expr::Backpassing(a, b, c) => Expr::Backpassing(
|
||||
arena.alloc(a.normalize(arena)),
|
||||
arena.alloc(b.normalize(arena)),
|
||||
arena.alloc(c.normalize(arena)),
|
||||
),
|
||||
Expr::Dbg => Expr::Dbg,
|
||||
Expr::DbgStmt(a, b) => Expr::DbgStmt(
|
||||
arena.alloc(a.normalize(arena)),
|
||||
arena.alloc(b.normalize(arena)),
|
||||
),
|
||||
Expr::DbgStmt {
|
||||
first,
|
||||
extra_args,
|
||||
continuation,
|
||||
} => Expr::DbgStmt {
|
||||
first: arena.alloc(first.normalize(arena)),
|
||||
extra_args: extra_args.normalize(arena),
|
||||
continuation: arena.alloc(continuation.normalize(arena)),
|
||||
},
|
||||
Expr::LowLevelDbg(x, a, b) => Expr::LowLevelDbg(
|
||||
x,
|
||||
arena.alloc(a.normalize(arena)),
|
||||
arena.alloc(b.normalize(arena)),
|
||||
),
|
||||
Expr::Try => Expr::Try,
|
||||
Expr::LowLevelTry(a, kind) => Expr::LowLevelTry(arena.alloc(a.normalize(arena)), kind),
|
||||
Expr::Return(a, b) => Expr::Return(
|
||||
arena.alloc(a.normalize(arena)),
|
||||
b.map(|loc_b| &*arena.alloc(loc_b.normalize(arena))),
|
||||
|
@ -755,7 +737,22 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
|||
}
|
||||
Expr::BinOps(a, b) => Expr::BinOps(a.normalize(arena), arena.alloc(b.normalize(arena))),
|
||||
Expr::UnaryOp(a, b) => {
|
||||
Expr::UnaryOp(arena.alloc(a.normalize(arena)), b.normalize(arena))
|
||||
let a = a.normalize(arena);
|
||||
match (a.value, b.value) {
|
||||
(Expr::Num(text), UnaryOp::Negate) if !text.starts_with('-') => {
|
||||
let mut res = String::new_in(arena);
|
||||
res.push('-');
|
||||
res.push_str(text);
|
||||
Expr::Num(res.into_bump_str())
|
||||
}
|
||||
(Expr::Float(text), UnaryOp::Negate) if !text.starts_with('-') => {
|
||||
let mut res = String::new_in(arena);
|
||||
res.push('-');
|
||||
res.push_str(text);
|
||||
Expr::Float(res.into_bump_str())
|
||||
}
|
||||
_ => Expr::UnaryOp(arena.alloc(a), b.normalize(arena)),
|
||||
}
|
||||
}
|
||||
Expr::If {
|
||||
if_thens,
|
||||
|
@ -776,7 +773,7 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
|||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.normalize(arena),
|
||||
Expr::SpaceAfter(a, _) => a.normalize(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
Expr::SingleQuote(a) => Expr::SingleQuote(a),
|
||||
Expr::EmptyRecordBuilder(a) => {
|
||||
Expr::EmptyRecordBuilder(arena.alloc(a.normalize(arena)))
|
||||
}
|
||||
|
@ -791,6 +788,61 @@ impl<'a> Normalize<'a> for Expr<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn fold_defs<'a>(
|
||||
arena: &'a Bump,
|
||||
mut defs: impl Iterator<Item = Result<&'a TypeDef<'a>, &'a ValueDef<'a>>>,
|
||||
final_expr: Expr<'a>,
|
||||
) -> Expr<'a> {
|
||||
let mut new_defs = Defs::default();
|
||||
|
||||
while let Some(def) = defs.next() {
|
||||
match def {
|
||||
Ok(td) => {
|
||||
let td = td.normalize(arena);
|
||||
new_defs.push_type_def(td, Region::zero(), &[], &[]);
|
||||
}
|
||||
Err(vd) => {
|
||||
let vd = vd.normalize(arena);
|
||||
|
||||
match vd {
|
||||
ValueDef::Stmt(&Loc {
|
||||
value:
|
||||
Expr::Apply(
|
||||
&Loc {
|
||||
value: Expr::Dbg, ..
|
||||
},
|
||||
args,
|
||||
_,
|
||||
),
|
||||
..
|
||||
}) => {
|
||||
let rest = fold_defs(arena, defs, final_expr);
|
||||
let new_final = Expr::DbgStmt {
|
||||
first: args[0],
|
||||
extra_args: &args[1..],
|
||||
continuation: arena.alloc(Loc::at_zero(rest)),
|
||||
};
|
||||
if new_defs.is_empty() {
|
||||
return new_final;
|
||||
}
|
||||
return Expr::Defs(
|
||||
arena.alloc(new_defs),
|
||||
arena.alloc(Loc::at_zero(new_final)),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
new_defs.push_value_def(vd, Region::zero(), &[], &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if new_defs.is_empty() {
|
||||
return final_expr;
|
||||
}
|
||||
Expr::Defs(arena.alloc(new_defs), arena.alloc(Loc::at_zero(final_expr)))
|
||||
}
|
||||
|
||||
fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
|
||||
match ident {
|
||||
BadIdent::Start(_) => BadIdent::Start(Position::zero()),
|
||||
|
@ -804,6 +856,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
|
|||
position: Position::zero(),
|
||||
declaration_region,
|
||||
},
|
||||
BadIdent::TooManyUnderscores(_) => BadIdent::TooManyUnderscores(Position::zero()),
|
||||
BadIdent::QualifiedTag(_) => BadIdent::QualifiedTag(Position::zero()),
|
||||
BadIdent::WeirdAccessor(_) => BadIdent::WeirdAccessor(Position::zero()),
|
||||
BadIdent::WeirdDotAccess(_) => BadIdent::WeirdDotAccess(Position::zero()),
|
||||
|
@ -847,7 +900,7 @@ impl<'a> Normalize<'a> for Pattern<'a> {
|
|||
is_negative,
|
||||
},
|
||||
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a.normalize(arena)),
|
||||
Pattern::Underscore(a) => Pattern::Underscore(a),
|
||||
Pattern::Malformed(a) => Pattern::Malformed(a),
|
||||
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, remove_spaces_bad_ident(b)),
|
||||
|
@ -1465,7 +1518,6 @@ impl<'a> Normalize<'a> for EExpect<'a> {
|
|||
EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero())
|
||||
}
|
||||
EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()),
|
||||
EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -513,7 +513,6 @@ pub enum EExpect<'a> {
|
|||
Condition(&'a EExpr<'a>, Position),
|
||||
Continuation(&'a EExpr<'a>, Position),
|
||||
IndentCondition(Position),
|
||||
DbgArity(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -836,7 +835,7 @@ where
|
|||
}
|
||||
|
||||
// This should be enough for anyone. Right? RIGHT?
|
||||
let indent_text = "| ; : ! ".repeat(20);
|
||||
let indent_text = "| ; : ! ".repeat(100);
|
||||
|
||||
let cur_indent = INDENT.with(|i| *i.borrow());
|
||||
|
||||
|
@ -1060,7 +1059,7 @@ where
|
|||
Some(
|
||||
b' ' | b'#' | b'\n' | b'\r' | b'\t' | b',' | b'(' | b')' | b'[' | b']' | b'{'
|
||||
| b'}' | b'"' | b'\'' | b'/' | b'\\' | b'+' | b'*' | b'%' | b'^' | b'&' | b'|'
|
||||
| b'<' | b'>' | b'=' | b'!' | b'~' | b'`' | b';' | b':' | b'?' | b'.',
|
||||
| b'<' | b'>' | b'=' | b'!' | b'~' | b'`' | b';' | b':' | b'?' | b'.' | b'@' | b'-',
|
||||
) => {
|
||||
state = state.advance(width);
|
||||
Ok((MadeProgress, (), state))
|
||||
|
@ -1669,6 +1668,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a parser that fails if the next byte is the given byte.
|
||||
pub fn error_on_byte<'a, T, E, F>(byte_to_match: u8, to_error: F) -> impl Parser<'a, T, E>
|
||||
where
|
||||
T: 'a,
|
||||
E: 'a,
|
||||
F: Fn(Position) -> E,
|
||||
{
|
||||
debug_assert_ne!(byte_to_match, b'\n');
|
||||
|
||||
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| match state.bytes().first() {
|
||||
Some(x) if *x == byte_to_match => Err((MadeProgress, to_error(state.pos()))),
|
||||
_ => Err((NoProgress, to_error(state.pos()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs two parsers in succession. If both parsers succeed, the output is a tuple of both outputs.
|
||||
/// Both parsers must have the same error type.
|
||||
///
|
||||
|
|
|
@ -181,6 +181,7 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
|
||||
match one_byte {
|
||||
b'"' if !is_single_quote => {
|
||||
preceded_by_dollar = false;
|
||||
if segment_parsed_bytes == 1 && segments.is_empty() {
|
||||
// special case of the empty string
|
||||
if is_multiline {
|
||||
|
@ -318,6 +319,7 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
));
|
||||
}
|
||||
b'\n' => {
|
||||
preceded_by_dollar = false;
|
||||
if is_multiline {
|
||||
let without_newline = &state.bytes()[0..(segment_parsed_bytes - 1)];
|
||||
let with_newline = &state.bytes()[0..segment_parsed_bytes];
|
||||
|
@ -456,7 +458,8 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri
|
|||
let (_progress, loc_expr, new_state) = skip_second(
|
||||
specialize_err_ref(
|
||||
EString::Format,
|
||||
loc(allocated(reset_min_indent(expr::expr_help()))),
|
||||
loc(allocated(reset_min_indent(expr::expr_help())))
|
||||
.trace("str_interpolation"),
|
||||
),
|
||||
byte(b')', EString::FormatEnd),
|
||||
)
|
||||
|
|
|
@ -10,9 +10,9 @@ use crate::expr::record_field;
|
|||
use crate::ident::{lowercase_ident, lowercase_ident_keyword_e};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
absolute_column_min_indent, and, collection_trailing_sep_e, either, increment_min_indent,
|
||||
indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed, then, zero_or_more,
|
||||
ERecord, ETypeAbilityImpl,
|
||||
absolute_column_min_indent, and, collection_trailing_sep_e, either, error_on_byte,
|
||||
increment_min_indent, indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed,
|
||||
then, zero_or_more, ERecord, ETypeAbilityImpl,
|
||||
};
|
||||
use crate::parser::{
|
||||
allocated, backtrackable, byte, fail, optional, specialize_err, specialize_err_ref, two_bytes,
|
||||
|
@ -97,7 +97,7 @@ fn check_type_alias<'a>(
|
|||
|
||||
fn parse_type_alias_after_as<'a>() -> impl Parser<'a, TypeHeader<'a>, EType<'a>> {
|
||||
then(
|
||||
space0_before_e(term(false), EType::TAsIndentStart),
|
||||
space0_before_e(term_or_apply_with_as(false), EType::TAsIndentStart),
|
||||
// TODO: introduce a better combinator for this.
|
||||
// `check_type_alias` doesn't need to modify the state or progress, but it needs to access `state.pos()`
|
||||
|arena, state, progress, output| {
|
||||
|
@ -111,23 +111,42 @@ fn parse_type_alias_after_as<'a>() -> impl Parser<'a, TypeHeader<'a>, EType<'a>>
|
|||
)
|
||||
}
|
||||
|
||||
fn term_fragment<'a>(
|
||||
stop_at_surface_has: bool,
|
||||
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
|
||||
one_of!(
|
||||
loc_wildcard(),
|
||||
loc_inferred(),
|
||||
specialize_err(EType::TInParens, loc_type_in_parens(stop_at_surface_has)),
|
||||
loc(specialize_err(
|
||||
EType::TRecord,
|
||||
record_type(stop_at_surface_has)
|
||||
)),
|
||||
loc(specialize_err(
|
||||
EType::TTagUnion,
|
||||
tag_union_type(stop_at_surface_has)
|
||||
)),
|
||||
loc(parse_type_variable(stop_at_surface_has)),
|
||||
)
|
||||
}
|
||||
|
||||
fn term<'a>(stop_at_surface_has: bool) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
|
||||
one_of!(
|
||||
term_fragment(stop_at_surface_has),
|
||||
loc(specialize_err(EType::TApply, concrete_type())),
|
||||
fail(EType::TStart),
|
||||
)
|
||||
.trace("type_annotation:term")
|
||||
}
|
||||
|
||||
fn term_or_apply_with_as<'a>(
|
||||
stop_at_surface_has: bool,
|
||||
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
|
||||
map_with_arena(
|
||||
and(
|
||||
one_of!(
|
||||
loc_wildcard(),
|
||||
loc_inferred(),
|
||||
specialize_err(EType::TInParens, loc_type_in_parens(stop_at_surface_has)),
|
||||
loc(specialize_err(
|
||||
EType::TRecord,
|
||||
record_type(stop_at_surface_has)
|
||||
)),
|
||||
loc(specialize_err(
|
||||
EType::TTagUnion,
|
||||
tag_union_type(stop_at_surface_has)
|
||||
)),
|
||||
term_fragment(stop_at_surface_has),
|
||||
loc(applied_type(stop_at_surface_has)),
|
||||
loc(parse_type_variable(stop_at_surface_has)),
|
||||
fail(EType::TStart),
|
||||
),
|
||||
// Inline alias notation, e.g. [Nil, Cons a (List a)] as List a
|
||||
|
@ -161,7 +180,7 @@ fn term<'a>(stop_at_surface_has: bool) -> impl Parser<'a, Loc<TypeAnnotation<'a>
|
|||
}
|
||||
},
|
||||
)
|
||||
.trace("type_annotation:term")
|
||||
.trace("type_annotation:term_or_apply_with_as")
|
||||
}
|
||||
|
||||
/// The `*` type variable, e.g. in (List *) Wildcard,
|
||||
|
@ -579,42 +598,52 @@ fn expression<'a>(
|
|||
stop_at_surface_has: bool,
|
||||
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'a>> {
|
||||
(move |arena, state: State<'a>, min_indent: u32| {
|
||||
let (p1, first, state) = space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
|
||||
.parse(arena, state, min_indent)?;
|
||||
let (p1, first, state) = space0_before_e(
|
||||
term_or_apply_with_as(stop_at_surface_has),
|
||||
EType::TIndentStart,
|
||||
)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let (p2, rest, rest_state) = zero_or_more(skip_first(
|
||||
backtrackable(byte(b',', EType::TFunctionArgument)),
|
||||
one_of![
|
||||
map_with_arena(
|
||||
and(
|
||||
backtrackable(space0_e(EType::TIndentStart)),
|
||||
and(
|
||||
term_or_apply_with_as(stop_at_surface_has),
|
||||
space0_e(EType::TIndentEnd)
|
||||
),
|
||||
),
|
||||
comma_args_help,
|
||||
),
|
||||
error_on_byte(b',', EType::TFunctionArgument)
|
||||
],
|
||||
))
|
||||
.trace("type_annotation:expression:rest_args")
|
||||
.parse(arena, state.clone(), min_indent)?;
|
||||
|
||||
let result = and(
|
||||
zero_or_more(skip_first(
|
||||
byte(b',', EType::TFunctionArgument),
|
||||
one_of![
|
||||
space0_around_ee(
|
||||
term(stop_at_surface_has),
|
||||
EType::TIndentStart,
|
||||
EType::TIndentEnd
|
||||
),
|
||||
fail(EType::TFunctionArgument)
|
||||
],
|
||||
))
|
||||
.trace("type_annotation:expression:rest_args"),
|
||||
and(
|
||||
space0_e(EType::TIndentStart),
|
||||
one_of![
|
||||
map(two_bytes(b'-', b'>', EType::TStart), |_| {
|
||||
FunctionArrow::Pure
|
||||
}),
|
||||
map(two_bytes(b'=', b'>', EType::TStart), |_| {
|
||||
FunctionArrow::Effectful
|
||||
}),
|
||||
],
|
||||
)
|
||||
.trace("type_annotation:expression:arrow"),
|
||||
space0_e(EType::TIndentStart),
|
||||
one_of![
|
||||
map(two_bytes(b'-', b'>', EType::TStart), |_| {
|
||||
FunctionArrow::Pure
|
||||
}),
|
||||
map(two_bytes(b'=', b'>', EType::TStart), |_| {
|
||||
FunctionArrow::Effectful
|
||||
}),
|
||||
],
|
||||
)
|
||||
.parse(arena, state.clone(), min_indent);
|
||||
.trace("type_annotation:expression:arrow")
|
||||
.parse(arena, rest_state, min_indent);
|
||||
|
||||
let (progress, annot, state) = match result {
|
||||
Ok((p2, (rest, (space_before_arrow, arrow)), state)) => {
|
||||
let (p3, return_type, state) =
|
||||
space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
|
||||
.parse(arena, state, min_indent)?;
|
||||
Ok((p3, (space_before_arrow, arrow), state)) => {
|
||||
let (p4, return_type, state) = space0_before_e(
|
||||
term_or_apply_with_as(stop_at_surface_has),
|
||||
EType::TIndentStart,
|
||||
)
|
||||
.parse(arena, state, min_indent)?;
|
||||
|
||||
let region = Region::span_across(&first.region, &return_type.region);
|
||||
|
||||
|
@ -636,7 +665,7 @@ fn expression<'a>(
|
|||
region,
|
||||
value: TypeAnnotation::Function(output, arrow, arena.alloc(return_type)),
|
||||
};
|
||||
let progress = p1.or(p2).or(p3);
|
||||
let progress = p1.or(p2).or(p3).or(p4);
|
||||
(progress, result, state)
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -694,6 +723,36 @@ fn expression<'a>(
|
|||
.trace("type_annotation:expression")
|
||||
}
|
||||
|
||||
fn comma_args_help<'a>(
|
||||
arena: &'a Bump,
|
||||
(spaces_before, (loc_val, spaces_after)): (
|
||||
&'a [CommentOrNewline<'a>],
|
||||
(Loc<TypeAnnotation<'a>>, &'a [CommentOrNewline<'a>]),
|
||||
),
|
||||
) -> Loc<TypeAnnotation<'a>> {
|
||||
if spaces_before.is_empty() {
|
||||
if spaces_after.is_empty() {
|
||||
loc_val
|
||||
} else {
|
||||
arena
|
||||
.alloc(loc_val.value)
|
||||
.with_spaces_after(spaces_after, loc_val.region)
|
||||
}
|
||||
} else if spaces_after.is_empty() {
|
||||
arena
|
||||
.alloc(loc_val.value)
|
||||
.with_spaces_before(spaces_before, loc_val.region)
|
||||
} else {
|
||||
let wrapped_expr = arena
|
||||
.alloc(loc_val.value)
|
||||
.with_spaces_after(spaces_after, loc_val.region);
|
||||
|
||||
arena
|
||||
.alloc(wrapped_expr.value)
|
||||
.with_spaces_before(spaces_before, wrapped_expr.region)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a basic type annotation that's a combination of variables
|
||||
/// (which are lowercase and unqualified, e.g. `a` in `List a`),
|
||||
/// type applications (which are uppercase and optionally qualified, e.g.
|
||||
|
|
|
@ -372,10 +372,10 @@ mod test_parse {
|
|||
let parsed = parse_module_defs(arena, state, ast::Defs::default());
|
||||
match parsed {
|
||||
Ok(_) => {
|
||||
// dbg!(_state);
|
||||
// eprintln!("{:?}", _state);
|
||||
}
|
||||
Err(_) => {
|
||||
// dbg!(_fail, _state);
|
||||
// eprintln!("{:?}, {:?}", _fail, _state);
|
||||
panic!("Failed to parse!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -763,8 +764,8 @@ impl RuntimeError {
|
|||
record: _,
|
||||
field: region,
|
||||
}
|
||||
| RuntimeError::ReadIngestedFileError { region, .. } => *region,
|
||||
RuntimeError::InvalidUnicodeCodePt(region) => *region,
|
||||
| RuntimeError::ReadIngestedFileError { region, .. }
|
||||
| RuntimeError::InvalidUnicodeCodePt(region) => *region,
|
||||
RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(),
|
||||
RuntimeError::LookupNotInScope { loc_name, .. } => loc_name.region,
|
||||
RuntimeError::OpaqueNotDefined { usage, .. } => usage.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,96 @@ 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,
|
||||
kind,
|
||||
} = 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, *kind);
|
||||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
Let(index, pool_slice) => {
|
||||
let let_con = &env.constraints.let_constraints[index.index()];
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use std::{path::PathBuf, str::Utf8Error};
|
||||
|
||||
use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind};
|
||||
use roc_can::expr::TryKind;
|
||||
use roc_can::{
|
||||
constraint::FxCallKind,
|
||||
expected::{Expected, PExpected},
|
||||
|
@ -48,6 +49,7 @@ pub enum TypeError {
|
|||
ExpectedEffectful(Region, ExpectEffectfulReason),
|
||||
UnsuffixedEffectfulFunction(Region, FxSuffixKind),
|
||||
SuffixedPureFunction(Region, FxSuffixKind),
|
||||
InvalidTryTarget(Region, ErrorType, TryKind),
|
||||
}
|
||||
|
||||
impl TypeError {
|
||||
|
@ -77,6 +79,7 @@ impl TypeError {
|
|||
TypeError::FxInTopLevel(_, _) => Warning,
|
||||
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
|
||||
TypeError::SuffixedPureFunction(_, _) => Warning,
|
||||
TypeError::InvalidTryTarget(_, _, _) => RuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +100,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),
|
||||
|
|
|
@ -155,7 +155,7 @@ impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> Env<'a, 'c, 'd, 'e, 'f, 'm, '
|
|||
}
|
||||
|
||||
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations.
|
||||
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) {
|
||||
let mono_id = match *subs.get_content_without_compacting(root_var) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(symbol, args) => {
|
||||
if symbol.is_builtin() {
|
||||
|
@ -452,12 +452,11 @@ fn number_args_to_mono_id(
|
|||
// Unroll aliases in this loop, as many aliases as we encounter.
|
||||
loop {
|
||||
match content {
|
||||
Content::Structure(flat_type) => {
|
||||
if let FlatType::Apply(outer_symbol, args) = flat_type {
|
||||
return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
Content::Structure(FlatType::Apply(outer_symbol, args)) => {
|
||||
return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems);
|
||||
}
|
||||
Content::Structure(_) => {
|
||||
break;
|
||||
}
|
||||
Content::FlexVar(_) => {
|
||||
// Num *
|
||||
|
|
|
@ -4105,3 +4105,12 @@ fn infinity_f32() {
|
|||
fn infinity_f64() {
|
||||
assert_evals_to!(r"Num.infinityF64", f64::INFINITY, f64);
|
||||
}
|
||||
#[allow(clippy::non_minimal_cfg)]
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn cast_signed_unsigned() {
|
||||
assert_evals_to!(r"Num.toI16 255u8", 255, i16);
|
||||
assert_evals_to!(r"Num.toU16 127i8", 127, u16);
|
||||
assert_evals_to!(r"Num.toU8 127i8", 127, u8);
|
||||
assert_evals_to!(r"Num.toI8 127u8", 127, i8);
|
||||
}
|
||||
|
|
|
@ -340,8 +340,6 @@ pub(crate) fn asm_evals_to<T, U, F>(
|
|||
let result = crate::helpers::dev::run_test_main::<T>(&lib);
|
||||
|
||||
if !errors.is_empty() {
|
||||
dbg!(&errors);
|
||||
|
||||
assert_eq!(
|
||||
errors,
|
||||
std::vec::Vec::new(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,13 +12,16 @@ version.workspace = true
|
|||
|
||||
[dependencies]
|
||||
bumpalo.workspace = true
|
||||
roc_can.workspace = true
|
||||
roc_collections.workspace = true
|
||||
roc_error_macros.workspace = true
|
||||
roc_fmt.workspace = true
|
||||
roc_module.workspace = true
|
||||
roc_parse.workspace = true
|
||||
roc_region.workspace = true
|
||||
roc_test_utils.workspace = true
|
||||
roc_test_utils_dir.workspace = true
|
||||
roc_types.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
|
|
|
@ -10,9 +10,12 @@ edition = "2021"
|
|||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
test_syntax.workspace = true
|
||||
roc_parse.workspace = true
|
||||
bumpalo = { workspace = true, features = ["collections"] }
|
||||
# WARNING! Do not update these dependencies to use the dot workspace syntax.
|
||||
# this crate is intentionally not a part of the overall workspace, so making changes here
|
||||
# is neither necessary nor desired.
|
||||
test_syntax = { path = "../../test_syntax" }
|
||||
roc_parse = { path = "../../parse" }
|
||||
bumpalo = { version = "3.12.0", features = ["collections"] }
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
|
|
|
@ -11,7 +11,7 @@ fuzz_target!(|data: &[u8]| {
|
|||
let ast = input.parse_in(&arena);
|
||||
if let Ok(ast) = ast {
|
||||
if !ast.is_malformed() {
|
||||
input.check_invariants(|_| (), true);
|
||||
input.check_invariants(|_| (), true, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use test_syntax::test_helpers::Input;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
|
|
|
@ -102,6 +102,12 @@ fn round_trip_once(input: Input<'_>) -> Option<String> {
|
|||
return Some("Different ast".to_string());
|
||||
}
|
||||
|
||||
let reformatted = reparsed_ast.format();
|
||||
|
||||
if output != reformatted {
|
||||
return Some("Formatting not stable".to_string());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,30 @@
|
|||
use std::path::Path;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_fmt::{annotation::Formattable, header::fmt_header};
|
||||
use roc_can::desugar;
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expr::canonicalize_expr;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_error_macros::set_panic_not_exit;
|
||||
use roc_fmt::{annotation::Formattable, header::fmt_header, MigrationFlags};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleIds, PackageModuleIds, Symbol};
|
||||
use roc_parse::header::parse_module_defs;
|
||||
use roc_parse::parser::Parser;
|
||||
use roc_parse::parser::SyntaxError;
|
||||
use roc_parse::state::State;
|
||||
use roc_parse::test_helpers::parse_loc_with;
|
||||
use roc_parse::{ast::Malformed, normalize::Normalize};
|
||||
use roc_parse::{
|
||||
ast::{Defs, Expr, FullAst, Header, Malformed, SpacesBefore},
|
||||
header::parse_module_defs,
|
||||
normalize::Normalize,
|
||||
parser::{Parser, SyntaxError},
|
||||
state::State,
|
||||
test_helpers::{parse_defs_with, parse_expr_with, parse_header_with},
|
||||
ast::{Defs, Expr, FullAst, Header, SpacesBefore},
|
||||
test_helpers::{parse_defs_with, parse_header_with},
|
||||
};
|
||||
use roc_region::all::Loc;
|
||||
use roc_region::all::Region;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
use roc_types::{
|
||||
subs::{VarStore, Variable},
|
||||
types::{AliasVar, Type},
|
||||
};
|
||||
|
||||
use roc_fmt::Buf;
|
||||
|
||||
|
@ -74,7 +90,7 @@ pub enum Output<'a> {
|
|||
|
||||
ModuleDefs(Defs<'a>),
|
||||
|
||||
Expr(Expr<'a>),
|
||||
Expr(Loc<Expr<'a>>),
|
||||
|
||||
Full(FullAst<'a>),
|
||||
}
|
||||
|
@ -82,7 +98,8 @@ pub enum Output<'a> {
|
|||
impl<'a> Output<'a> {
|
||||
pub fn format(&self) -> InputOwned {
|
||||
let arena = Bump::new();
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
let flags = MigrationFlags::new(false);
|
||||
let mut buf = Buf::new_in(&arena, flags);
|
||||
match self {
|
||||
Output::Header(header) => {
|
||||
fmt_header(&mut buf, header);
|
||||
|
@ -115,6 +132,81 @@ impl<'a> Output<'a> {
|
|||
Output::Full { .. } => format!("{self:#?}\n"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize(&self, arena: &Bump, src: &str) {
|
||||
set_panic_not_exit(true); // can has a bunch of internal_error! calls
|
||||
|
||||
match self {
|
||||
Output::Header(_) => {}
|
||||
Output::ModuleDefs(_) => {
|
||||
// TODO: canonicalize module defs
|
||||
}
|
||||
Output::Full(_) => {
|
||||
// TODO: canonicalize full ast
|
||||
}
|
||||
Output::Expr(loc_expr) => {
|
||||
let mut var_store = VarStore::default();
|
||||
let qualified_module_ids = PackageModuleIds::default();
|
||||
let home = ModuleIds::default().get_or_insert(&"Test".into());
|
||||
|
||||
let mut scope = Scope::new(
|
||||
home,
|
||||
"TestPath".into(),
|
||||
IdentIds::default(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = Env::new(
|
||||
arena,
|
||||
src,
|
||||
home,
|
||||
Path::new("Test.roc"),
|
||||
&dep_idents,
|
||||
&qualified_module_ids,
|
||||
None,
|
||||
roc_can::env::FxMode::PurityInference,
|
||||
);
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
//
|
||||
// If we did this *during* canonicalization, then each time we
|
||||
// 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.
|
||||
let loc_expr = desugar::desugar_expr(&mut env, &mut scope, loc_expr);
|
||||
|
||||
scope.add_alias(
|
||||
Symbol::NUM_INT,
|
||||
Region::zero(),
|
||||
vec![Loc::at_zero(AliasVar::unbound(
|
||||
"a".into(),
|
||||
Variable::EMPTY_RECORD,
|
||||
))],
|
||||
vec![],
|
||||
Type::EmptyRec,
|
||||
roc_types::types::AliasKind::Structural,
|
||||
);
|
||||
|
||||
let (_loc_expr, _output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
&mut scope,
|
||||
Region::zero(),
|
||||
&loc_expr.value,
|
||||
);
|
||||
|
||||
let mut all_ident_ids = IdentIds::exposed_builtins(1);
|
||||
all_ident_ids.insert(home, scope.locals.ident_ids);
|
||||
|
||||
let _interns = Interns {
|
||||
module_ids: env.qualified_module_ids.clone().into_module_ids(),
|
||||
all_ident_ids,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Malformed for Output<'a> {
|
||||
|
@ -162,7 +254,7 @@ impl<'a> Input<'a> {
|
|||
}
|
||||
|
||||
Input::Expr(input) => {
|
||||
let expr = parse_expr_with(arena, input)?;
|
||||
let expr = parse_loc_with(arena, input).map_err(|e| e.problem)?;
|
||||
Ok(Output::Expr(expr))
|
||||
}
|
||||
|
||||
|
@ -196,6 +288,7 @@ impl<'a> Input<'a> {
|
|||
&self,
|
||||
handle_formatted_output: impl Fn(Input),
|
||||
check_idempotency: bool,
|
||||
canonicalize_mode: Option<bool>,
|
||||
) {
|
||||
let arena = Bump::new();
|
||||
|
||||
|
@ -239,7 +332,7 @@ impl<'a> Input<'a> {
|
|||
self.as_str(),
|
||||
output.as_ref().as_str(),
|
||||
actual,
|
||||
reparsed_ast_normalized
|
||||
reparsed_ast
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -258,5 +351,27 @@ impl<'a> Input<'a> {
|
|||
assert_multiline_str_eq!(output.as_ref().as_str(), reformatted.as_ref().as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(expect_panic) = canonicalize_mode {
|
||||
if expect_panic {
|
||||
let text = self.as_str();
|
||||
let res = std::panic::catch_unwind(|| {
|
||||
let new_arena = Bump::new();
|
||||
actual.canonicalize(&new_arena, text);
|
||||
});
|
||||
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Canonicalize was expected to panic, but it did not. \
|
||||
If you're running test_snapshots, you may need to remove this test from \
|
||||
the list of tests that are expected to panic (great!), \
|
||||
in `fn expect_canonicalize_panics`"
|
||||
);
|
||||
} else {
|
||||
// TODO grab the output here and assert things about it.
|
||||
// For now we just make sure that it doesn't crash on this input
|
||||
actual.canonicalize(&arena, self.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Expr(When(IfGuard(Start(@28), @27), @0), @0)
|
||||
Expr(When(IfGuard(Start(@27), @27), @0), @0)
|
|
@ -0,0 +1 @@
|
|||
Expr(If(Condition(Start(@2), @2), @0), @0)
|
|
@ -0,0 +1 @@
|
|||
if!==9
|
|
@ -0,0 +1 @@
|
|||
M @S -S implements
|
|
@ -0,0 +1 @@
|
|||
Expr(Pattern(Start(@4), @4), @0)
|
|
@ -0,0 +1 @@
|
|||
M@S -S implements
|
|
@ -0,0 +1 @@
|
|||
Expr(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(Type(TInParens(End(@78), @74), @72), @71), @69), @68), @66), @65), @61), @60), @58), @57), @53), @52), @50), @49), @47), @46), @44), @43), @41), @40), @39), @38), @36), @35), @31), @30), @28), @27), @23), @22), @20), @19), @17), @16), @11), @10), @8), @7), @3), @2), @2), @0)
|
|
@ -0,0 +1 @@
|
|||
.:(i,i,(i,(i,ii,(i,(i,(i,i,(i,(i,i,(i,(J(i,(i,(i,(i,(i,i,(i,(i,i,(i,(i,(i,(J[]
|
|
@ -0,0 +1 @@
|
|||
Expr(Str(EndlessMultiLine(@3), @0), @0)
|
|
@ -0,0 +1,2 @@
|
|||
"""$
|
||||
(
|
|
@ -1,4 +1,4 @@
|
|||
Apply(
|
||||
@0-2 Apply(
|
||||
@0-1 Tag(
|
||||
"I",
|
||||
),
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
Closure(
|
||||
[
|
||||
@1-11 MalformedIdent(
|
||||
"the_answer",
|
||||
UnderscoreInMiddle(
|
||||
@5,
|
||||
),
|
||||
),
|
||||
],
|
||||
@15-17 Num(
|
||||
"42",
|
||||
),
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-37 SpaceAfter(
|
||||
When(
|
||||
@5-6 Var {
|
||||
module_name: "",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-37 SpaceAfter(
|
||||
When(
|
||||
@5-6 Var {
|
||||
module_name: "",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
MalformedIdent(
|
||||
@0-3 MalformedIdent(
|
||||
"I.5",
|
||||
QualifiedTupleAccessor(
|
||||
@1,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
MalformedIdent(
|
||||
@0-12 MalformedIdent(
|
||||
"One.Two.Whee",
|
||||
QualifiedTag(
|
||||
@12,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-46 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-55 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-58 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-107 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
if
|
||||
h
|
||||
then
|
||||
A
|
||||
else
|
||||
&e
|
||||
s #
|
|
@ -0,0 +1,75 @@
|
|||
@0-22 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@0-20,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Stmt(
|
||||
@0-20 If {
|
||||
if_thens: [
|
||||
(
|
||||
@3-4 SpaceBefore(
|
||||
SpaceAfter(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "h",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@10-11 SpaceBefore(
|
||||
SpaceAfter(
|
||||
Tag(
|
||||
"A",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
final_else: @18-20 RecordUpdater(
|
||||
"e",
|
||||
),
|
||||
indented_else: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
@21-22 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "s",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
[
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
if
|
||||
h
|
||||
then
|
||||
A
|
||||
else &e
|
||||
s#
|
|
@ -1,4 +1,4 @@
|
|||
BinOps(
|
||||
@0-5 BinOps(
|
||||
[
|
||||
(
|
||||
@0-1 Var {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
BinOps(
|
||||
@0-8 BinOps(
|
||||
[
|
||||
(
|
||||
@0-1 Num(
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
M : r
|
||||
h
|
|
@ -1,10 +1,10 @@
|
|||
Defs(
|
||||
@0-8 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(0),
|
||||
],
|
||||
regions: [
|
||||
@0-3,
|
||||
@0-6,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
|
@ -16,25 +16,26 @@ Defs(
|
|||
type_defs: [
|
||||
Alias {
|
||||
header: TypeHeader {
|
||||
name: @0-1 "J",
|
||||
name: @0-1 "M",
|
||||
vars: [],
|
||||
},
|
||||
ann: @2-3 Apply(
|
||||
"",
|
||||
"R",
|
||||
[],
|
||||
ann: @4-5 SpaceBefore(
|
||||
BoundVariable(
|
||||
"r",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
},
|
||||
],
|
||||
value_defs: [],
|
||||
},
|
||||
@5-8 SpaceBefore(
|
||||
MalformedIdent(
|
||||
"n_p",
|
||||
UnderscoreInMiddle(
|
||||
@7,
|
||||
),
|
||||
),
|
||||
@7-8 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "h",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
|
@ -0,0 +1,3 @@
|
|||
M:(
|
||||
r)
|
||||
h
|
|
@ -0,0 +1,3 @@
|
|||
A #
|
||||
p : e
|
||||
A
|
|
@ -0,0 +1,48 @@
|
|||
@0-9 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(0),
|
||||
],
|
||||
regions: [
|
||||
@0-7,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [
|
||||
Alias {
|
||||
header: TypeHeader {
|
||||
name: @0-1 "A",
|
||||
vars: [
|
||||
@4-5 SpaceBefore(
|
||||
Identifier {
|
||||
ident: "p",
|
||||
},
|
||||
[
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
ann: @6-7 BoundVariable(
|
||||
"e",
|
||||
),
|
||||
},
|
||||
],
|
||||
value_defs: [],
|
||||
},
|
||||
@8-9 SpaceBefore(
|
||||
Tag(
|
||||
"A",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
A#
|
||||
p:e
|
||||
A
|
|
@ -0,0 +1,3 @@
|
|||
K : #
|
||||
s
|
||||
K
|
|
@ -0,0 +1,44 @@
|
|||
@0-9 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(0),
|
||||
],
|
||||
regions: [
|
||||
@0-7,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [
|
||||
Alias {
|
||||
header: TypeHeader {
|
||||
name: @0-1 "K",
|
||||
vars: [],
|
||||
},
|
||||
ann: @5-6 SpaceBefore(
|
||||
BoundVariable(
|
||||
"s",
|
||||
),
|
||||
[
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
],
|
||||
value_defs: [],
|
||||
},
|
||||
@8-9 SpaceBefore(
|
||||
Tag(
|
||||
"K",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
K:(#
|
||||
s)
|
||||
K
|
|
@ -0,0 +1,4 @@
|
|||
O : O z
|
||||
#
|
||||
|
||||
b #
|
|
@ -0,0 +1,59 @@
|
|||
@0-11 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(0),
|
||||
],
|
||||
regions: [
|
||||
@0-9,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [
|
||||
Alias {
|
||||
header: TypeHeader {
|
||||
name: @0-1 "O",
|
||||
vars: [],
|
||||
},
|
||||
ann: @2-9 Apply(
|
||||
"",
|
||||
"O",
|
||||
[
|
||||
@4-5 SpaceAfter(
|
||||
BoundVariable(
|
||||
"z",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
],
|
||||
value_defs: [],
|
||||
},
|
||||
@10-11 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "b",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
[
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
O:O(z
|
||||
#
|
||||
)
|
||||
b#
|
|
@ -0,0 +1,2 @@
|
|||
p
|
||||
! .p!!
|
|
@ -0,0 +1,47 @@
|
|||
@0-8 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@0-1,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Stmt(
|
||||
@0-1 Var {
|
||||
module_name: "",
|
||||
ident: "p",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
@2-8 SpaceBefore(
|
||||
UnaryOp(
|
||||
@4-7 SpaceBefore(
|
||||
TrySuffix {
|
||||
target: Task,
|
||||
expr: AccessorFunction(
|
||||
RecordField(
|
||||
"p!",
|
||||
),
|
||||
),
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@2-3 Not,
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
p
|
||||
!
|
||||
.p!!
|
|
@ -1,4 +1,4 @@
|
|||
Defs(
|
||||
@0-42 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
SpaceAfter(
|
||||
@0-105 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Defs(
|
||||
@0-43 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
r :
|
||||
r
|
||||
#
|
||||
#
|
||||
|
||||
h
|
|
@ -0,0 +1,58 @@
|
|||
@0-13 SpaceAfter(
|
||||
Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@0-11,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Annotation(
|
||||
@0-1 Identifier {
|
||||
ident: "r",
|
||||
},
|
||||
@4-5 SpaceBefore(
|
||||
SpaceAfter(
|
||||
BoundVariable(
|
||||
"r",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
@12-13 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "h",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
r:(
|
||||
r
|
||||
#
|
||||
#
|
||||
)
|
||||
h
|
|
@ -0,0 +1,3 @@
|
|||
{ l: s #
|
||||
} : s
|
||||
o
|
|
@ -0,0 +1,51 @@
|
|||
@0-11 Defs(
|
||||
Defs {
|
||||
tags: [
|
||||
EitherIndex(2147483648),
|
||||
],
|
||||
regions: [
|
||||
@0-9,
|
||||
],
|
||||
space_before: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
space_after: [
|
||||
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
|
||||
],
|
||||
spaces: [],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
Annotation(
|
||||
@0-7 RecordDestructure(
|
||||
[
|
||||
@1-6 SpaceAfter(
|
||||
RequiredField(
|
||||
"l",
|
||||
@5-6 Identifier {
|
||||
ident: "s",
|
||||
},
|
||||
),
|
||||
[
|
||||
LineComment(
|
||||
"",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@8-9 BoundVariable(
|
||||
"s",
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
@10-11 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "o",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
{l#
|
||||
:s}:s
|
||||
o
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue