Merge branch 'trunk' into double-typecheck

This commit is contained in:
Richard Feldman 2019-12-02 17:18:12 -05:00 committed by GitHub
commit 651e4563f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 286 additions and 214 deletions

View file

@ -59,7 +59,7 @@ pub fn canonicalize_module_defs<'a>(
for loc_def in loc_defs {
buf.push_back(canonicalize_def(
arena,
loc_def.value,
&loc_def.value,
loc_def.region,
home.clone(),
scope,
@ -72,7 +72,7 @@ pub fn canonicalize_module_defs<'a>(
fn canonicalize_def<'a>(
arena: &Bump,
def: Def<'a>,
def: &'a Def<'a>,
region: Region,
home: Box<str>,
scope: &mut ImMap<Box<str>, (Symbol, Region)>,
@ -150,8 +150,7 @@ fn canonicalize_def<'a>(
// Ignore spaces
Def::SpaceBefore(def, _) | Def::SpaceAfter(def, _) => {
// TODO FIXME performance disaster!!!
canonicalize_def(arena, def.clone(), region, home, scope, var_store)
canonicalize_def(arena, def, region, home, scope, var_store)
}
}
}
@ -860,6 +859,12 @@ fn canonicalize_expr(
local_successors(&References::new(), &env.closures)
);
}
ast::Expr::Nested(sub_expr) => {
let (answer, output) =
canonicalize_expr(rigids, env, var_store, scope, region, sub_expr, expected);
(answer.value, output)
}
ast::Expr::BinaryInt(string) => {
let (constraint, answer) =
int_expr_from_result(var_store, finish_parsing_bin(string), env, expected, region);
@ -903,7 +908,7 @@ fn canonicalize_expr(
}
ast::Expr::UnaryOp(_, loc_op) => {
panic!(
"A binary operator did not get desugared somehow: {:?}",
"A unary operator did not get desugared somehow: {:?}",
loc_op
);
}
@ -1258,7 +1263,7 @@ fn add_idents_from_pattern<'a>(
RecordField(_, _) => {
panic!("TODO implement RecordField pattern in add_idents_from_pattern.");
}
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) => {
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
// Ignore the newline/comment info; it doesn't matter in canonicalization.
add_idents_from_pattern(region, pattern, scope, answer)
}
@ -1300,7 +1305,7 @@ fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Regi
RecordField(_, _) => {
panic!("TODO implement RecordField pattern in remove_idents.");
}
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) => {
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
// Ignore the newline/comment info; it doesn't matter in canonicalization.
remove_idents(pattern, idents)
}

View file

@ -34,30 +34,41 @@ fn new_op_expr<'a>(
/// Reorder the expression tree based on operator precedence and associativity rules,
/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Located<Expr<'a>> {
use crate::operator::Associativity::*;
use std::cmp::Ordering;
match &loc_expr.value {
Float(_)
| Nested(Float(_))
| Int(_)
| Nested(Int(_))
| HexInt(_)
| Nested(HexInt(_))
| OctalInt(_)
| Nested(OctalInt(_))
| BinaryInt(_)
| Nested(BinaryInt(_))
| Str(_)
| Nested(Str(_))
| BlockStr(_)
| Nested(BlockStr(_))
| QualifiedField(_, _)
| Nested(QualifiedField(_, _))
| AccessorFunction(_)
| Nested(AccessorFunction(_))
| Var(_, _)
| Nested(Var(_, _))
| MalformedIdent(_)
| Nested(MalformedIdent(_))
| MalformedClosure
| Nested(MalformedClosure)
| PrecedenceConflict(_, _, _)
| Variant(_, _) => loc_expr,
| Nested(PrecedenceConflict(_, _, _))
| Variant(_, _)
| Nested(Variant(_, _)) => loc_expr,
Field(sub_expr, paths) => arena.alloc(Located {
Field(sub_expr, paths) | Nested(Field(sub_expr, paths)) => arena.alloc(Located {
region: loc_expr.region,
value: Field(desugar(arena, sub_expr), paths.clone()),
}),
List(elems) => {
List(elems) | Nested(List(elems)) => {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena);
for elem in elems {
@ -70,7 +81,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
value,
})
}
Record(fields) => {
Record(fields) | Nested(Record(fields)) => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields {
@ -87,171 +98,14 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
value: Record(new_fields),
})
}
Closure(loc_patterns, loc_ret) => arena.alloc(Located {
Closure(loc_patterns, loc_ret) | Nested(Closure(loc_patterns, loc_ret)) => {
arena.alloc(Located {
region: loc_expr.region,
value: Closure(loc_patterns, desugar(arena, loc_ret)),
}),
BinOp(_) => {
let mut infixes = Infixes::new(arena.alloc(loc_expr));
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::new_in(arena);
let mut op_stack: Vec<Located<BinOp>> = Vec::new_in(arena);
while let Some(token) = infixes.next() {
match token {
InfixToken::Arg(next_expr) => arg_stack.push(next_expr),
InfixToken::Op(next_op) => {
match op_stack.pop() {
Some(stack_op) => {
match next_op.value.cmp(&stack_op.value) {
Ordering::Less => {
// Inline
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
infixes.next_op = Some(next_op);
arg_stack.push(arena.alloc(new_op_expr(
arena,
left.clone(),
stack_op,
right.clone(),
)));
})
}
Ordering::Greater => {
// Swap
op_stack.push(stack_op);
op_stack.push(next_op);
}
Ordering::Equal => {
match (
next_op.value.associativity(),
stack_op.value.associativity(),
) {
(LeftAssociative, LeftAssociative) => {
// Inline
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
infixes.next_op = Some(next_op);
arg_stack.push(arena.alloc(new_op_expr(
arena,
left.clone(),
stack_op,
right.clone(),
)));
}
(RightAssociative, RightAssociative) => {
// Swap
op_stack.push(stack_op);
op_stack.push(next_op);
}
(NonAssociative, NonAssociative) => {
// Both operators were non-associative, e.g. (True == False == False).
// We should tell the author to disambiguate by grouping them with parens.
let bad_op = next_op.clone();
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
let broken_expr = new_op_expr(
arena,
left.clone(),
next_op,
right.clone(),
);
let region = broken_expr.region;
let value = Expr::PrecedenceConflict(
bad_op,
stack_op,
arena.alloc(broken_expr),
);
return arena.alloc(Located { region, value });
}
_ => {
// The operators had the same precedence but different associativity.
//
// In many languages, this case can happen due to (for example) <| and |> having the same
// precedence but different associativity. Languages which support custom operators with
// (e.g. Haskell) can potentially have arbitrarily many of these cases.
//
// By design, Roc neither allows custom operators nor has any built-in operators with
// the same precedence and different associativity, so this should never happen!
panic!("BinOps had the same associativity, but different precedence. This should never happen!");
}
}
}
}
}
None => op_stack.push(next_op),
};
}
}
}
for loc_op in op_stack.into_iter().rev() {
let right = desugar(arena, arg_stack.pop().unwrap());
let left = desugar(arena, arg_stack.pop().unwrap());
let region = Region::span_across(&left.region, &right.region);
let value = match loc_op.value {
Pizza => {
// Rewrite the Pizza operator into an Apply
match &right.value {
Apply(function, arguments, _called_via) => {
let mut args = Vec::with_capacity_in(1 + arguments.len(), arena);
args.push(left);
for arg in arguments {
args.push(arg);
}
Apply(function, args, CalledVia::BinOp(Pizza))
}
expr => {
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
let mut args = Vec::with_capacity_in(1, arena);
args.push(*arena.alloc(left));
let function = arena.alloc(Located {
value: expr.clone(),
region: right.region,
});
Apply(function, args, CalledVia::BinOp(Pizza))
}
}
}
binop => {
// This is a normal binary operator like (+), so desugar it
// into the appropriate function call.
let (module_parts, name) = desugar_binop(binop, arena);
let mut args = Vec::with_capacity_in(2, arena);
args.push(left);
args.push(right);
let loc_expr = arena.alloc(Located {
value: Expr::Var(module_parts, name),
region: loc_op.region,
});
Apply(loc_expr, args, CalledVia::BinOp(binop))
}
};
arg_stack.push(arena.alloc(Located { region, value }));
}
assert_eq!(arg_stack.len(), 1);
arg_stack.pop().unwrap()
}
Defs(defs, loc_ret) => {
BinOp(_) | Nested(BinOp(_)) => desugar_bin_op(arena, loc_expr),
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.into_iter() {
@ -265,7 +119,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
region: loc_expr.region,
})
}
Apply(loc_fn, loc_args, called_via) => {
Apply(loc_fn, loc_args, called_via) | Nested(Apply(loc_fn, loc_args, called_via)) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
@ -277,15 +131,22 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
region: loc_expr.region,
})
}
Case(loc_cond_expr, branches) => {
Case(loc_cond_expr, branches) | Nested(Case(loc_cond_expr, branches)) => {
let loc_desugared_cond = &*arena.alloc(desugar(arena, &loc_cond_expr));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for (loc_pattern, loc_branch_expr) in branches.into_iter() {
// TODO FIXME cloning performance disaster
let desugared = desugar(arena, &loc_branch_expr);
desugared_branches.push(&*arena.alloc((
loc_pattern.clone(),
desugar(arena, &loc_branch_expr).clone(),
Located {
region: loc_pattern.region,
value: Pattern::Nested(&loc_pattern.value),
},
Located {
region: desugared.region,
value: Nested(&desugared.value),
},
)));
}
@ -294,7 +155,7 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
region: loc_expr.region,
})
}
UnaryOp(loc_arg, loc_op) => {
UnaryOp(loc_arg, loc_op) | Nested(UnaryOp(loc_arg, loc_op)) => {
use crate::operator::UnaryOp::*;
let region = loc_op.region;
@ -317,25 +178,25 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
region: loc_expr.region,
})
}
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
SpaceBefore(expr, _)
| Nested(SpaceBefore(expr, _))
| SpaceAfter(expr, _)
| Nested(SpaceAfter(expr, _))
| ParensAround(expr)
| Nested(ParensAround(expr))
| Nested(Nested(expr)) => {
// Since we've already begun canonicalization, spaces and parens
// are no longer needed and should be dropped.
desugar(
arena,
arena.alloc(Located {
// TODO FIXME performance disaster!!! Must remove this clone!
//
// This won't be easy because:
//
// * If this function takes an &'a Expr, then Infixes hits a problem.
// * If SpaceBefore holds a Loc<&'a Expr>, then Spaceable hits a problem.
// * If all the existing &'a Loc<Expr> values become Loc<&'a Expr>...who knows?
value: (*expr).clone(),
value: Nested(expr),
region: loc_expr.region,
}),
)
}
If((condition, then_branch, else_branch)) => {
If((condition, then_branch, else_branch))
| Nested(If((condition, then_branch, else_branch))) => {
// desugar if into case, meaning that
//
// if b then x else y
@ -358,13 +219,15 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
// no type errors will occur here so using this region should be fine
let pattern_region = condition.region;
// TODO make False qualified
branches.push(&*arena.alloc((
Located {
value: Pattern::Variant(&[], "False"),
region: pattern_region,
},
else_branch.clone(),
Located {
value: Nested(&else_branch.value),
region: else_branch.region,
},
)));
branches.push(&*arena.alloc((
@ -372,7 +235,10 @@ pub fn desugar<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Loca
value: Pattern::Underscore,
region: pattern_region,
},
then_branch.clone(),
Located {
value: Nested(&then_branch.value),
region: then_branch.region,
},
)));
desugar(
@ -417,23 +283,27 @@ fn desugar_field<'a>(
use crate::parse::ast::AssignedField::*;
match field {
LabeledValue(ref loc_str, spaces, loc_expr) => {
AssignedField::LabeledValue(loc_str.clone(), spaces, desugar(arena, loc_expr))
}
LabelOnly(ref loc_str) => LabelOnly(loc_str.clone()),
SpaceBefore(ref field, spaces) => {
SpaceBefore(arena.alloc(desugar_field(arena, field)), spaces)
}
SpaceAfter(ref field, spaces) => {
SpaceAfter(arena.alloc(desugar_field(arena, field)), spaces)
}
LabeledValue(loc_str, spaces, loc_expr) => AssignedField::LabeledValue(
Located {
value: loc_str.value,
region: loc_str.region,
},
spaces,
desugar(arena, loc_expr),
),
LabelOnly(loc_str) => LabelOnly(Located {
value: loc_str.value,
region: loc_str.region,
}),
SpaceBefore(field, spaces) => SpaceBefore(arena.alloc(desugar_field(arena, field)), spaces),
SpaceAfter(field, spaces) => SpaceAfter(arena.alloc(desugar_field(arena, field)), spaces),
Malformed(string) => Malformed(string),
}
}
#[inline(always)]
fn desugar_binop(binop: BinOp, arena: &Bump) -> (&[&str], &str) {
fn binop_to_function(binop: BinOp, arena: &Bump) -> (&[&str], &str) {
use self::BinOp::*;
match binop {
@ -505,6 +375,189 @@ fn desugar_binop(binop: BinOp, arena: &Bump) -> (&[&str], &str) {
}
}
fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a Located<Expr<'a>> {
use crate::operator::Associativity::*;
use std::cmp::Ordering;
let mut infixes = Infixes::new(loc_expr);
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::new_in(arena);
let mut op_stack: Vec<Located<BinOp>> = Vec::new_in(arena);
while let Some(token) = infixes.next() {
match token {
InfixToken::Arg(next_expr) => arg_stack.push(next_expr),
InfixToken::Op(next_op) => {
match op_stack.pop() {
Some(stack_op) => {
match next_op.value.cmp(&stack_op.value) {
Ordering::Less => {
// Inline
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
infixes.next_op = Some(next_op);
arg_stack.push(arena.alloc(new_op_expr(
arena,
Located {
value: Nested(&left.value),
region: left.region,
},
stack_op,
Located {
value: Nested(&right.value),
region: right.region,
},
)));
}
Ordering::Greater => {
// Swap
op_stack.push(stack_op);
op_stack.push(next_op);
}
Ordering::Equal => {
match (
next_op.value.associativity(),
stack_op.value.associativity(),
) {
(LeftAssociative, LeftAssociative) => {
// Inline
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
infixes.next_op = Some(next_op);
arg_stack.push(arena.alloc(new_op_expr(
arena,
Located {
value: Nested(&left.value),
region: left.region,
},
stack_op,
Located {
value: Nested(&right.value),
region: right.region,
},
)));
}
(RightAssociative, RightAssociative) => {
// Swap
op_stack.push(stack_op);
op_stack.push(next_op);
}
(NonAssociative, NonAssociative) => {
// Both operators were non-associative, e.g. (True == False == False).
// We should tell the author to disambiguate by grouping them with parens.
let bad_op = next_op.clone();
let right = arg_stack.pop().unwrap();
let left = arg_stack.pop().unwrap();
let broken_expr = new_op_expr(
arena,
Located {
value: Nested(&left.value),
region: left.region,
},
next_op,
Located {
value: Nested(&right.value),
region: right.region,
},
);
let region = broken_expr.region;
let value = Expr::PrecedenceConflict(
bad_op,
stack_op,
arena.alloc(broken_expr),
);
return arena.alloc(Located { region, value });
}
_ => {
// The operators had the same precedence but different associativity.
//
// In many languages, this case can happen due to (for example) <| and |> having the same
// precedence but different associativity. Languages which support custom operators with
// (e.g. Haskell) can potentially have arbitrarily many of these cases.
//
// By design, Roc neither allows custom operators nor has any built-in operators with
// the same precedence and different associativity, so this should never happen!
panic!("BinOps had the same associativity, but different precedence. This should never happen!");
}
}
}
}
}
None => op_stack.push(next_op),
};
}
}
}
for loc_op in op_stack.into_iter().rev() {
let right = desugar(arena, arg_stack.pop().unwrap());
let left = desugar(arena, arg_stack.pop().unwrap());
let region = Region::span_across(&left.region, &right.region);
let value = match loc_op.value {
Pizza => {
// Rewrite the Pizza operator into an Apply
match &right.value {
Apply(function, arguments, _called_via) => {
let mut args = Vec::with_capacity_in(1 + arguments.len(), arena);
args.push(left);
for arg in arguments {
args.push(arg);
}
Apply(function, args, CalledVia::BinOp(Pizza))
}
expr => {
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
let mut args = Vec::with_capacity_in(1, arena);
args.push(left);
let function = arena.alloc(Located {
value: Nested(expr),
region: right.region,
});
Apply(function, args, CalledVia::BinOp(Pizza))
}
}
}
binop => {
// This is a normal binary operator like (+), so desugar it
// into the appropriate function call.
let (module_parts, name) = binop_to_function(binop, arena);
let mut args = Vec::with_capacity_in(2, arena);
args.push(left);
args.push(right);
let loc_expr = arena.alloc(Located {
value: Expr::Var(module_parts, name),
region: loc_op.region,
});
Apply(loc_expr, args, CalledVia::BinOp(binop))
}
};
arg_stack.push(arena.alloc(Located { region, value }));
}
assert_eq!(arg_stack.len(), 1);
arg_stack.pop().unwrap()
}
#[derive(Debug, Clone, PartialEq)]
enum InfixToken<'a> {
Arg(&'a Located<Expr<'a>>),
@ -561,9 +614,10 @@ impl<'a> Iterator for Infixes<'a> {
.remaining_expr
.take()
.map(|loc_expr| match loc_expr.value {
Expr::BinOp((left, op, right)) => {
Expr::BinOp((left, loc_op, right))
| Expr::Nested(Expr::BinOp((left, loc_op, right))) => {
self.remaining_expr = Some(right);
self.next_op = Some(op.clone());
self.next_op = Some(loc_op.clone());
InfixToken::Arg(left)
}

View file

@ -258,7 +258,7 @@ pub fn canonicalize_pattern<'a>(
},
// &EmptyRecordLiteral => Pattern::EmptyRecordLiteral,
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
return canonicalize_pattern(
env,
state,
@ -271,6 +271,7 @@ pub fn canonicalize_pattern<'a>(
expected,
)
}
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
};
@ -356,7 +357,7 @@ fn add_constraints<'a>(
));
}
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) => {
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
add_constraints(pattern, scope, region, expected, state)
}

