mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge branch 'trunk' into let-rec-style
This commit is contained in:
commit
f60260a14f
10 changed files with 529 additions and 213 deletions
367
src/can/def.rs
367
src/can/def.rs
|
@ -15,7 +15,7 @@ use crate::can::problem::RuntimeError::*;
|
|||
use crate::can::procedure::References;
|
||||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use crate::collections::{ImSet, MutMap, MutSet, SendMap};
|
||||
use crate::graph::{strongly_connected_component, topological_sort};
|
||||
use crate::ident::Ident;
|
||||
use crate::parse::ast;
|
||||
|
@ -89,87 +89,48 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
while let Some(loc_def) = it.next() {
|
||||
match &loc_def.value {
|
||||
Annotation(pattern, annotation) => match it.peek() {
|
||||
Some(Located {
|
||||
value: Body(body_pattern, body_expr),
|
||||
..
|
||||
}) if &pattern == body_pattern => {
|
||||
it.next();
|
||||
Nested(Annotation(pattern, annotation)) | Annotation(pattern, annotation) => {
|
||||
match it.peek() {
|
||||
Some(Located {
|
||||
value: Body(body_pattern, body_expr),
|
||||
..
|
||||
}) if pattern.value.equivalent(&body_pattern.value) => {
|
||||
it.next();
|
||||
|
||||
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &typed,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &typed,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &loc_def.value,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &loc_def.value,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
Nested(Annotation(pattern, annotation)) => match it.peek() {
|
||||
Some(Located {
|
||||
value: Body(body_pattern, body_expr),
|
||||
..
|
||||
}) if pattern.value == body_pattern.value => {
|
||||
it.next();
|
||||
|
||||
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &typed,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
canonicalize_def(
|
||||
rigids,
|
||||
found_rigids,
|
||||
env,
|
||||
Located {
|
||||
region: loc_def.region,
|
||||
value: &loc_def.value,
|
||||
},
|
||||
scope,
|
||||
&mut can_defs_by_symbol,
|
||||
flex_info,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_ => {
|
||||
canonicalize_def(
|
||||
|
@ -353,6 +314,45 @@ pub fn sort_can_defs(
|
|||
}
|
||||
}
|
||||
|
||||
fn canonicalize_def_pattern(
|
||||
env: &mut Env,
|
||||
loc_pattern: &Located<ast::Pattern>,
|
||||
scope: &mut Scope,
|
||||
flex_info: &mut Info,
|
||||
var_store: &VarStore,
|
||||
) -> (PatternState, Located<Pattern>) {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let pattern_var = var_store.fresh();
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
var_store,
|
||||
scope,
|
||||
Assignment,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
&mut shadowable_idents,
|
||||
pattern_expected,
|
||||
);
|
||||
|
||||
flex_info.vars.push(pattern_var);
|
||||
|
||||
(state, loc_can_pattern)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn canonicalize_def<'a>(
|
||||
rigids: &Rigids,
|
||||
|
@ -376,7 +376,7 @@ fn canonicalize_def<'a>(
|
|||
// Each def gets to have all the idents in scope that are defined in this
|
||||
// block. Order of defs doesn't matter, thanks to referential transparency!
|
||||
let (_opt_loc_pattern, (_loc_can_expr, _can_output)) = match loc_def.value {
|
||||
Annotation(_loc_pattern, loc_annotation) => {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
// TODO implement this:
|
||||
//
|
||||
// Is this a standalone annotation, or is it annotating the
|
||||
|
@ -397,44 +397,98 @@ fn canonicalize_def<'a>(
|
|||
// immediately, then canonicalize it to get its Variable, then use that
|
||||
// Variable to generate the extra constraints.
|
||||
|
||||
let (_state, loc_can_pattern) =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, flex_info, var_store);
|
||||
|
||||
// Any time there's a lookup on this symbol in the outer Let,
|
||||
// it should result in this expression's type. After all, this
|
||||
// is the type to which this symbol is defined!
|
||||
add_pattern_to_lookup_types(
|
||||
&scope,
|
||||
&loc_pattern,
|
||||
&mut flex_info.def_types,
|
||||
expr_type.clone(),
|
||||
);
|
||||
|
||||
// annotation sans body cannot introduce new rigids that are visible in other annotations
|
||||
// but the rigids can show up in type error messages, so still register them
|
||||
let (seen_rigids, can_annotation) =
|
||||
canonicalize_annotation(&loc_annotation.value, var_store);
|
||||
|
||||
// union seen rigids with already found ones
|
||||
for (k, v) in seen_rigids {
|
||||
found_rigids.insert(k, v);
|
||||
}
|
||||
|
||||
let arity = can_annotation.arity();
|
||||
|
||||
let annotation_expected = FromAnnotation(
|
||||
loc_can_pattern.clone(),
|
||||
arity,
|
||||
AnnotationSource::TypedBody,
|
||||
can_annotation,
|
||||
);
|
||||
|
||||
// ensure expected type unifies with annotated type
|
||||
flex_info
|
||||
.constraints
|
||||
.push(Eq(expr_type, annotation_expected, loc_annotation.region));
|
||||
|
||||
// Fabricate a body for this annotation, that will error at runtime
|
||||
let value = Expr::RuntimeError(NoImplementation);
|
||||
let loc_expr = Located {
|
||||
value,
|
||||
region: loc_annotation.region,
|
||||
let loc_can_expr = if arity > 0 {
|
||||
Located {
|
||||
value,
|
||||
region: loc_annotation.region,
|
||||
}
|
||||
} else {
|
||||
let symbol = scope.gen_unique_symbol();
|
||||
|
||||
// generate a fake pattern for each argument. this makes signatures
|
||||
// that are functions only crash when they are applied.
|
||||
let mut underscores = Vec::with_capacity(arity);
|
||||
for _ in 0..arity {
|
||||
let underscore: Located<Pattern> = Located {
|
||||
value: Pattern::Underscore,
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
underscores.push(underscore);
|
||||
}
|
||||
|
||||
let body = Box::new(Located {
|
||||
value,
|
||||
region: loc_annotation.region,
|
||||
});
|
||||
|
||||
Located {
|
||||
value: Closure(symbol, Recursive::NotRecursive, underscores, body),
|
||||
region: loc_annotation.region,
|
||||
}
|
||||
};
|
||||
|
||||
(None, (loc_expr, Output::default()))
|
||||
for (_, (symbol, _)) in idents_from_patterns(std::iter::once(loc_pattern), &scope) {
|
||||
can_defs_by_symbol.insert(
|
||||
symbol,
|
||||
Def {
|
||||
// TODO try to remove this .clone()!
|
||||
pattern: loc_can_pattern.clone(),
|
||||
expr: Located {
|
||||
region: loc_can_expr.region,
|
||||
// TODO try to remove this .clone()!
|
||||
value: loc_can_expr.value.clone(),
|
||||
},
|
||||
variables_by_symbol: im::HashMap::clone(&variables_by_symbol),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
(None, (loc_can_expr, Output::default()))
|
||||
}
|
||||
|
||||
TypedDef(loc_pattern, loc_annotation, loc_expr) => {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let pattern_var = var_store.fresh();
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
var_store,
|
||||
scope,
|
||||
Assignment,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
&mut shadowable_idents,
|
||||
pattern_expected,
|
||||
);
|
||||
|
||||
flex_info.vars.push(pattern_var);
|
||||
let (state, loc_can_pattern) =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, flex_info, var_store);
|
||||
|
||||
// Any time there's a lookup on this symbol in the outer Let,
|
||||
// it should result in this expression's type. After all, this
|
||||
|
@ -450,47 +504,41 @@ fn canonicalize_def<'a>(
|
|||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||
let outer_identifier = env.tailcallable_symbol.clone();
|
||||
|
||||
// TODO ensure TypedDef has a pattern identifier?
|
||||
// e.g. in elm, you can't type
|
||||
//
|
||||
// (foo, bar) : (Int, Bool)
|
||||
// implicitly, that is the case here too
|
||||
let mut fname = "invalid name".to_string();
|
||||
|
||||
if let (&ast::Pattern::Identifier(ref name), &Pattern::Identifier(ref defined_symbol)) =
|
||||
if let (&ast::Pattern::Identifier(_), &Pattern::Identifier(ref defined_symbol)) =
|
||||
(&loc_pattern.value, &loc_can_pattern.value)
|
||||
{
|
||||
fname = (*name).to_string();
|
||||
env.tailcallable_symbol = Some(defined_symbol.clone());
|
||||
variables_by_symbol.insert(defined_symbol.clone(), expr_var);
|
||||
};
|
||||
|
||||
let (ftv_sendmap, can_annotation) =
|
||||
let (seen_rigids, can_annotation) =
|
||||
canonicalize_annotation(&loc_annotation.value, var_store);
|
||||
|
||||
let mut ftv: Rigids = ImMap::default();
|
||||
let mut ftv: Rigids = rigids.clone();
|
||||
|
||||
for (var, name) in ftv_sendmap.clone() {
|
||||
ftv.insert(name.into(), Type::Variable(var));
|
||||
for (var, name_lowercase) in seen_rigids {
|
||||
let name: Box<str> = name_lowercase.clone().into();
|
||||
|
||||
// if the rigid is known already, nothing needs to happen
|
||||
// otherwise register it.
|
||||
if !rigids.contains_key(&name) {
|
||||
// possible use this rigid in nested def's
|
||||
ftv.insert(name, Type::Variable(var));
|
||||
|
||||
// mark this variable as a rigid
|
||||
found_rigids.insert(var, name_lowercase);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the known type variables (TODO can clone be prevented?)
|
||||
let new_rigids = ftv.difference(rigids.clone());
|
||||
let annotation_expected = FromAnnotation(
|
||||
loc_can_pattern.clone(),
|
||||
can_annotation.arity(),
|
||||
AnnotationSource::TypedBody,
|
||||
can_annotation,
|
||||
);
|
||||
|
||||
let new_rtv = rigids.clone().union(new_rigids);
|
||||
|
||||
let arity = if let crate::types::Type::Function(args, _) = &can_annotation {
|
||||
args.len()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let annotation_expected =
|
||||
FromAnnotation(fname, arity, AnnotationSource::TypedBody, can_annotation);
|
||||
|
||||
let (mut loc_can_expr, mut can_output, ret_constraint) = canonicalize_expr(
|
||||
// rigids,
|
||||
&new_rtv,
|
||||
let (mut loc_can_expr, can_output, ret_constraint) = canonicalize_expr(
|
||||
&ftv,
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
|
@ -499,11 +547,8 @@ fn canonicalize_def<'a>(
|
|||
annotation_expected.clone(),
|
||||
);
|
||||
|
||||
*found_rigids = found_rigids.clone().union(ftv_sendmap.clone());
|
||||
can_output.rigids = ftv_sendmap;
|
||||
|
||||
// ensure expected type unifies with annotated type
|
||||
state
|
||||
flex_info
|
||||
.constraints
|
||||
.push(Eq(expr_type, annotation_expected, loc_def.region));
|
||||
|
||||
|
@ -628,34 +673,8 @@ fn canonicalize_def<'a>(
|
|||
// If we have a pattern, then the def has a body (that is, it's not a
|
||||
// standalone annotation), so we need to canonicalize the pattern and expr.
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let pattern_var = var_store.fresh();
|
||||
let pattern_type = Type::Variable(pattern_var);
|
||||
let pattern_expected = PExpected::NoExpectation(pattern_type);
|
||||
|
||||
let mut state = PatternState {
|
||||
headers: SendMap::default(),
|
||||
vars: Vec::with_capacity(1),
|
||||
constraints: Vec::with_capacity(1),
|
||||
};
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
&mut state,
|
||||
var_store,
|
||||
scope,
|
||||
Assignment,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
&mut shadowable_idents,
|
||||
pattern_expected,
|
||||
);
|
||||
|
||||
flex_info.vars.push(pattern_var);
|
||||
let (state, loc_can_pattern) =
|
||||
canonicalize_def_pattern(env, loc_pattern, scope, flex_info, var_store);
|
||||
|
||||
// Any time there's a lookup on this symbol in the outer Let,
|
||||
// it should result in this expression's type. After all, this
|
||||
|
|
|
@ -354,11 +354,13 @@ pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol,
|
|||
// }
|
||||
// }
|
||||
}
|
||||
RecordDestructure(_) => {
|
||||
panic!("TODO implement RecordDestructure pattern in remove_idents.");
|
||||
RecordDestructure(patterns) => {
|
||||
for loc_pattern in patterns {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
}
|
||||
RecordField(_, _) => {
|
||||
panic!("TODO implement RecordField pattern in remove_idents.");
|
||||
RecordField(_, loc_pattern) => {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
||||
|
|
|
@ -12,7 +12,7 @@ pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) {
|
|||
Body(loc_pattern, loc_expr) => {
|
||||
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||
buf.push_str(" = ");
|
||||
fmt_expr(buf, &loc_expr.value, indent, false);
|
||||
fmt_expr(buf, &loc_expr.value, indent, false, true);
|
||||
}
|
||||
TypedDef(_loc_pattern, _loc_annotation, _loc_expr) => {
|
||||
panic!("TODO support Annotation in TypedDef");
|
||||
|
|
|
@ -10,21 +10,30 @@ pub fn fmt_expr<'a>(
|
|||
expr: &'a Expr<'a>,
|
||||
indent: u16,
|
||||
apply_needs_parens: bool,
|
||||
format_newlines: bool,
|
||||
) {
|
||||
use self::Expr::*;
|
||||
|
||||
match expr {
|
||||
SpaceBefore(sub_expr, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
||||
if format_newlines {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
} else {
|
||||
fmt_comments_only(buf, spaces.iter(), indent);
|
||||
}
|
||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines);
|
||||
}
|
||||
SpaceAfter(sub_expr, spaces) => {
|
||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
fmt_expr(buf, sub_expr, indent, apply_needs_parens, format_newlines);
|
||||
if format_newlines {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
} else {
|
||||
fmt_comments_only(buf, spaces.iter(), indent);
|
||||
}
|
||||
}
|
||||
ParensAround(sub_expr) => {
|
||||
buf.push('(');
|
||||
fmt_expr(buf, sub_expr, indent, false);
|
||||
fmt_expr(buf, sub_expr, indent, false, true);
|
||||
buf.push(')');
|
||||
}
|
||||
Str(string) => {
|
||||
|
@ -45,12 +54,12 @@ pub fn fmt_expr<'a>(
|
|||
buf.push('(');
|
||||
}
|
||||
|
||||
fmt_expr(buf, &loc_expr.value, indent, true);
|
||||
fmt_expr(buf, &loc_expr.value, indent, true, true);
|
||||
|
||||
for loc_arg in loc_args {
|
||||
buf.push(' ');
|
||||
|
||||
fmt_expr(buf, &loc_arg.value, indent, true);
|
||||
fmt_expr(buf, &loc_arg.value, indent, true, true);
|
||||
}
|
||||
|
||||
if apply_needs_parens {
|
||||
|
@ -118,19 +127,14 @@ pub fn fmt_expr<'a>(
|
|||
|
||||
// Even if there were no defs, which theoretically should never happen,
|
||||
// still print the return value.
|
||||
fmt_expr(buf, &ret.value, indent, false);
|
||||
fmt_expr(buf, &ret.value, indent, false, true);
|
||||
}
|
||||
If((loc_condition, loc_then, loc_else)) => {
|
||||
buf.push_str("if ");
|
||||
fmt_expr(buf, &loc_condition.value, indent, false);
|
||||
buf.push_str(" then ");
|
||||
fmt_expr(buf, &loc_then.value, indent, false);
|
||||
buf.push_str(" else ");
|
||||
fmt_expr(buf, &loc_else.value, indent, false);
|
||||
fmt_if(buf, loc_condition, loc_then, loc_else, indent);
|
||||
}
|
||||
Case(loc_condition, branches) => {
|
||||
buf.push_str("case ");
|
||||
fmt_expr(buf, &loc_condition.value, indent, false);
|
||||
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||
buf.push_str(" when\n");
|
||||
|
||||
let mut it = branches.iter().peekable();
|
||||
|
@ -153,10 +157,10 @@ pub fn fmt_expr<'a>(
|
|||
match expr.value {
|
||||
Expr::SpaceBefore(nested, spaces) => {
|
||||
fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2));
|
||||
fmt_expr(buf, &nested, indent + (INDENT * 2), false);
|
||||
fmt_expr(buf, &nested, indent + (INDENT * 2), false, true);
|
||||
}
|
||||
_ => {
|
||||
fmt_expr(buf, &expr.value, indent + (INDENT * 2), false);
|
||||
fmt_expr(buf, &expr.value, indent + (INDENT * 2), false, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +197,7 @@ pub fn fmt_field<'a>(
|
|||
|
||||
buf.push(':');
|
||||
buf.push(' ');
|
||||
fmt_expr(buf, &value.value, indent, apply_needs_parens);
|
||||
fmt_expr(buf, &value.value, indent, apply_needs_parens, true);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
|
@ -346,6 +350,47 @@ pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn fmt_if<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_condition: &'a Located<Expr<'a>>,
|
||||
loc_then: &'a Located<Expr<'a>>,
|
||||
loc_else: &'a Located<Expr<'a>>,
|
||||
indent: u16,
|
||||
) {
|
||||
let is_multiline_then = is_multiline_expr(&loc_then.value);
|
||||
let is_multiline_else = is_multiline_expr(&loc_else.value);
|
||||
let is_multiline = is_multiline_then || is_multiline_else;
|
||||
|
||||
buf.push_str("if ");
|
||||
fmt_expr(buf, &loc_condition.value, indent, false, true);
|
||||
buf.push_str(" then");
|
||||
|
||||
let return_indent = if is_multiline {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
if is_multiline {
|
||||
newline(buf, return_indent);
|
||||
} else {
|
||||
buf.push_str(" ");
|
||||
}
|
||||
|
||||
fmt_expr(buf, &loc_then.value, return_indent, false, false);
|
||||
|
||||
if is_multiline {
|
||||
buf.push('\n');
|
||||
newline(buf, indent);
|
||||
buf.push_str("else");
|
||||
newline(buf, return_indent);
|
||||
} else {
|
||||
buf.push_str(" else ");
|
||||
}
|
||||
|
||||
fmt_expr(buf, &loc_else.value, return_indent, false, false);
|
||||
}
|
||||
|
||||
pub fn fmt_closure<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
|
||||
|
@ -408,7 +453,7 @@ pub fn fmt_closure<'a>(
|
|||
buf.push(' ');
|
||||
}
|
||||
|
||||
fmt_expr(buf, &loc_ret.value, indent, false);
|
||||
fmt_expr(buf, &loc_ret.value, indent, false, true);
|
||||
}
|
||||
|
||||
pub fn fmt_record<'a>(
|
||||
|
|
|
@ -43,10 +43,7 @@ where
|
|||
}
|
||||
}
|
||||
LineComment(comment) => {
|
||||
buf.push('#');
|
||||
buf.push_str(comment);
|
||||
|
||||
newline(buf, indent);
|
||||
fmt_comment(buf, comment, indent);
|
||||
|
||||
// Reset to 1 because we just printed a \n
|
||||
consecutive_newlines = 1;
|
||||
|
@ -66,11 +63,15 @@ where
|
|||
match space {
|
||||
Newline => {}
|
||||
LineComment(comment) => {
|
||||
buf.push('#');
|
||||
buf.push_str(comment);
|
||||
|
||||
newline(buf, indent);
|
||||
fmt_comment(buf, comment, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
|
||||
buf.push('#');
|
||||
buf.push_str(comment);
|
||||
|
||||
newline(buf, indent);
|
||||
}
|
||||
|
|
|
@ -354,6 +354,64 @@ impl<'a> Pattern<'a> {
|
|||
Ident::Malformed(string) => Pattern::Malformed(string),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that patterns are equivalent, meaning they have the same shape, but may have
|
||||
/// different locations/whitespace
|
||||
pub fn equivalent(&self, other: &Self) -> bool {
|
||||
use Pattern::*;
|
||||
match (self, other) {
|
||||
(Identifier(x), Identifier(y)) => x == y,
|
||||
(GlobalTag(x), GlobalTag(y)) => x == y,
|
||||
(PrivateTag(x), PrivateTag(y)) => x == y,
|
||||
(Apply(constructor_x, args_x), Apply(constructor_y, args_y)) => {
|
||||
let equivalent_args = args_x
|
||||
.iter()
|
||||
.zip(args_y.iter())
|
||||
.all(|(p, q)| p.value.equivalent(&q.value));
|
||||
|
||||
constructor_x.value.equivalent(&constructor_y.value) && equivalent_args
|
||||
}
|
||||
(RecordDestructure(fields_x), RecordDestructure(fields_y)) => fields_x
|
||||
.iter()
|
||||
.zip(fields_y.iter())
|
||||
.all(|(p, q)| p.value.equivalent(&q.value)),
|
||||
(RecordField(x, inner_x), RecordField(y, inner_y)) => {
|
||||
x == y && inner_x.value.equivalent(&inner_y.value)
|
||||
}
|
||||
(Nested(x), Nested(y)) => x.equivalent(y),
|
||||
|
||||
// Literal
|
||||
(IntLiteral(x), IntLiteral(y)) => x == y,
|
||||
(
|
||||
NonBase10Literal {
|
||||
string: string_x,
|
||||
base: base_x,
|
||||
is_negative: is_negative_x,
|
||||
},
|
||||
NonBase10Literal {
|
||||
string: string_y,
|
||||
base: base_y,
|
||||
is_negative: is_negative_y,
|
||||
},
|
||||
) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y,
|
||||
(FloatLiteral(x), FloatLiteral(y)) => x == y,
|
||||
(StrLiteral(x), StrLiteral(y)) => x == y,
|
||||
(BlockStrLiteral(x), BlockStrLiteral(y)) => x == y,
|
||||
(EmptyRecordLiteral, EmptyRecordLiteral) => true,
|
||||
(Underscore, Underscore) => true,
|
||||
|
||||
// Space
|
||||
(SpaceBefore(x, _), SpaceBefore(y, _)) => x.equivalent(y),
|
||||
(SpaceAfter(x, _), SpaceAfter(y, _)) => x.equivalent(y),
|
||||
|
||||
// Malformed
|
||||
(Malformed(x), Malformed(y)) => x == y,
|
||||
(QualifiedIdentifier(x), QualifiedIdentifier(y)) => x == y,
|
||||
|
||||
// Different constructors
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Spaceable<'a> {
|
||||
|
|
|
@ -1235,7 +1235,10 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
Attempting::Record,
|
||||
loc!(record!(loc!(expr(min_indent)), min_indent))
|
||||
),
|
||||
optional(and!(space0(min_indent), equals_with_indent()))
|
||||
optional(and!(
|
||||
space0(min_indent),
|
||||
either!(equals_with_indent(), colon_with_indent())
|
||||
))
|
||||
),
|
||||
move |arena, state, (loc_assigned_fields, opt_def)| match opt_def {
|
||||
None => {
|
||||
|
@ -1258,7 +1261,7 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
|
||||
Ok((value, state))
|
||||
}
|
||||
Some((spaces_before_equals, equals_indent)) => {
|
||||
Some((spaces_before_equals, Either::First(equals_indent))) => {
|
||||
// This is a record destructure def.
|
||||
let region = loc_assigned_fields.region;
|
||||
let assigned_fields = loc_assigned_fields.value;
|
||||
|
@ -1291,6 +1294,40 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||
};
|
||||
|
||||
Ok((answer, state))
|
||||
}
|
||||
Some((spaces_before_colon, Either::Second(colon_indent))) => {
|
||||
// This is a record type annotation
|
||||
let region = loc_assigned_fields.region;
|
||||
let assigned_fields = loc_assigned_fields.value;
|
||||
let mut loc_patterns = Vec::with_capacity_in(assigned_fields.len(), arena);
|
||||
|
||||
for loc_assigned_field in assigned_fields {
|
||||
let region = loc_assigned_field.region;
|
||||
match assigned_field_to_pattern(arena, &loc_assigned_field.value) {
|
||||
Ok(value) => loc_patterns.push(Located { region, value }),
|
||||
// an Expr became a pattern that should not be.
|
||||
Err(e) => return Err((e, state)),
|
||||
}
|
||||
}
|
||||
|
||||
let pattern = Pattern::RecordDestructure(loc_patterns);
|
||||
let value = if spaces_before_colon.is_empty() {
|
||||
pattern
|
||||
} else {
|
||||
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon)
|
||||
};
|
||||
let loc_pattern = Located { region, value };
|
||||
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
|
||||
let (parsed_expr, state) =
|
||||
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
|
||||
|
||||
let answer = if spaces_after_equals.is_empty() {
|
||||
parsed_expr
|
||||
} else {
|
||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||
};
|
||||
|
||||
Ok((answer, state))
|
||||
}
|
||||
},
|
||||
|
|
17
src/types.rs
17
src/types.rs
|
@ -1,4 +1,5 @@
|
|||
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{MutSet, SendMap};
|
||||
use crate::operator::{ArgSide, BinOp};
|
||||
|
@ -183,12 +184,20 @@ impl Type {
|
|||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arity(&self) -> usize {
|
||||
if let Type::Function(args, _) = self {
|
||||
args.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expected<T> {
|
||||
NoExpectation(T),
|
||||
FromAnnotation(String, usize, AnnotationSource, T),
|
||||
FromAnnotation(Located<Pattern>, usize, AnnotationSource, T),
|
||||
ForReason(Reason, T, Region),
|
||||
}
|
||||
|
||||
|
@ -264,7 +273,7 @@ pub enum Reason {
|
|||
ElemInList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Constraint {
|
||||
Eq(Type, Expected<Type>, Region),
|
||||
|
@ -288,7 +297,7 @@ pub enum PatternCategory {
|
|||
Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LetConstraint {
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
|
|
|
@ -38,7 +38,7 @@ mod test_format {
|
|||
Ok(actual) => {
|
||||
let mut buf = String::new_in(&arena);
|
||||
|
||||
fmt_expr(&mut buf, &actual, 0, false);
|
||||
fmt_expr(&mut buf, &actual, 0, false, true);
|
||||
|
||||
assert_eq!(buf, expected)
|
||||
},
|
||||
|
@ -618,6 +618,77 @@ mod test_format {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_if() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if lessThan four five then
|
||||
four
|
||||
else
|
||||
five
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if lessThan four five then
|
||||
four
|
||||
|
||||
else
|
||||
five
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
if lessThan three four then
|
||||
|
||||
|
||||
three
|
||||
|
||||
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
four
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
if lessThan three four then
|
||||
three
|
||||
|
||||
else
|
||||
four
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if foo bar then
|
||||
a b c
|
||||
|
||||
else
|
||||
d e f
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// fn multi_line_application() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// combine
|
||||
// peanutButter
|
||||
// chocolate
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
|
||||
// CASE
|
||||
|
||||
#[test]
|
||||
|
@ -634,6 +705,37 @@ mod test_format {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_case_with_space() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
case year when
|
||||
1999 ->
|
||||
|
||||
|
||||
1
|
||||
|
||||
|
||||
|
||||
_ ->
|
||||
|
||||
0
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
case year when
|
||||
1999 ->
|
||||
1
|
||||
|
||||
_ ->
|
||||
0
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_with_comments() {
|
||||
expr_formats_same(indoc!(
|
||||
|
|
|
@ -903,8 +903,51 @@ mod test_infer {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_signature_without_body() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Int -> Bool
|
||||
|
||||
foo 2
|
||||
"#
|
||||
),
|
||||
"Bool",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_signature_without_body_rigid() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Int -> custom
|
||||
|
||||
foo 2
|
||||
"#
|
||||
),
|
||||
"custom",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessor_function() {
|
||||
infer_eq(".foo", "{ foo : a }* -> a");
|
||||
}
|
||||
|
||||
// RecordDestructure does not get canonicalized yet
|
||||
// #[test]
|
||||
// fn type_signature_without_body_record() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// { x, y } : { x : (Int -> custom) , y : Int }
|
||||
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// "Int -> custom",
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue