mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
1719 lines
62 KiB
Rust
1719 lines
62 KiB
Rust
use self::env::Env;
|
|
use self::expr::Expr::{self, *};
|
|
use self::pattern::PatternType::*;
|
|
use self::pattern::{canonicalize_pattern, Pattern};
|
|
use self::problem::Problem;
|
|
use self::problem::RuntimeError::*;
|
|
use self::procedure::{Procedure, References};
|
|
use self::scope::Scope;
|
|
use self::symbol::Symbol;
|
|
use bumpalo::Bump;
|
|
use collections::{ImMap, ImSet, MutMap, MutSet};
|
|
use constrain;
|
|
use graph::{strongly_connected_component, topological_sort};
|
|
use ident::Ident;
|
|
use parse::ast::{self, Def};
|
|
use region::{Located, Region};
|
|
use std::i64;
|
|
use subs::{Subs, Variable};
|
|
use types::Constraint::{self, *};
|
|
use types::Expected::{self, *};
|
|
use types::LetConstraint;
|
|
use types::Reason;
|
|
use types::Type::{self, *};
|
|
|
|
pub mod env;
|
|
pub mod expr;
|
|
pub mod operator;
|
|
pub mod pattern;
|
|
pub mod problem;
|
|
pub mod procedure;
|
|
pub mod scope;
|
|
pub mod string;
|
|
pub mod symbol;
|
|
|
|
pub fn canonicalize_declaration<'a>(
|
|
arena: &Bump,
|
|
subs: &mut Subs,
|
|
home: Box<str>,
|
|
name: Box<str>,
|
|
region: Region,
|
|
loc_expr: Located<ast::Expr<'a>>,
|
|
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
|
declared_variants: &ImMap<Symbol, Located<Box<str>>>,
|
|
expected: Expected<Type>,
|
|
) -> (
|
|
Located<Expr>,
|
|
Output,
|
|
Vec<Problem>,
|
|
MutMap<Symbol, Procedure>,
|
|
) {
|
|
// Desugar operators (convert them to Apply calls, taking into account
|
|
// operator precedence and associativity rules), before doing any other canonicalization.
|
|
//
|
|
// If we did this *during* canonicalization, then each time we
|
|
// visited an Operator node we'd recursively try to apply this to each of its nested
|
|
// operators, and thena again on *their* nested operators, ultimately applying the
|
|
// rules multiple times unnecessarily.
|
|
let loc_expr = operator::desugar(arena, loc_expr.value, loc_expr.region);
|
|
|
|
// If we're canonicalizing the declaration `foo = ...` inside the `Main` module,
|
|
// scope_prefix will be "Main.foo$" and its first closure will be named "Main.foo$0"
|
|
let scope_prefix = format!("{}.{}$", home, name).into();
|
|
let mut scope = Scope::new(scope_prefix, declared_idents.clone());
|
|
let mut env = Env::new(home, declared_variants.clone());
|
|
let (loc_expr, output) = canonicalize_expr(
|
|
&mut env,
|
|
subs,
|
|
&mut scope,
|
|
region,
|
|
&loc_expr.value,
|
|
expected,
|
|
);
|
|
|
|
(loc_expr, output, env.problems, env.procedures)
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Output {
|
|
pub references: References,
|
|
pub tail_call: Option<Symbol>,
|
|
pub constraint: Constraint,
|
|
}
|
|
|
|
impl Output {
|
|
pub fn new(constraint: Constraint) -> Output {
|
|
Output {
|
|
references: References::new(),
|
|
tail_call: None,
|
|
constraint,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn canonicalize_expr(
|
|
env: &mut Env,
|
|
subs: &mut Subs,
|
|
scope: &mut Scope,
|
|
region: Region,
|
|
expr: &ast::Expr,
|
|
expected: Expected<Type>,
|
|
) -> (Located<Expr>, Output) {
|
|
use self::Expr::*;
|
|
|
|
let (expr, output) = match expr {
|
|
ast::Expr::Int(string) => {
|
|
let (constraint, answer) = int_from_parsed(subs, string, env, expected, region);
|
|
|
|
(answer, Output::new(constraint))
|
|
}
|
|
ast::Expr::Float(string) => {
|
|
let (constraint, answer) = float_from_parsed(subs, string, env, expected, region);
|
|
|
|
(answer, Output::new(constraint))
|
|
}
|
|
ast::Expr::Record(fields) => {
|
|
if fields.is_empty() {
|
|
let constraint = Eq(EmptyRec, expected, region);
|
|
|
|
(EmptyRecord, Output::new(constraint))
|
|
} else {
|
|
panic!("TODO canonicalize nonempty record");
|
|
}
|
|
}
|
|
ast::Expr::Str(string) => {
|
|
let constraint = Eq(constrain::str_type(), expected, region);
|
|
|
|
(Str((*string).into()), Output::new(constraint))
|
|
}
|
|
ast::Expr::List(loc_elems) => {
|
|
if loc_elems.is_empty() {
|
|
let list_var = subs.mk_flex_var();
|
|
let constraint = Eq(constrain::empty_list_type(list_var), expected, region);
|
|
|
|
(List(list_var, Vec::new()), Output::new(constraint))
|
|
} else {
|
|
let mut can_elems = Vec::with_capacity(loc_elems.len());
|
|
let list_var = subs.mk_flex_var(); // `v` in the type (List v)
|
|
let list_type = Type::Variable(list_var);
|
|
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
|
|
let mut references = References::new();
|
|
|
|
for loc_elem in loc_elems.iter() {
|
|
let elem_var = subs.mk_flex_var();
|
|
let elem_type = Variable(elem_var);
|
|
let elem_expected = NoExpectation(elem_type.clone());
|
|
let list_elem_constraint = Eq(
|
|
list_type.clone(),
|
|
ForReason(Reason::ElemInList, elem_type, region),
|
|
region,
|
|
);
|
|
let (can_expr, elem_out) = canonicalize_expr(
|
|
env,
|
|
subs,
|
|
scope,
|
|
loc_elem.region,
|
|
&loc_elem.value,
|
|
elem_expected,
|
|
);
|
|
|
|
constraints.push(list_elem_constraint);
|
|
constraints.push(elem_out.constraint);
|
|
|
|
references = references.union(elem_out.references);
|
|
|
|
can_elems.push(can_expr);
|
|
}
|
|
|
|
constraints.push(Eq(constrain::list_type(list_type), expected, region));
|
|
|
|
let mut output = Output::new(And(constraints));
|
|
|
|
output.references = references;
|
|
|
|
// A list literal is never a tail call!
|
|
output.tail_call = None;
|
|
|
|
(List(list_var, can_elems), output)
|
|
}
|
|
}
|
|
|
|
//ast::Expr::If(loc_cond, loc_true, loc_false) => {
|
|
// // Canonicalize the nested expressions
|
|
// let (cond_expr, cond_out) = canonicalize(env, scope, *loc_cond);
|
|
// let (true_expr, true_out) = canonicalize(env, scope, *loc_true);
|
|
// let (false_expr, false_out) = canonicalize(env, scope, *loc_false);
|
|
|
|
// // Incorporate all three expressions into a combined Output value.
|
|
// let expr = If(
|
|
// Box::new(cond_expr),
|
|
// Box::new(true_expr),
|
|
// Box::new(false_expr),
|
|
// );
|
|
// let mut output = cond_out;
|
|
|
|
// // If both branches are tail calling the same symbol, then so is the conditional as a whole.
|
|
// // Also, if both branches are not tail calls (tail_call == None), then so is the conditional.
|
|
// // If the branches are different, we leave the default of None as-is.
|
|
// if true_out.tail_call == false_out.tail_call {
|
|
// output.tail_call = true_out.tail_call;
|
|
// }
|
|
|
|
// // To evaluate the whole if-expression, we depend on all the values that both branches depend on.
|
|
// output.references = output.references.union(true_out.references);
|
|
// output.references = output.references.union(false_out.references);
|
|
|
|
// (expr, output)
|
|
//}
|
|
ast::Expr::Apply(loc_fn, loc_args, application_style) => {
|
|
// The expression that evaluates to the function being called, e.g. `foo` in
|
|
// (foo) bar baz
|
|
let fn_var = subs.mk_flex_var();
|
|
let fn_type = Variable(fn_var);
|
|
let fn_region = loc_fn.region;
|
|
let fn_expected = NoExpectation(fn_type.clone());
|
|
// TODO look up the name and use NamedFnArg if possible.
|
|
let fn_reason = Reason::AnonymousFnCall(loc_args.len() as u8);
|
|
|
|
// Canonicalize the function expression and its arguments
|
|
let (fn_expr, mut output) =
|
|
canonicalize_expr(env, subs, scope, loc_fn.region, &loc_fn.value, fn_expected);
|
|
|
|
// The function's return type
|
|
let ret_var = subs.mk_flex_var();
|
|
let ret_type = Variable(ret_var);
|
|
|
|
// This will be used in the occurs check
|
|
let mut vars = Vec::with_capacity(2 + loc_args.len());
|
|
|
|
vars.push(fn_var);
|
|
vars.push(ret_var);
|
|
|
|
let mut arg_types = Vec::with_capacity(loc_args.len());
|
|
let mut arg_cons = Vec::with_capacity(loc_args.len());
|
|
|
|
let mut args = Vec::new();
|
|
let mut outputs = Vec::new();
|
|
|
|
for (index, loc_arg) in loc_args.iter().enumerate() {
|
|
let region = loc_arg.region;
|
|
let arg_var = subs.mk_flex_var();
|
|
let arg_type = Variable(arg_var);
|
|
// TODO look up the name and use NamedFnArg if possible.
|
|
let reason = Reason::AnonymousFnArg(index as u8);
|
|
let expected_arg = ForReason(reason, arg_type.clone(), region);
|
|
let (arg_expr, arg_out) = canonicalize_expr(
|
|
env,
|
|
subs,
|
|
scope,
|
|
loc_arg.region,
|
|
&loc_arg.value,
|
|
expected_arg,
|
|
);
|
|
|
|
let arg_con = arg_out.constraint.clone();
|
|
|
|
vars.push(arg_var);
|
|
arg_types.push(arg_type);
|
|
arg_cons.push(arg_con);
|
|
|
|
args.push(arg_expr);
|
|
outputs.push(arg_out);
|
|
}
|
|
|
|
// We're not tail-calling a symbol (by name), we're tail-calling a function value.
|
|
output.tail_call = None;
|
|
|
|
let expr = match &fn_expr.value {
|
|
&Var(_, ref sym) | &FunctionPointer(_, ref sym) => {
|
|
// In the FunctionPointer case, we're calling an inline closure;
|
|
// something like ((\a b -> a + b) 1 2)
|
|
output.references.calls.insert(sym.clone());
|
|
|
|
CallByName(sym.clone(), args, *application_style)
|
|
}
|
|
&RuntimeError(_) => {
|
|
// We can't call a runtime error; bail out by propagating it!
|
|
return (fn_expr, output);
|
|
}
|
|
not_var => {
|
|
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
|
|
// Use CallPointer here.
|
|
panic!("TODO support function calls that aren't by name, via CallPointer, in this case: {:?}", not_var);
|
|
}
|
|
};
|
|
|
|
for arg_out in outputs {
|
|
output.references = output.references.union(arg_out.references);
|
|
}
|
|
|
|
let fn_con = output.constraint;
|
|
|
|
// TODO occurs check!
|
|
// return $ exists vars $ CAnd ...
|
|
|
|
let expected_fn_type = ForReason(
|
|
fn_reason,
|
|
Function(arg_types, Box::new(ret_type.clone())),
|
|
region,
|
|
);
|
|
|
|
output.constraint = And(vec![
|
|
fn_con,
|
|
Eq(fn_type, expected_fn_type, fn_region),
|
|
And(arg_cons),
|
|
Eq(ret_type, expected, region),
|
|
]);
|
|
|
|
(expr, output)
|
|
}
|
|
ast::Expr::Var(module_parts, name) => {
|
|
let symbol = if module_parts.is_empty() {
|
|
scope.symbol(name)
|
|
} else {
|
|
Symbol::from_parts(module_parts, name)
|
|
};
|
|
|
|
let mut output = Output::new(Lookup(symbol, expected, region));
|
|
let ident = Ident::new(module_parts, name);
|
|
let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) {
|
|
Ok(symbol) => Var(subs.mk_flex_var(), symbol),
|
|
Err(ident) => {
|
|
let loc_ident = Located {
|
|
region: region,
|
|
value: ident,
|
|
};
|
|
|
|
env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
|
|
|
RuntimeError(UnrecognizedConstant(loc_ident))
|
|
}
|
|
};
|
|
|
|
(can_expr, output)
|
|
}
|
|
|
|
//ast::Expr::InterpolatedStr(pairs, suffix) => {
|
|
// let mut output = Output::new();
|
|
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
|
|
// .into_iter()
|
|
// .map(|(string, loc_ident)| {
|
|
// // From a language design perspective, we only permit idents in interpolation.
|
|
// // However, in a canonical Expr we store it as a full Expr, not a Symbol.
|
|
// // This is so that we can resolve it to either Var or Unrecognized; if we
|
|
// // stored it as a Symbol, we couldn't record runtime errors here.
|
|
// let can_expr = match resolve_ident(
|
|
// &env,
|
|
// &scope,
|
|
// loc_ident.value,
|
|
// &mut output.references,
|
|
// ) {
|
|
// Ok(symbol) => Var(symbol),
|
|
// Err(ident) => {
|
|
// let loc_ident = Located {
|
|
// region: loc_ident.region,
|
|
// value: ident,
|
|
// };
|
|
|
|
// env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
|
|
|
// RuntimeError(UnrecognizedConstant(loc_ident))
|
|
// }
|
|
// };
|
|
|
|
// (
|
|
// string,
|
|
// Located {
|
|
// region: loc_ident.region,
|
|
// value: can_expr,
|
|
// },
|
|
// )
|
|
// })
|
|
// .collect();
|
|
|
|
// (InterpolatedStr(can_pairs, suffix), output)
|
|
//}
|
|
|
|
//ast::Expr::ApplyVariant(variant_name, opt_args) => {
|
|
// // Canonicalize the arguments and union their references into our output.
|
|
// // We'll do this even if the variant name isn't recognized, since we still
|
|
// // want to report canonicalization problems with the variant's arguments,
|
|
// // and their references still matter for purposes of detecting unused things.
|
|
// let mut output = Output::new();
|
|
|
|
// let opt_can_args = match opt_args {
|
|
// Some(args) => {
|
|
// let mut can_args = Vec::with_capacity(args.len());
|
|
|
|
// for arg in args {
|
|
// let (loc_expr, arg_output) = canonicalize(env, scope, arg);
|
|
|
|
// output.references = output.references.union(arg_output.references);
|
|
|
|
// can_args.push(loc_expr);
|
|
// }
|
|
|
|
// Some(can_args)
|
|
// }
|
|
// None => None,
|
|
// };
|
|
|
|
// let can_expr = match resolve_variant_name(&env, variant_name, &mut output.references) {
|
|
// Ok(symbol) => ApplyVariant(symbol, opt_can_args),
|
|
// Err(variant_name) => {
|
|
// let loc_variant = Located {
|
|
// region: loc_expr.region,
|
|
// value: variant_name,
|
|
// };
|
|
|
|
// env.problem(Problem::UnrecognizedVariant(loc_variant.clone()));
|
|
|
|
// RuntimeError(UnrecognizedVariant(loc_variant))
|
|
// }
|
|
// };
|
|
|
|
// (can_expr, output)
|
|
//}
|
|
ast::Expr::Defs(defs, loc_ret) => {
|
|
// The body expression gets a new scope for canonicalization,
|
|
// so clone it.
|
|
can_defs(env, subs, scope.clone(), defs, expected, loc_ret)
|
|
}
|
|
ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => {
|
|
// The globally unique symbol that will refer to this closure once it gets converted
|
|
// into a top-level procedure for code gen.
|
|
//
|
|
// The symbol includes the module name, the top-level declaration name, and the
|
|
// index (0-based) of the closure within that declaration.
|
|
//
|
|
// Example: "MyModule$main$3" if this is the 4th closure in MyModule.main.
|
|
let symbol = scope.gen_unique_symbol();
|
|
|
|
// The body expression gets a new scope for canonicalization.
|
|
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
|
// rest of this block.
|
|
let mut scope = scope.clone();
|
|
|
|
let arg_idents: Vec<(Ident, (Symbol, Region))> =
|
|
idents_from_patterns(loc_arg_patterns.iter(), &scope);
|
|
|
|
// Add the arguments' idents to scope.idents. If there's a collision,
|
|
// it means there was shadowing, which will be handled later.
|
|
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
|
|
|
let mut state = PatternState {
|
|
assignment_types: ImMap::default(),
|
|
vars: Vec::with_capacity(loc_arg_patterns.len()),
|
|
reversed_constraints: Vec::with_capacity(1),
|
|
};
|
|
let args = constrain_args(loc_arg_patterns.iter(), &scope, subs, &mut state);
|
|
let mut can_args: Vec<Located<Pattern>> = Vec::with_capacity(loc_arg_patterns.len());
|
|
|
|
for loc_pattern in loc_arg_patterns.into_iter() {
|
|
// 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);
|
|
|
|
can_args.push(canonicalize_pattern(
|
|
env,
|
|
subs,
|
|
&mut scope,
|
|
&FunctionArg,
|
|
&loc_pattern,
|
|
&mut shadowable_idents,
|
|
))
|
|
}
|
|
|
|
let body_type = NoExpectation(args.ret_type);
|
|
let (loc_body_expr, mut output) = canonicalize_expr(
|
|
env,
|
|
subs,
|
|
&mut scope,
|
|
loc_body_expr.region,
|
|
&loc_body_expr.value,
|
|
body_type,
|
|
);
|
|
|
|
state.reversed_constraints.reverse();
|
|
|
|
let assignments_constraint = And(state.reversed_constraints);
|
|
let ret_constraint = output.constraint;
|
|
|
|
// panic!("TODO occurs check");
|
|
|
|
// We need a var to record in the procedure for later.
|
|
// We'll set it equal to the overall `expected` we've been passed.
|
|
let var = subs.mk_flex_var();
|
|
let typ = Variable(var);
|
|
|
|
output.constraint = And(vec![
|
|
Let(Box::new(LetConstraint {
|
|
rigid_vars: Vec::new(),
|
|
flex_vars: state.vars,
|
|
assignment_types: state.assignment_types,
|
|
assignments_constraint,
|
|
ret_constraint,
|
|
})),
|
|
// "the closure's type is equal to the var we've stored for later use in the proc"
|
|
Eq(args.typ, NoExpectation(typ.clone()), region),
|
|
// "the var we've stored for later is equal to the overall expected type"
|
|
Eq(typ, expected, region),
|
|
]);
|
|
|
|
// Now that we've collected all the references, check to see if any of the args we defined
|
|
// went unreferenced. If any did, report them as unused arguments.
|
|
for (ident, (arg_symbol, region)) in arg_idents {
|
|
if !output.references.has_local(&arg_symbol) {
|
|
// The body never referenced this argument we declared. It's an unused argument!
|
|
env.problem(Problem::UnusedArgument(Located {
|
|
region,
|
|
value: ident,
|
|
}));
|
|
}
|
|
|
|
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
|
// we end up with weird conclusions like the expression (\x -> x + 1)
|
|
// references the (nonexistant) local variable x!
|
|
output.references.locals.remove(&arg_symbol);
|
|
}
|
|
|
|
// We've finished analyzing the closure. Its references.locals are now the values it closes over,
|
|
// since we removed the only locals it shouldn't close over (its arguments).
|
|
// Register it as a top-level procedure in the Env!
|
|
env.register_closure(
|
|
symbol.clone(),
|
|
can_args,
|
|
loc_body_expr,
|
|
region,
|
|
output.references.clone(),
|
|
var,
|
|
args.ret_var,
|
|
);
|
|
|
|
// Always return a function pointer, in case that's how the closure is being used (e.g. with Apply).
|
|
(FunctionPointer(var, symbol), output)
|
|
}
|
|
|
|
// ast::Expr::Case(loc_cond, branches) => {
|
|
// // Canonicalize the conditional
|
|
// let (can_cond, mut output) = canonicalize(env, scope, *loc_cond);
|
|
// let mut can_branches = Vec::with_capacity(branches.len());
|
|
// let mut recorded_tail_call = false;
|
|
|
|
// for (loc_pattern, loc_expr) in branches {
|
|
// // Each case branch gets a new scope for canonicalization.
|
|
// // Shadow `scope` to make sure we don't accidentally use the original one for the
|
|
// // rest of this block.
|
|
// let mut scope = scope.clone();
|
|
|
|
// // 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.clone(), &mut shadowable_idents);
|
|
|
|
// let loc_can_pattern = canonicalize_pattern(
|
|
// env,
|
|
// &mut scope,
|
|
// &CaseBranch,
|
|
// &loc_pattern,
|
|
// &mut shadowable_idents,
|
|
// );
|
|
|
|
// // Patterns introduce new idents to the scope!
|
|
// // Add the assigned identifiers to scope. If there's a collision, it means there
|
|
// // was shadowing, which will be handled later.
|
|
// let assigned_idents: Vec<(Ident, (Symbol, Region))> =
|
|
// idents_from_patterns(std::iter::once(&loc_pattern), &scope);
|
|
|
|
// scope.idents = union_pairs(scope.idents, assigned_idents.iter());
|
|
|
|
// let (can_expr, branch_output) = canonicalize(env, &mut scope, loc_expr);
|
|
|
|
// output.references = output.references.union(branch_output.references);
|
|
|
|
// // If all branches are tail calling the same symbol, then so is the conditional as a whole.
|
|
// if !recorded_tail_call {
|
|
// // If we haven't recorded output.tail_call yet, record it.
|
|
// output.tail_call = branch_output.tail_call;
|
|
// recorded_tail_call = true;
|
|
// } else if branch_output.tail_call != output.tail_call {
|
|
// // If we recorded output.tail_call, but what we recorded differs from what we just saw,
|
|
// // then game over. This can't possibly be a self tail call!
|
|
// output.tail_call = None;
|
|
// }
|
|
|
|
// // Now that we've collected all the references for this branch, check to see if
|
|
// // any of the new idents it defined were unused. If any were, report it.
|
|
// for (ident, (symbol, region)) in assigned_idents {
|
|
// if !output.references.has_local(&symbol) {
|
|
// let loc_ident = Located {
|
|
// region: region,
|
|
// value: ident.clone(),
|
|
// };
|
|
|
|
// env.problem(Problem::UnusedAssignment(loc_ident));
|
|
// }
|
|
// }
|
|
|
|
// can_branches.push((loc_can_pattern, can_expr));
|
|
// }
|
|
|
|
// // One of the branches should have flipped this, so this should only happen
|
|
// // in the situation where the case had no branches. That can come up, though!
|
|
// // A case with no branches is a runtime error, but it will mess things up
|
|
// // if code gen mistakenly thinks this is a tail call just because its condition
|
|
// // happend to be one. (The condition gave us our initial output value.)
|
|
// if !recorded_tail_call {
|
|
// output.tail_call = None;
|
|
// }
|
|
|
|
// // Incorporate all three expressions into a combined Output value.
|
|
// let expr = Case(Box::new(can_cond), can_branches);
|
|
|
|
// (expr, output)
|
|
// }
|
|
ast::Expr::BlockStr(_)
|
|
| ast::Expr::Field(_, _)
|
|
| ast::Expr::QualifiedField(_, _)
|
|
| ast::Expr::AccessorFunction(_)
|
|
| ast::Expr::If(_)
|
|
| ast::Expr::Case(_, _)
|
|
| ast::Expr::Variant(_, _)
|
|
| ast::Expr::MalformedIdent(_)
|
|
| ast::Expr::MalformedClosure
|
|
| ast::Expr::PrecedenceConflict(_, _, _) => {
|
|
panic!(
|
|
"TODO restore the rest of canonicalize()'s branches {:?}",
|
|
local_successors(&References::new(), &MutMap::default())
|
|
);
|
|
}
|
|
ast::Expr::BinaryInt(string) => {
|
|
let (constraint, answer) = bin_from_parsed(subs, string, env, expected, region);
|
|
|
|
(answer, Output::new(constraint))
|
|
}
|
|
ast::Expr::HexInt(string) => {
|
|
let (constraint, answer) = hex_from_parsed(subs, string, env, expected, region);
|
|
|
|
(answer, Output::new(constraint))
|
|
}
|
|
ast::Expr::OctalInt(string) => {
|
|
let (constraint, answer) = oct_from_parsed(subs, string, env, expected, region);
|
|
|
|
(answer, Output::new(constraint))
|
|
}
|
|
// Below this point, we shouln't see any of these nodes anymore because
|
|
// operator desugaring should have removed them!
|
|
ast::Expr::SpaceBefore(sub_expr, _spaces) => {
|
|
panic!(
|
|
"A SpaceBefore did not get removed during operator desugaring somehow: {:?}",
|
|
sub_expr
|
|
);
|
|
}
|
|
ast::Expr::SpaceAfter(sub_expr, _spaces) => {
|
|
panic!(
|
|
"A SpaceAfter did not get removed during operator desugaring somehow: {:?}",
|
|
sub_expr
|
|
);
|
|
}
|
|
ast::Expr::Operator((_, loc_op, _)) => {
|
|
panic!("An operator did not get desugared somehow: {:?}", loc_op);
|
|
}
|
|
};
|
|
|
|
// At the end, diff used_idents and assigned_idents to see which were unused.
|
|
// Add warnings for those!
|
|
|
|
// In a later phase, unused top level declarations won't get monomorphized or code-genned.
|
|
// We aren't going to bother with DCE at the level of local assignments. It's going to be
|
|
// a rounding error anyway (especially given that they'll be surfaced as warnings), LLVM will
|
|
// DCE them in optimized builds, and it's not worth the bookkeeping for dev builds.
|
|
(
|
|
Located {
|
|
region,
|
|
value: expr,
|
|
},
|
|
output,
|
|
)
|
|
}
|
|
|
|
fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
|
where
|
|
I: Iterator<Item = &'a (K, V)>,
|
|
K: std::hash::Hash + std::cmp::Eq + Clone,
|
|
K: 'a,
|
|
V: Clone,
|
|
V: 'a,
|
|
{
|
|
for (ref k, ref v) in pairs {
|
|
map.insert(k.clone(), v.clone());
|
|
}
|
|
|
|
map
|
|
}
|
|
|
|
fn local_successors<'a>(
|
|
references: &'a References,
|
|
procedures: &'a MutMap<Symbol, Procedure>,
|
|
) -> ImSet<Symbol> {
|
|
let mut answer = references.locals.clone();
|
|
|
|
for call_symbol in references.calls.iter() {
|
|
answer = answer.union(call_successors(call_symbol, procedures));
|
|
}
|
|
|
|
answer
|
|
}
|
|
|
|
fn call_successors<'a>(
|
|
call_symbol: &'a Symbol,
|
|
procedures: &'a MutMap<Symbol, Procedure>,
|
|
) -> ImSet<Symbol> {
|
|
// TODO (this comment should be moved to a GH issue) this may cause an infinite loop if 2 procedures reference each other; may need to track visited procedures!
|
|
match procedures.get(call_symbol) {
|
|
Some(procedure) => {
|
|
let mut answer = local_successors(&procedure.references, procedures);
|
|
|
|
answer.insert(call_symbol.clone());
|
|
|
|
answer
|
|
}
|
|
None => ImSet::default(),
|
|
}
|
|
}
|
|
|
|
fn references_from_local<'a, T>(
|
|
assigned_symbol: Symbol,
|
|
visited: &'a mut MutSet<Symbol>,
|
|
refs_by_assignment: &'a MutMap<Symbol, (T, References)>,
|
|
procedures: &'a MutMap<Symbol, Procedure>,
|
|
) -> References {
|
|
match refs_by_assignment.get(&assigned_symbol) {
|
|
Some((_, refs)) => {
|
|
let mut answer: References = References::new();
|
|
|
|
visited.insert(assigned_symbol);
|
|
|
|
for local in refs.locals.iter() {
|
|
if !visited.contains(&local) {
|
|
let other_refs: References = references_from_local(
|
|
local.clone(),
|
|
visited,
|
|
refs_by_assignment,
|
|
procedures,
|
|
);
|
|
|
|
answer = answer.union(other_refs);
|
|
}
|
|
|
|
answer.locals.insert(local.clone());
|
|
}
|
|
|
|
for call in refs.calls.iter() {
|
|
if !visited.contains(&call) {
|
|
let other_refs =
|
|
references_from_call(call.clone(), visited, refs_by_assignment, procedures);
|
|
|
|
answer = answer.union(other_refs);
|
|
}
|
|
|
|
answer.calls.insert(call.clone());
|
|
}
|
|
|
|
answer
|
|
}
|
|
None => {
|
|
// This should never happen! If the local was not recognized, it should not have been
|
|
// added to the local references.
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// When we get a list of cyclic idents, the first node listed is a matter of chance.
|
|
/// This reorders the list such that the first node listed is always alphabetically the lowest,
|
|
/// while preserving the overall order of the cycle.
|
|
///
|
|
/// Example: the cycle (c ---> a ---> b) becomes (a ---> b ---> c)
|
|
pub fn sort_cyclic_idents<'a, I>(
|
|
loc_idents: Vec<Located<Ident>>,
|
|
ordered_idents: &mut I,
|
|
) -> Vec<Located<Ident>>
|
|
where
|
|
I: Iterator<Item = &'a Ident>,
|
|
{
|
|
// Find the first ident in ordered_idents that also appears in loc_idents.
|
|
let first_ident = ordered_idents
|
|
.find(|ident| {
|
|
loc_idents
|
|
.iter()
|
|
.any(|loc_ident| &&loc_ident.value == ident)
|
|
})
|
|
.unwrap();
|
|
|
|
let mut answer = Vec::with_capacity(loc_idents.len());
|
|
let mut end = Vec::with_capacity(loc_idents.len());
|
|
let mut encountered_first_ident = false;
|
|
|
|
for loc_ident in loc_idents {
|
|
if encountered_first_ident {
|
|
answer.push(loc_ident);
|
|
} else if &loc_ident.value == first_ident {
|
|
encountered_first_ident = true;
|
|
|
|
answer.push(loc_ident);
|
|
} else {
|
|
end.push(loc_ident);
|
|
}
|
|
}
|
|
|
|
// Add the contents of `end` to the end of the answer.
|
|
answer.extend_from_slice(end.as_slice());
|
|
|
|
answer
|
|
}
|
|
|
|
fn references_from_call<'a, T>(
|
|
call_symbol: Symbol,
|
|
visited: &'a mut MutSet<Symbol>,
|
|
refs_by_assignment: &'a MutMap<Symbol, (T, References)>,
|
|
procedures: &'a MutMap<Symbol, Procedure>,
|
|
) -> References {
|
|
match procedures.get(&call_symbol) {
|
|
Some(procedure) => {
|
|
let mut answer = procedure.references.clone();
|
|
|
|
visited.insert(call_symbol);
|
|
|
|
for closed_over_local in procedure.references.locals.iter() {
|
|
if !visited.contains(&closed_over_local) {
|
|
let other_refs = references_from_local(
|
|
closed_over_local.clone(),
|
|
visited,
|
|
refs_by_assignment,
|
|
procedures,
|
|
);
|
|
|
|
answer = answer.union(other_refs);
|
|
}
|
|
|
|
answer.locals.insert(closed_over_local.clone());
|
|
}
|
|
|
|
for call in procedure.references.calls.iter() {
|
|
if !visited.contains(&call) {
|
|
let other_refs =
|
|
references_from_call(call.clone(), visited, refs_by_assignment, procedures);
|
|
|
|
answer = answer.union(other_refs);
|
|
}
|
|
|
|
answer.calls.insert(call.clone());
|
|
}
|
|
|
|
answer
|
|
}
|
|
None => {
|
|
// If the call symbol was not in the procedures map, that means we're calling a non-function and
|
|
// will get a type mismatch later. For now, assume no references as a result of the "call."
|
|
References::new()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn idents_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Ident, (Symbol, Region))>
|
|
where
|
|
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
|
|
{
|
|
let mut answer = Vec::new();
|
|
|
|
for loc_pattern in loc_patterns {
|
|
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, &mut answer);
|
|
}
|
|
|
|
answer
|
|
}
|
|
|
|
/// helper function for idents_from_patterns
|
|
fn add_idents_from_pattern<'a>(
|
|
region: &'a Region,
|
|
pattern: &'a ast::Pattern<'a>,
|
|
scope: &'a Scope,
|
|
answer: &'a mut Vec<(Ident, (Symbol, Region))>,
|
|
) {
|
|
use parse::ast::Pattern::*;
|
|
|
|
match &pattern {
|
|
&Identifier(name) => {
|
|
let symbol = scope.symbol(&name);
|
|
|
|
answer.push((Ident::Unqualified(name.to_string()), (symbol, *region)));
|
|
}
|
|
&QualifiedIdentifier(_name) => {
|
|
panic!("TODO implement QualifiedIdentifier pattern.");
|
|
}
|
|
&Apply(_, _) => {
|
|
panic!("TODO implement Apply pattern.");
|
|
// &AppliedVariant(_, ref opt_loc_args) => match opt_loc_args {
|
|
// &None => (),
|
|
// &Some(ref loc_args) => {
|
|
// for loc_arg in loc_args.iter() {
|
|
// add_idents_from_pattern(loc_arg, scope, answer);
|
|
// }
|
|
// }
|
|
// },
|
|
}
|
|
|
|
&RecordDestructure(_) => {
|
|
panic!("TODO implement RecordDestructure pattern in add_idents_from_pattern.");
|
|
}
|
|
&SpaceBefore(pattern, _) | &SpaceAfter(pattern, _) => {
|
|
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
|
add_idents_from_pattern(region, pattern, scope, answer)
|
|
}
|
|
&Variant(_, _)
|
|
| &IntLiteral(_)
|
|
| &HexIntLiteral(_)
|
|
| &OctalIntLiteral(_)
|
|
| &BinaryIntLiteral(_)
|
|
| &FloatLiteral(_)
|
|
| &StrLiteral(_)
|
|
| &EmptyRecordLiteral
|
|
| &Malformed(_)
|
|
| &Underscore => (),
|
|
}
|
|
}
|
|
|
|
fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Region)>) {
|
|
use parse::ast::Pattern::*;
|
|
|
|
match &pattern {
|
|
Identifier(name) => {
|
|
idents.remove(&(Ident::Unqualified(name.to_string())));
|
|
}
|
|
QualifiedIdentifier(_name) => {
|
|
panic!("TODO implement QualifiedIdentifier pattern in remove_idents.");
|
|
}
|
|
Apply(_, _) => {
|
|
panic!("TODO implement Apply pattern in remove_idents.");
|
|
// AppliedVariant(_, Some(loc_args)) => {
|
|
// for loc_arg in loc_args {
|
|
// remove_idents(loc_arg.value, idents);
|
|
// }
|
|
// }
|
|
}
|
|
RecordDestructure(_) => {
|
|
panic!("TODO implement RecordDestructure pattern in remove_idents.");
|
|
}
|
|
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) => {
|
|
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
|
remove_idents(pattern, idents)
|
|
}
|
|
Variant(_, _)
|
|
| IntLiteral(_)
|
|
| HexIntLiteral(_)
|
|
| BinaryIntLiteral(_)
|
|
| OctalIntLiteral(_)
|
|
| FloatLiteral(_)
|
|
| StrLiteral(_)
|
|
| EmptyRecordLiteral
|
|
| Malformed(_)
|
|
| Underscore => {}
|
|
}
|
|
}
|
|
|
|
/// If it could not be found, return it unchanged as an Err.
|
|
#[inline(always)] // This is shared code between Var and InterpolatedStr; it was inlined when handwritten
|
|
fn resolve_ident<'a>(
|
|
env: &'a Env,
|
|
scope: &Scope,
|
|
ident: Ident,
|
|
references: &mut References,
|
|
) -> Result<Symbol, Ident> {
|
|
if scope.idents.contains_key(&ident) {
|
|
let recognized = match ident {
|
|
Ident::Unqualified(name) => {
|
|
let symbol = scope.symbol(&name);
|
|
|
|
references.locals.insert(symbol.clone());
|
|
|
|
symbol
|
|
}
|
|
Ident::Qualified(path, name) => {
|
|
let symbol = Symbol::new(&path, &name);
|
|
|
|
references.globals.insert(symbol.clone());
|
|
|
|
symbol
|
|
}
|
|
};
|
|
|
|
Ok(recognized)
|
|
} else {
|
|
match ident {
|
|
Ident::Unqualified(name) => {
|
|
// Try again, this time using the current module as the path.
|
|
let qualified = Ident::Qualified(env.home.clone().to_string(), name.clone());
|
|
|
|
if scope.idents.contains_key(&qualified) {
|
|
let symbol = Symbol::new(&env.home, &name);
|
|
|
|
references.globals.insert(symbol.clone());
|
|
|
|
Ok(symbol)
|
|
} else {
|
|
// We couldn't find the unqualified ident in scope. NAMING PROBLEM!
|
|
Err(Ident::Unqualified(name))
|
|
}
|
|
}
|
|
qualified @ Ident::Qualified(_, _) => {
|
|
// We couldn't find the qualified ident in scope. NAMING PROBLEM!
|
|
Err(qualified)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///// Translate a VariantName into a resolved symbol if it's found in env.declared_variants.
|
|
///// If it could not be found, return it unchanged as an Err.
|
|
//#[inline(always)]
|
|
//fn resolve_variant_name(
|
|
// env: &Env,
|
|
// variant_name: VariantName,
|
|
// references: &mut References,
|
|
//) -> Result<Symbol, VariantName> {
|
|
// let symbol = Symbol::from_variant(&variant_name, &env.home);
|
|
|
|
// if env.variants.contains_key(&symbol) {
|
|
// references.variants.insert(symbol.clone());
|
|
|
|
// Ok(symbol)
|
|
// } else {
|
|
// // We couldn't find the qualified variant name in scope. NAMING PROBLEM!
|
|
// Err(variant_name)
|
|
// }
|
|
//}
|
|
|
|
#[inline(always)]
|
|
fn float_from_parsed(
|
|
subs: &mut Subs,
|
|
raw: &str,
|
|
env: &mut Env,
|
|
expected: Expected<Type>,
|
|
region: Region,
|
|
) -> (Constraint, Expr) {
|
|
// Ignore underscores.
|
|
match raw.replace("_", "").parse::<f64>() {
|
|
Ok(float) if float.is_finite() => (
|
|
constrain::float_literal(subs, expected, region),
|
|
Expr::Float(float),
|
|
),
|
|
_ => {
|
|
let runtime_error = FloatOutsideRange(raw.into());
|
|
|
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
|
|
|
(True, Expr::RuntimeError(runtime_error))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn int_from_parsed(
|
|
subs: &mut Subs,
|
|
raw: &str,
|
|
env: &mut Env,
|
|
expected: Expected<Type>,
|
|
region: Region,
|
|
) -> (Constraint, Expr) {
|
|
// Ignore underscores.
|
|
match raw.replace("_", "").parse::<i64>() {
|
|
Ok(int) => (
|
|
constrain::int_literal(subs, expected, region),
|
|
Expr::Int(int),
|
|
),
|
|
Err(_) => {
|
|
let runtime_error = IntOutsideRange(raw.into());
|
|
|
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
|
|
|
(True, Expr::RuntimeError(runtime_error))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn hex_from_parsed<'a>(
|
|
subs: &mut Subs,
|
|
raw: &'a str,
|
|
env: &'a mut Env,
|
|
expected: Expected<Type>,
|
|
region: Region,
|
|
) -> (Constraint, Expr) {
|
|
// Ignore underscores.
|
|
match i64::from_str_radix(raw.replace("_", "").as_str(), 16) {
|
|
Ok(int) => (
|
|
constrain::int_literal(subs, expected, region),
|
|
Expr::Int(int),
|
|
),
|
|
Err(parse_err) => {
|
|
let runtime_error = InvalidHex(parse_err, raw.into());
|
|
|
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
|
|
|
(True, Expr::RuntimeError(runtime_error))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn oct_from_parsed<'a>(
|
|
subs: &mut Subs,
|
|
raw: &'a str,
|
|
env: &'a mut Env,
|
|
expected: Expected<Type>,
|
|
region: Region,
|
|
) -> (Constraint, Expr) {
|
|
// Ignore underscores.
|
|
match i64::from_str_radix(raw.replace("_", "").as_str(), 8) {
|
|
Ok(int) => (
|
|
constrain::int_literal(subs, expected, region),
|
|
Expr::Int(int),
|
|
),
|
|
Err(parse_err) => {
|
|
let runtime_error = InvalidOctal(parse_err, raw.into());
|
|
|
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
|
|
|
(True, Expr::RuntimeError(runtime_error))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn bin_from_parsed<'a>(
|
|
subs: &mut Subs,
|
|
raw: &'a str,
|
|
env: &'a mut Env,
|
|
expected: Expected<Type>,
|
|
region: Region,
|
|
) -> (Constraint, Expr) {
|
|
// Ignore underscores.
|
|
match i64::from_str_radix(raw.replace("_", "").as_str(), 2) {
|
|
Ok(int) => (
|
|
constrain::int_literal(subs, expected, region),
|
|
Expr::Int(int),
|
|
),
|
|
Err(parse_err) => {
|
|
let runtime_error = InvalidBinary(parse_err, raw.into());
|
|
|
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
|
|
|
(True, Expr::RuntimeError(runtime_error))
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Info {
|
|
pub vars: Vec<Variable>,
|
|
pub constraints: Vec<Constraint>,
|
|
pub assignment_types: ImMap<Symbol, Located<Type>>,
|
|
}
|
|
|
|
impl Info {
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
Info {
|
|
vars: Vec::with_capacity(capacity),
|
|
constraints: Vec::with_capacity(capacity),
|
|
assignment_types: ImMap::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This lets us share bound type variables between nested annotations, e.g.
|
|
///
|
|
/// blah : Map k v -> Int
|
|
/// blah mapping =
|
|
/// nested : Map k v # <-- the same k and v from the top-level annotation
|
|
/// nested = mapping
|
|
/// 42
|
|
///
|
|
/// In elm/compiler this is called RTV - the "Rigid Type Variables" dictionary.
|
|
// type BoundTypeVars = ImMap<Box<str>, Type>;
|
|
|
|
struct PatternState {
|
|
assignment_types: ImMap<Symbol, Located<Type>>,
|
|
vars: Vec<Variable>,
|
|
reversed_constraints: Vec<Constraint>,
|
|
}
|
|
|
|
impl PatternState {
|
|
pub fn add_pattern(
|
|
&mut self,
|
|
scope: &Scope,
|
|
loc_pattern: Located<ast::Pattern>,
|
|
expected: Expected<Type>,
|
|
) {
|
|
let region = loc_pattern.region;
|
|
|
|
match loc_pattern.value {
|
|
ast::Pattern::Identifier(name) => {
|
|
let symbol = scope.symbol(&name);
|
|
|
|
self.add_to_assignment_types(region, symbol, expected)
|
|
}
|
|
ast::Pattern::Underscore => (),
|
|
_ => panic!("TODO other patterns"),
|
|
}
|
|
}
|
|
|
|
fn add_to_assignment_types(
|
|
&mut self,
|
|
region: Region,
|
|
symbol: Symbol,
|
|
expected: Expected<Type>,
|
|
) {
|
|
self.assignment_types.insert(
|
|
symbol,
|
|
Located {
|
|
region,
|
|
value: expected.get_type(),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn add_pattern_to_lookup_types<'a>(
|
|
scope: &Scope,
|
|
loc_pattern: Located<ast::Pattern<'a>>,
|
|
lookup_types: &mut ImMap<Symbol, Located<Type>>,
|
|
expr_type: Type,
|
|
) {
|
|
let region = loc_pattern.region;
|
|
|
|
match loc_pattern.value {
|
|
ast::Pattern::Identifier(name) => {
|
|
let symbol = scope.symbol(&name);
|
|
let loc_type = Located {
|
|
region,
|
|
value: expr_type,
|
|
};
|
|
|
|
lookup_types.insert(symbol, loc_type);
|
|
}
|
|
_ => panic!("TODO constrain patterns other than Identifier"),
|
|
}
|
|
}
|
|
|
|
fn pattern_from_def<'a>(def: &'a Def<'a>) -> Option<&'a Located<ast::Pattern<'a>>> {
|
|
match def {
|
|
Def::Annotation(_, _) => None,
|
|
Def::Body(ref loc_pattern, _) => Some(loc_pattern),
|
|
Def::TypeAlias(_, _) => None,
|
|
Def::CustomType(_, _) => None,
|
|
Def::SpaceBefore(ref other_def, _) => pattern_from_def(other_def),
|
|
Def::SpaceAfter(ref other_def, _) => pattern_from_def(other_def),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn can_defs<'a>(
|
|
env: &mut Env,
|
|
subs: &mut Subs,
|
|
scope: Scope,
|
|
defs: &'a bumpalo::collections::Vec<'a, &'a Located<Def<'a>>>,
|
|
expected: Expected<Type>,
|
|
loc_ret: &'a Located<ast::Expr<'a>>,
|
|
) -> (Expr, Output) {
|
|
let mut scope = scope;
|
|
|
|
// Add the assigned identifiers to scope. If there's a collision, it means there
|
|
// was shadowing, which will be handled later.
|
|
let assigned_idents: Vec<(Ident, (Symbol, Region))> = idents_from_patterns(
|
|
// TODO can we get rid of this clone? It's recursively cloning expressions...
|
|
defs.iter()
|
|
.flat_map(|loc_def| pattern_from_def(&loc_def.value)),
|
|
&scope,
|
|
);
|
|
|
|
scope.idents = union_pairs(scope.idents, assigned_idents.iter());
|
|
|
|
// Used in canonicalization
|
|
let mut refs_by_assignment: MutMap<Symbol, (Located<Ident>, References)> = MutMap::default();
|
|
let mut can_assignments_by_symbol: MutMap<Symbol, (Located<Pattern>, Located<Expr>)> =
|
|
MutMap::default();
|
|
|
|
// Used in constraint generation
|
|
let rigid_info = Info::with_capacity(defs.len());
|
|
let mut flex_info = Info::with_capacity(defs.len());
|
|
let mut iter = defs.into_iter();
|
|
|
|
while let Some(loc_def) = iter.next() {
|
|
// Each assignment gets to have all the idents in scope that are assigned in this
|
|
// block. Order of assignments doesn't matter, thanks to referential transparency!
|
|
let (opt_loc_pattern, (loc_can_expr, can_output)) = match loc_def.value {
|
|
Def::Annotation(_loc_pattern, loc_annotation) => {
|
|
// TODO implement this:
|
|
//
|
|
// Is this a standalone annotation, or is it annotating the
|
|
// next def? This is annotating the next def iff:
|
|
//
|
|
// 1. There is a next def.
|
|
// 2. It is a Def::Body.
|
|
// 3. Its Pattern contains at least one SpaceBefore.
|
|
// 4. The count of all Newlines across all of its SpaceBefores is exactly 1.
|
|
//
|
|
// This tells us we're an annotation in the following scenario:
|
|
//
|
|
// foo : String
|
|
// foo = "blah"
|
|
//
|
|
// Knowing that, we then need to incorporate the annotation's type constraints
|
|
// into the next def's. To do this, we extract the next def from the iterator
|
|
// immediately, then canonicalize it to get its Variable, then use that
|
|
// Variable to generate the extra constraints.
|
|
|
|
let value = Expr::RuntimeError(NoImplementation);
|
|
let loc_expr = Located {
|
|
value,
|
|
region: loc_annotation.region,
|
|
};
|
|
|
|
(None, (loc_expr, Output::new(True)))
|
|
}
|
|
Def::Body(loc_pattern, loc_expr) => {
|
|
// Make types for the pattern and the body expr.
|
|
let expr_var = subs.mk_flex_var();
|
|
let expr_type = Type::Variable(expr_var);
|
|
let pattern_var = subs.mk_flex_var();
|
|
let pattern_type = Type::Variable(pattern_var);
|
|
|
|
flex_info.vars.push(pattern_var);
|
|
|
|
let mut state = PatternState {
|
|
assignment_types: ImMap::default(),
|
|
vars: Vec::with_capacity(1),
|
|
reversed_constraints: Vec::with_capacity(1),
|
|
};
|
|
|
|
state.add_pattern(
|
|
&scope,
|
|
loc_pattern.clone(),
|
|
NoExpectation(pattern_type.clone()),
|
|
);
|
|
state.reversed_constraints.reverse();
|
|
let def_constraint = And(state.reversed_constraints);
|
|
|
|
// 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 assigned!
|
|
add_pattern_to_lookup_types(
|
|
&scope,
|
|
loc_pattern.clone(),
|
|
&mut flex_info.assignment_types,
|
|
expr_type.clone(),
|
|
);
|
|
|
|
let (loc_can_expr, output) = canonicalize_expr(
|
|
env,
|
|
subs,
|
|
&mut scope,
|
|
loc_expr.region,
|
|
&loc_expr.value,
|
|
NoExpectation(expr_type),
|
|
);
|
|
|
|
flex_info.constraints.push(Let(Box::new(LetConstraint {
|
|
rigid_vars: Vec::new(),
|
|
flex_vars: state.vars,
|
|
assignment_types: state.assignment_types,
|
|
assignments_constraint: def_constraint,
|
|
ret_constraint: output.constraint.clone(),
|
|
})));
|
|
|
|
(Some(loc_pattern), (loc_can_expr, output))
|
|
}
|
|
Def::CustomType(_, _) => {
|
|
panic!("TODO error - custom types can only be defined at the toplevel")
|
|
}
|
|
Def::TypeAlias(_, _) => {
|
|
panic!("TODO error - type aliases can only be defined at the toplevel")
|
|
}
|
|
};
|
|
|
|
// 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.
|
|
if let Some(loc_pattern) = opt_loc_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 loc_can_pattern = canonicalize_pattern(
|
|
env,
|
|
subs,
|
|
&mut scope,
|
|
&Assignment,
|
|
&loc_pattern,
|
|
&mut shadowable_idents,
|
|
);
|
|
let mut renamed_closure_assignment: Option<&Symbol> = None;
|
|
|
|
// Give closures names (and tail-recursive status) where appropriate.
|
|
let can_expr = match (
|
|
&loc_pattern.value,
|
|
&loc_can_pattern.value,
|
|
&loc_can_expr.value,
|
|
) {
|
|
// First, make sure we are actually assigning an identifier instead of (for example) a variant.
|
|
//
|
|
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
|
|
// which also implies it's not a self tail call!
|
|
//
|
|
// Only assignments of the form (foo = ...) can be closure declarations or self tail calls.
|
|
(
|
|
&ast::Pattern::Identifier(ref name),
|
|
&Pattern::Identifier(_, ref assigned_symbol),
|
|
&FunctionPointer(_, ref symbol),
|
|
) => {
|
|
// Since everywhere in the code it'll be referred to by its assigned name,
|
|
// remove its generated name from the procedure map. (We'll re-insert it later.)
|
|
let mut procedure = env.procedures.remove(&symbol).unwrap();
|
|
let proc_var = procedure.var;
|
|
|
|
// The original ident name will be used for debugging and stack traces.
|
|
procedure.name = Some((*name).into());
|
|
|
|
// The closure is self tail recursive iff it tail calls itself (by assigned name).
|
|
procedure.is_self_tail_recursive = match &can_output.tail_call {
|
|
&None => false,
|
|
&Some(ref symbol) => symbol == assigned_symbol,
|
|
};
|
|
|
|
// Re-insert the procedure into the map, under its assigned name. This way,
|
|
// when code elsewhere calls it by assigned name, it'll resolve properly.
|
|
env.procedures.insert(assigned_symbol.clone(), procedure);
|
|
|
|
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
|
// would result in circular assignment errors!)
|
|
refs_by_assignment
|
|
.entry(assigned_symbol.clone())
|
|
.and_modify(|(_, refs)| {
|
|
refs.locals = refs.locals.without(assigned_symbol);
|
|
});
|
|
|
|
renamed_closure_assignment = Some(&assigned_symbol);
|
|
|
|
// Return a reference to the assigned symbol, since the auto-generated one no
|
|
// longer references any entry in the procedure map!
|
|
Var(proc_var, assigned_symbol.clone())
|
|
}
|
|
_ => loc_can_expr.value,
|
|
};
|
|
|
|
let mut assigned_symbols = Vec::new();
|
|
|
|
// Store the referenced locals in the refs_by_assignment map, so we can later figure out
|
|
// which assigned names reference each other.
|
|
for (ident, (symbol, region)) in
|
|
idents_from_patterns(std::iter::once(&loc_pattern), &scope)
|
|
{
|
|
let refs =
|
|
// Functions' references don't count in assignments.
|
|
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
|
|
// parent commit for the bug this fixed!
|
|
if renamed_closure_assignment == Some(&symbol) {
|
|
References::new()
|
|
} else {
|
|
can_output.references.clone()
|
|
};
|
|
|
|
refs_by_assignment.insert(
|
|
symbol.clone(),
|
|
(
|
|
Located {
|
|
value: ident,
|
|
region,
|
|
},
|
|
refs,
|
|
),
|
|
);
|
|
|
|
assigned_symbols.push(symbol.clone());
|
|
}
|
|
|
|
for symbol in assigned_symbols {
|
|
can_assignments_by_symbol.insert(
|
|
symbol,
|
|
(
|
|
loc_can_pattern.clone(),
|
|
Located {
|
|
region: loc_can_expr.region,
|
|
value: can_expr.clone(),
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The assignment as a whole is a tail call iff its return expression is a tail call.
|
|
// Use its output as a starting point because its tail_call already has the right answer!
|
|
let (ret_expr, mut output) = canonicalize_expr(
|
|
env,
|
|
subs,
|
|
&mut scope,
|
|
loc_ret.region,
|
|
&loc_ret.value,
|
|
expected,
|
|
);
|
|
|
|
let ret_con = output.constraint;
|
|
|
|
// Rigid constraint for the def expr as a whole
|
|
output.constraint = Let(Box::new(LetConstraint {
|
|
rigid_vars: rigid_info.vars,
|
|
flex_vars: Vec::new(),
|
|
assignment_types: rigid_info.assignment_types,
|
|
assignments_constraint:
|
|
// Flex constraint
|
|
Let(Box::new(LetConstraint {
|
|
rigid_vars: Vec::new(),
|
|
flex_vars: flex_info.vars,
|
|
assignment_types: flex_info.assignment_types.clone(),
|
|
assignments_constraint:
|
|
// Final flex constraints
|
|
Let(Box::new(LetConstraint {
|
|
rigid_vars: Vec::new(),
|
|
flex_vars: Vec::new(),
|
|
assignment_types: flex_info.assignment_types,
|
|
assignments_constraint: True,
|
|
ret_constraint: And(flex_info.constraints)
|
|
})),
|
|
ret_constraint: And(vec![And(rigid_info.constraints), ret_con])
|
|
})),
|
|
ret_constraint: True,
|
|
}));
|
|
|
|
// Determine the full set of references by traversing the graph.
|
|
let mut visited_symbols = MutSet::default();
|
|
|
|
// Start with the return expression's referenced locals. They are the only ones that count!
|
|
//
|
|
// If I have two assignments which reference each other, but neither of them
|
|
// is referenced in the return expression, I don't want either of them (or their references)
|
|
// to end up in the final output.references. They were unused, and so were their references!
|
|
//
|
|
// The reason we need a graph here is so we don't overlook transitive dependencies.
|
|
// For example, if I have `a = b + 1` and the assignment returns `a + 1`, then the
|
|
// assignment as a whole references both `a` *and* `b`, even though it doesn't
|
|
// directly mention `b` - because `a` depends on `b`. If we didn't traverse a graph here,
|
|
// we'd erroneously give a warning that `b` was unused since it wasn't directly referenced.
|
|
for symbol in output.references.locals.clone().into_iter() {
|
|
// Traverse the graph and look up *all* the references for this local symbol.
|
|
let refs = references_from_local(
|
|
symbol,
|
|
&mut visited_symbols,
|
|
&refs_by_assignment,
|
|
&env.procedures,
|
|
);
|
|
|
|
output.references = output.references.union(refs);
|
|
}
|
|
|
|
for symbol in output.references.calls.clone().into_iter() {
|
|
// Traverse the graph and look up *all* the references for this call.
|
|
// Reuse the same visited_symbols as before; if we already visited it, we
|
|
// won't learn anything new from visiting it again!
|
|
let refs = references_from_call(
|
|
symbol,
|
|
&mut visited_symbols,
|
|
&refs_by_assignment,
|
|
&env.procedures,
|
|
);
|
|
|
|
output.references = output.references.union(refs);
|
|
}
|
|
|
|
// Now that we've collected all the references, check to see if any of the new idents
|
|
// we defined went unused by the return expression. If any were unused, report it.
|
|
for (ident, (symbol, region)) in assigned_idents.clone() {
|
|
if !output.references.has_local(&symbol) {
|
|
let loc_ident = Located {
|
|
region: region,
|
|
value: ident.clone(),
|
|
};
|
|
|
|
env.problem(Problem::UnusedAssignment(loc_ident));
|
|
}
|
|
}
|
|
|
|
// Use topological sort to reorder the assignments based on their dependencies to one another.
|
|
// This way, during code gen, no assignment will refer to a value that hasn't been initialized yet.
|
|
// As a bonus, the topological sort also reveals any cycles between the assignments, allowing
|
|
// us to give a CircularAssignment error.
|
|
let successors = |symbol: &Symbol| -> ImSet<Symbol> {
|
|
let (_, references) = refs_by_assignment.get(symbol).unwrap();
|
|
|
|
local_successors(&references, &env.procedures)
|
|
};
|
|
|
|
let mut assigned_symbols: Vec<Symbol> = Vec::new();
|
|
|
|
for symbol in can_assignments_by_symbol.keys().into_iter() {
|
|
assigned_symbols.push(symbol.clone())
|
|
}
|
|
|
|
match topological_sort(assigned_symbols.as_slice(), successors) {
|
|
Ok(sorted_symbols) => {
|
|
let mut can_assignments = Vec::new();
|
|
|
|
for symbol in sorted_symbols
|
|
.into_iter()
|
|
// Topological sort gives us the reverse of the sorting we want!
|
|
.rev()
|
|
{
|
|
can_assignments.push(can_assignments_by_symbol.get(&symbol).unwrap().clone());
|
|
}
|
|
|
|
(
|
|
Defs(subs.mk_flex_var(), can_assignments, Box::new(ret_expr)),
|
|
output,
|
|
)
|
|
}
|
|
Err(node_in_cycle) => {
|
|
// We have one node we know is in the cycle.
|
|
// We want to show the entire cycle in the error message, so expand it out.
|
|
let mut loc_idents_in_cycle: Vec<Located<Ident>> = Vec::new();
|
|
|
|
for symbol in strongly_connected_component(&node_in_cycle, successors)
|
|
.into_iter()
|
|
// Strongly connected component gives us the reverse of the sorting we want!
|
|
.rev()
|
|
{
|
|
loc_idents_in_cycle.push(refs_by_assignment.get(&symbol).unwrap().0.clone());
|
|
}
|
|
|
|
// Sort them to make the report more helpful.
|
|
loc_idents_in_cycle = sort_cyclic_idents(
|
|
loc_idents_in_cycle,
|
|
&mut assigned_idents.iter().map(|(ident, _)| ident),
|
|
);
|
|
|
|
env.problem(Problem::CircularAssignment(loc_idents_in_cycle.clone()));
|
|
|
|
let mut regions = Vec::with_capacity(can_assignments_by_symbol.len());
|
|
|
|
for (loc_pattern, loc_expr) in can_assignments_by_symbol.values() {
|
|
regions.push((loc_pattern.region, loc_expr.region));
|
|
}
|
|
|
|
(
|
|
RuntimeError(CircularAssignment(
|
|
loc_idents_in_cycle,
|
|
regions,
|
|
ret_expr.region,
|
|
)),
|
|
output,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Args {
|
|
pub vars: Vec<Variable>,
|
|
pub typ: Type,
|
|
pub ret_type: Type,
|
|
pub ret_var: Variable,
|
|
}
|
|
|
|
fn constrain_args<'a, I>(args: I, scope: &Scope, subs: &mut Subs, state: &mut PatternState) -> Args
|
|
where
|
|
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
|
|
{
|
|
let (mut vars, arg_types) = patterns_to_variables(args.into_iter(), scope, subs, state);
|
|
|
|
let ret_var = subs.mk_flex_var();
|
|
let ret_type = Type::Variable(ret_var);
|
|
|
|
vars.push(ret_var);
|
|
|
|
let typ = Type::Function(arg_types, Box::new(ret_type.clone()));
|
|
|
|
Args {
|
|
vars,
|
|
typ,
|
|
ret_type,
|
|
ret_var,
|
|
}
|
|
}
|
|
|
|
fn patterns_to_variables<'a, I>(
|
|
patterns: I,
|
|
scope: &Scope,
|
|
subs: &mut Subs,
|
|
state: &mut PatternState,
|
|
) -> (Vec<Variable>, Vec<Type>)
|
|
where
|
|
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
|
|
{
|
|
let mut vars = Vec::with_capacity(state.vars.capacity());
|
|
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
|
|
|
for loc_pattern in patterns {
|
|
let pattern_var = subs.mk_flex_var();
|
|
let pattern_type = Type::Variable(pattern_var);
|
|
|
|
state.add_pattern(
|
|
scope,
|
|
loc_pattern.clone(),
|
|
NoExpectation(pattern_type.clone()),
|
|
);
|
|
|
|
vars.push(pattern_var);
|
|
pattern_types.push(pattern_type);
|
|
}
|
|
|
|
(vars, pattern_types)
|
|
}
|