View file

@ -286,7 +286,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
is_multiline_expr(&loc_subexpr.value)
}
ParensAround(subexpr) => is_multiline_expr(&subexpr),
ParensAround(subexpr) | Nested(subexpr) => is_multiline_expr(&subexpr),
Closure(loc_patterns, loc_body) => {
// check the body first because it's more likely to be multiline

View file

@ -84,6 +84,10 @@ pub fn fmt_pattern<'a>(
fmt_spaces(buf, spaces.iter(), indent);
}
Nested(sub_pattern) => {
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens);
}
// Malformed
Malformed(string) => buf.push_str(string),
QualifiedIdentifier(maybe_qualified) => {

View file

@ -168,6 +168,10 @@ pub enum Expr<'a> {
SpaceAfter(&'a Expr<'a>, &'a [CommentOrNewline<'a>]),
ParensAround(&'a Expr<'a>),
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take an (&Expr) and create a plain (Expr) from it.
Nested(&'a Expr<'a>),
// Problems
MalformedIdent(&'a str),
MalformedClosure,
@ -279,6 +283,10 @@ pub enum Pattern<'a> {
/// can only occur inside of a RecordDestructure
RecordField(&'a str, &'a Loc<Pattern<'a>>),
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take an (&Expr) and create a plain (Expr) from it.
Nested(&'a Pattern<'a>),
// Literal
IntLiteral(&'a str),
HexIntLiteral(&'a str),

View file

@ -272,7 +272,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
spaces,
)),
Expr::ParensAround(sub_expr) => expr_to_pattern(arena, sub_expr),
Expr::ParensAround(sub_expr) | Expr::Nested(sub_expr) => expr_to_pattern(arena, sub_expr),
Expr::Record(loc_assigned_fields) => {
let mut loc_patterns = Vec::with_capacity_in(loc_assigned_fields.len(), arena);