Merge branch 'trunk' into let-rec-style

This commit is contained in:
Richard Feldman 2019-12-23 01:12:55 -05:00 committed by GitHub
commit f60260a14f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 529 additions and 213 deletions

View file

@ -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

View file

@ -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.

View file

@ -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");

View file

@ -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>(

View file

@ -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);
}

View file

@ -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> {

View file

@ -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))
}
},

View file

@ -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>,

View file

@ -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!(

View file

@ -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",
// );
// }
}