mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
sanity check
This commit is contained in:
parent
2037133882
commit
32edd55d3d
3 changed files with 526 additions and 30 deletions
|
@ -235,7 +235,6 @@ pub fn canonicalize_defs<'a>(
|
|||
let mut scope = original_scope.clone();
|
||||
let num_defs = loc_defs.len();
|
||||
let mut refs_by_symbol = MutMap::default();
|
||||
let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher());
|
||||
|
||||
let mut type_defs = Vec::with_capacity(num_defs);
|
||||
let mut value_defs = Vec::with_capacity(num_defs);
|
||||
|
@ -508,6 +507,39 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
let mut def_order = DefOrdering::from_symbol_to_id(env.home, symbol_to_id);
|
||||
|
||||
let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher());
|
||||
|
||||
// env.home.register_debug_idents(&env.ident_ids);
|
||||
|
||||
if true {
|
||||
for pending_def in pending_value_defs.into_iter() {
|
||||
let region = pending_def.loc_pattern().region;
|
||||
|
||||
let bindings = bindings_from_patterns(std::iter::once(pending_def.loc_pattern()));
|
||||
|
||||
let temp_output = canonicalize_pending_value_def_new(
|
||||
env,
|
||||
pending_def,
|
||||
output,
|
||||
&mut scope,
|
||||
&mut can_defs_by_symbol,
|
||||
var_store,
|
||||
&mut refs_by_symbol,
|
||||
&mut aliases,
|
||||
&abilities_in_scope,
|
||||
);
|
||||
|
||||
output = temp_output.output;
|
||||
|
||||
for (symbol, _) in bindings {
|
||||
can_defs_by_symbol.insert(symbol, temp_output.def.clone());
|
||||
refs_by_symbol.insert(symbol, (region, temp_output.references.clone()));
|
||||
}
|
||||
|
||||
// TODO we should do something with these references; they include
|
||||
// things like type annotations.
|
||||
}
|
||||
} else {
|
||||
for pending_def in pending_value_defs.into_iter() {
|
||||
output = canonicalize_pending_value_def(
|
||||
env,
|
||||
|
@ -524,6 +556,7 @@ pub fn canonicalize_defs<'a>(
|
|||
// TODO we should do something with these references; they include
|
||||
// things like type annotations.
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which idents we introduced in the course of this process.
|
||||
let mut symbols_introduced = MutMap::default();
|
||||
|
@ -1203,6 +1236,12 @@ fn add_annotation_aliases(
|
|||
}
|
||||
}
|
||||
|
||||
struct TempOutput {
|
||||
output: Output,
|
||||
def: Def,
|
||||
references: References,
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
|
@ -1648,6 +1687,444 @@ fn canonicalize_pending_value_def<'a>(
|
|||
output
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn canonicalize_pending_value_def_new<'a>(
|
||||
env: &mut Env<'a>,
|
||||
pending_def: PendingValueDef<'a>,
|
||||
mut output: Output,
|
||||
scope: &mut Scope,
|
||||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||
var_store: &mut VarStore,
|
||||
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
|
||||
aliases: &mut ImMap<Symbol, Alias>,
|
||||
abilities_in_scope: &[Symbol],
|
||||
) -> TempOutput {
|
||||
use PendingValueDef::*;
|
||||
|
||||
// Make types for the body expr, even if we won't end up having a body.
|
||||
let expr_var = var_store.fresh();
|
||||
let mut vars_by_symbol = SendMap::default();
|
||||
|
||||
match pending_def {
|
||||
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
|
||||
// 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 type_annotation = canonicalize_annotation(
|
||||
env,
|
||||
scope,
|
||||
&loc_ann.value,
|
||||
loc_ann.region,
|
||||
var_store,
|
||||
abilities_in_scope,
|
||||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
|
||||
for symbol in type_annotation.references.iter() {
|
||||
output.references.insert_type_lookup(*symbol);
|
||||
}
|
||||
|
||||
add_annotation_aliases(&type_annotation, aliases);
|
||||
|
||||
output
|
||||
.introduced_variables
|
||||
.union(&type_annotation.introduced_variables);
|
||||
|
||||
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
|
||||
|
||||
let arity = type_annotation.typ.arity();
|
||||
|
||||
let problem = match &loc_can_pattern.value {
|
||||
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
|
||||
def_symbol: *symbol,
|
||||
},
|
||||
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: loc_ident.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
},
|
||||
_ => RuntimeError::NoImplementation,
|
||||
};
|
||||
|
||||
// Fabricate a body for this annotation, that will error at runtime
|
||||
let value = Expr::RuntimeError(problem);
|
||||
let is_closure = arity > 0;
|
||||
let loc_can_expr = if !is_closure {
|
||||
Loc {
|
||||
value,
|
||||
region: loc_ann.region,
|
||||
}
|
||||
} else {
|
||||
let symbol = env.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: Loc<Pattern> = Loc {
|
||||
value: Pattern::Underscore,
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
underscores.push((var_store.fresh(), underscore));
|
||||
}
|
||||
|
||||
let body_expr = Loc {
|
||||
value,
|
||||
region: loc_ann.region,
|
||||
};
|
||||
|
||||
Loc {
|
||||
value: Closure(ClosureData {
|
||||
function_type: var_store.fresh(),
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
return_type: var_store.fresh(),
|
||||
name: symbol,
|
||||
captured_symbols: Vec::new(),
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: underscores,
|
||||
loc_body: Box::new(body_expr),
|
||||
}),
|
||||
region: loc_ann.region,
|
||||
}
|
||||
};
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
Some(Loc::at(loc_ann.region, type_annotation)),
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: References::default(),
|
||||
def,
|
||||
}
|
||||
}
|
||||
|
||||
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
|
||||
let type_annotation = canonicalize_annotation(
|
||||
env,
|
||||
scope,
|
||||
&loc_ann.value,
|
||||
loc_ann.region,
|
||||
var_store,
|
||||
abilities_in_scope,
|
||||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in type_annotation.references.iter() {
|
||||
output.references.insert_type_lookup(*symbol);
|
||||
}
|
||||
|
||||
add_annotation_aliases(&type_annotation, aliases);
|
||||
|
||||
output
|
||||
.introduced_variables
|
||||
.union(&type_annotation.introduced_variables);
|
||||
|
||||
// bookkeeping for tail-call detection. If we're assigning to an
|
||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||
let outer_identifier = env.tailcallable_symbol;
|
||||
|
||||
if let Pattern::Identifier(ref defined_symbol) = &loc_can_pattern.value {
|
||||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&loc_can_pattern.value, &loc_expr.value)
|
||||
{
|
||||
env.closure_name_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
|
||||
|
||||
let (mut loc_can_expr, can_output) =
|
||||
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||
|
||||
output.references.union_mut(&can_output.references);
|
||||
|
||||
// reset the tailcallable_symbol
|
||||
env.tailcallable_symbol = outer_identifier;
|
||||
|
||||
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
|
||||
//
|
||||
// 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 defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||
if let Pattern::Identifier(symbol)
|
||||
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
|
||||
{
|
||||
if let Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
return_type,
|
||||
name: ref closure_name,
|
||||
ref arguments,
|
||||
loc_body: ref body,
|
||||
ref captured_symbols,
|
||||
..
|
||||
}) = loc_can_expr.value
|
||||
{
|
||||
// Since everywhere in the code it'll be referred to by its defined name,
|
||||
// remove its generated name from the closure map. (We'll re-insert it later.)
|
||||
let references = env.closures.remove(closure_name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
|
||||
closure_name, env.closures
|
||||
)
|
||||
});
|
||||
|
||||
// Re-insert the closure into the map, under its defined name.
|
||||
// closures don't have a name, and therefore pick a fresh symbol. But in this
|
||||
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
|
||||
// and we want to reference it by that name.
|
||||
env.closures.insert(symbol, references);
|
||||
|
||||
// The closure is self tail recursive iff it tail calls itself (by defined name).
|
||||
let is_recursive = match can_output.tail_call {
|
||||
Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
|
||||
_ => Recursive::NotRecursive,
|
||||
};
|
||||
|
||||
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
|
||||
refs.remove_value_lookup(&symbol);
|
||||
});
|
||||
|
||||
// renamed_closure_def = Some(&symbol);
|
||||
loc_can_expr.value = Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
return_type,
|
||||
name: symbol,
|
||||
captured_symbols: captured_symbols.clone(),
|
||||
recursive: is_recursive,
|
||||
arguments: arguments.clone(),
|
||||
loc_body: body.clone(),
|
||||
});
|
||||
|
||||
// Functions' references don't count in defs.
|
||||
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
|
||||
// parent commit for the bug this fixed!
|
||||
//
|
||||
// NOTE: this is where we lose reference information for closure bodies.
|
||||
let refs = References::new();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
Some(Loc::at(loc_ann.region, type_annotation)),
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
} else {
|
||||
let refs = can_output.references.clone();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
Some(Loc::at(loc_ann.region, type_annotation)),
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let refs = can_output.references.clone();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
Some(Loc::at(loc_ann.region, type_annotation)),
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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_can_pattern, loc_expr) => {
|
||||
// bookkeeping for tail-call detection. If we're assigning to an
|
||||
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
|
||||
let outer_identifier = env.tailcallable_symbol;
|
||||
|
||||
if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) =
|
||||
(&loc_pattern.value, &loc_can_pattern.value)
|
||||
{
|
||||
env.tailcallable_symbol = Some(*defined_symbol);
|
||||
|
||||
// TODO isn't types_by_symbol enough? Do we need vars_by_symbol too?
|
||||
vars_by_symbol.insert(*defined_symbol, expr_var);
|
||||
};
|
||||
|
||||
// register the name of this closure, to make sure the closure won't capture it's own name
|
||||
if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) =
|
||||
(&loc_can_pattern.value, &loc_expr.value)
|
||||
{
|
||||
env.closure_name_symbol = Some(*defined_symbol);
|
||||
};
|
||||
|
||||
let (mut loc_can_expr, can_output) =
|
||||
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||
|
||||
// reset the tailcallable_symbol
|
||||
env.tailcallable_symbol = outer_identifier;
|
||||
|
||||
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
|
||||
//
|
||||
// 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 defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
||||
if let Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
return_type,
|
||||
name: ref closure_name,
|
||||
ref arguments,
|
||||
loc_body: ref body,
|
||||
ref captured_symbols,
|
||||
..
|
||||
}) = loc_can_expr.value
|
||||
{
|
||||
// Since everywhere in the code it'll be referred to by its defined name,
|
||||
// remove its generated name from the closure map. (We'll re-insert it later.)
|
||||
let references = env.closures.remove(closure_name).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
|
||||
closure_name, env.closures
|
||||
)
|
||||
});
|
||||
|
||||
// Re-insert the closure into the map, under its defined name.
|
||||
// closures don't have a name, and therefore pick a fresh symbol. But in this
|
||||
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
|
||||
// and we want to reference it by that name.
|
||||
env.closures.insert(symbol, references);
|
||||
|
||||
// The closure is self tail recursive iff it tail calls itself (by defined name).
|
||||
let is_recursive = match can_output.tail_call {
|
||||
Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
|
||||
_ => Recursive::NotRecursive,
|
||||
};
|
||||
|
||||
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
|
||||
refs.remove_value_lookup(&symbol);
|
||||
});
|
||||
|
||||
loc_can_expr.value = Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
closure_ext_var,
|
||||
return_type,
|
||||
name: symbol,
|
||||
captured_symbols: captured_symbols.clone(),
|
||||
recursive: is_recursive,
|
||||
arguments: arguments.clone(),
|
||||
loc_body: body.clone(),
|
||||
});
|
||||
|
||||
// Functions' references don't count in defs.
|
||||
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
|
||||
// parent commit for the bug this fixed!
|
||||
let refs = References::new();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
None,
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
} else {
|
||||
let refs = can_output.references.clone();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
None,
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let refs = can_output.references.clone();
|
||||
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
expr_var,
|
||||
None,
|
||||
vars_by_symbol.clone(),
|
||||
);
|
||||
|
||||
output.union(can_output);
|
||||
|
||||
TempOutput {
|
||||
output,
|
||||
references: refs,
|
||||
def,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn can_defs_with_return<'a>(
|
||||
env: &mut Env<'a>,
|
||||
|
|
|
@ -593,6 +593,27 @@ fn top_level_constant() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||
fn top_level_destructure() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ main ] to "./platform"
|
||||
|
||||
{a, b} = { a: 1, b: 2 }
|
||||
|
||||
main =
|
||||
|
||||
a + b
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn linked_list_len_0() {
|
||||
|
@ -2971,6 +2992,7 @@ fn mix_function_and_closure_level_of_indirection() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn do_pass_bool_byte_closure_layout() {
|
||||
// see https://github.com/rtfeldman/roc/pull/1706
|
||||
|
|
|
@ -7093,25 +7093,22 @@ I need all branches in an `if` to have the same type!
|
|||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||
|
||||
Something is off with the body of the `f` definition:
|
||||
Something is off with the body of the `inner` definition:
|
||||
|
||||
1│ f : a, b, * -> *
|
||||
2│ f = \_, _, x2 ->
|
||||
3│ inner : * -> *
|
||||
4│ inner = \y -> y
|
||||
5│ inner x2
|
||||
^^^^^^^^
|
||||
^
|
||||
|
||||
The type annotation on `f` says this `inner` call should have the type:
|
||||
The type annotation on `inner` says this `y` value should have the type:
|
||||
|
||||
*
|
||||
|
||||
However, the type of this `inner` call is connected to another type in a
|
||||
However, the type of this `y` value is connected to another type in a
|
||||
way that isn't reflected in this annotation.
|
||||
|
||||
Tip: Any connection between types must use a named type variable, not
|
||||
a `*`! Maybe the annotation on `f` should have a named type variable in
|
||||
place of the `*`?
|
||||
a `*`! Maybe the annotation on `inner` should have a named type variable
|
||||
in place of the `*`?
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue