Check for invalid cycles after type solving recursive defs

Disallow cycles that pass through a non-function value. Since we
evaluate eagerly, having one such cycle means there is at least one path
in the program that (likely) has unbounded recursion. Of course we can't
be certain (halting problem), but it's very likely, and avoids stuff
like #1926. Also, mono (as it's done today) won't work if things in a
cycle aren't functions.

Closes #1926
This commit is contained in:
Ayaz Hafiz 2022-05-10 16:02:10 -04:00
parent 17d8545510
commit 710a10a29c
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
17 changed files with 261 additions and 49 deletions

View file

@ -73,7 +73,13 @@ pub fn load_types(
Declaration::Declare(def) => { Declaration::Declare(def) => {
vec![def] vec![def]
} }
Declaration::DeclareRec(defs) => defs, Declaration::DeclareRec(defs, cycle_mark) => {
if cycle_mark.is_illegal(&subs) {
vec![]
} else {
defs
}
}
Declaration::Builtin(..) => { Declaration::Builtin(..) => {
unreachable!("Builtin decl in userspace module?") unreachable!("Builtin decl in userspace module?")
} }

View file

@ -5,7 +5,7 @@ use roc_collections::soa::{EitherIndex, Index, Slice};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{ExhaustiveMark, Variable}; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, Variable};
use roc_types::types::{Category, PatternCategory, Type}; use roc_types::types::{Category, PatternCategory, Type};
#[derive(Debug)] #[derive(Debug)]
@ -13,7 +13,8 @@ pub struct Constraints {
pub constraints: Vec<Constraint>, pub constraints: Vec<Constraint>,
pub types: Vec<Type>, pub types: Vec<Type>,
pub variables: Vec<Variable>, pub variables: Vec<Variable>,
pub loc_symbols: Vec<(Symbol, Region)>, pub symbols: Vec<Symbol>,
pub regions: Vec<Region>,
pub let_constraints: Vec<LetConstraint>, pub let_constraints: Vec<LetConstraint>,
pub categories: Vec<Category>, pub categories: Vec<Category>,
pub pattern_categories: Vec<PatternCategory>, pub pattern_categories: Vec<PatternCategory>,
@ -24,6 +25,7 @@ pub struct Constraints {
pub sketched_rows: Vec<SketchedRows>, pub sketched_rows: Vec<SketchedRows>,
pub eq: Vec<Eq>, pub eq: Vec<Eq>,
pub pattern_eq: Vec<PatternEq>, pub pattern_eq: Vec<PatternEq>,
pub cycles: Vec<Cycle>,
} }
impl Default for Constraints { impl Default for Constraints {
@ -37,7 +39,8 @@ impl Constraints {
let constraints = Vec::new(); let constraints = Vec::new();
let mut types = Vec::new(); let mut types = Vec::new();
let variables = Vec::new(); let variables = Vec::new();
let loc_symbols = Vec::new(); let symbols = Vec::new();
let regions = Vec::new();
let let_constraints = Vec::new(); let let_constraints = Vec::new();
let mut categories = Vec::with_capacity(16); let mut categories = Vec::with_capacity(16);
let mut pattern_categories = Vec::with_capacity(16); let mut pattern_categories = Vec::with_capacity(16);
@ -48,6 +51,7 @@ impl Constraints {
let sketched_rows = Vec::new(); let sketched_rows = Vec::new();
let eq = Vec::new(); let eq = Vec::new();
let pattern_eq = Vec::new(); let pattern_eq = Vec::new();
let cycles = Vec::new();
types.extend([ types.extend([
Type::EmptyRec, Type::EmptyRec,
@ -90,7 +94,8 @@ impl Constraints {
constraints, constraints,
types, types,
variables, variables,
loc_symbols, symbols,
regions,
let_constraints, let_constraints,
categories, categories,
pattern_categories, pattern_categories,
@ -101,6 +106,7 @@ impl Constraints {
sketched_rows, sketched_rows,
eq, eq,
pattern_eq, pattern_eq,
cycles,
} }
} }
@ -363,24 +369,28 @@ impl Constraints {
let it = it.into_iter(); let it = it.into_iter();
let types_start = self.types.len(); let types_start = self.types.len();
let loc_symbols_start = self.loc_symbols.len(); let symbols_start = self.symbols.len();
let regions_start = self.regions.len();
// because we have an ExactSizeIterator, we can reserve space here // because we have an ExactSizeIterator, we can reserve space here
let length = it.len(); let length = it.len();
self.types.reserve(length); self.types.reserve(length);
self.loc_symbols.reserve(length); self.symbols.reserve(length);
self.regions.reserve(length);
for (symbol, loc_type) in it { for (symbol, loc_type) in it {
let Loc { region, value } = loc_type; let Loc { region, value } = loc_type;
self.types.push(value); self.types.push(value);
self.loc_symbols.push((symbol, region)); self.symbols.push(symbol);
self.regions.push(region);
} }
DefTypes { DefTypes {
types: Slice::new(types_start as _, length as _), types: Slice::new(types_start as _, length as _),
loc_symbols: Slice::new(loc_symbols_start as _, length as _), symbols: Slice::new(symbols_start as _, length as _),
regions: Slice::new(regions_start as _, length as _),
} }
} }
@ -582,7 +592,8 @@ impl Constraints {
| Constraint::IncludesTag(_) | Constraint::IncludesTag(_)
| Constraint::PatternPresence(_, _, _, _) | Constraint::PatternPresence(_, _, _, _)
| Constraint::Exhaustive { .. } | Constraint::Exhaustive { .. }
| Constraint::Resolve(..) => false, | Constraint::Resolve(..)
| Constraint::CheckCycle(..) => false,
} }
} }
@ -645,6 +656,31 @@ impl Constraints {
Constraint::Exhaustive(equality, sketched_rows, context, exhaustive) Constraint::Exhaustive(equality, sketched_rows, context, exhaustive)
} }
pub fn check_cycle<I, I1, I2>(
&mut self,
symbols: I,
symbol_regions: I1,
expr_regions: I2,
cycle_mark: IllegalCycleMark,
) -> Constraint
where
I: IntoIterator<Item = Symbol>,
I1: IntoIterator<Item = Region>,
I2: IntoIterator<Item = Region>,
{
let symbols = Slice::extend_new(&mut self.symbols, symbols);
let symbol_regions = Slice::extend_new(&mut self.regions, symbol_regions);
let expr_regions = Slice::extend_new(&mut self.regions, expr_regions);
let cycle = Cycle {
symbols,
symbol_regions,
expr_regions,
};
let cycle_index = Index::push_new(&mut self.cycles, cycle);
Constraint::CheckCycle(cycle_index, cycle_mark)
}
} }
roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8); roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8);
@ -734,12 +770,14 @@ pub enum Constraint {
), ),
/// Attempt to resolve a specialization. /// Attempt to resolve a specialization.
Resolve(OpportunisticResolve), Resolve(OpportunisticResolve),
CheckCycle(Index<Cycle>, IllegalCycleMark),
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct DefTypes { pub struct DefTypes {
pub types: Slice<Type>, pub types: Slice<Type>,
pub loc_symbols: Slice<(Symbol, Region)>, pub symbols: Slice<Symbol>,
pub regions: Slice<Region>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -759,6 +797,13 @@ pub struct IncludesTag {
pub region: Region, pub region: Region,
} }
#[derive(Debug, Clone, Copy)]
pub struct Cycle {
pub symbols: Slice<Symbol>,
pub symbol_regions: Slice<Region>,
pub expr_regions: Slice<Region>,
}
/// Custom impl to limit vertical space used by the debug output /// Custom impl to limit vertical space used by the debug output
impl std::fmt::Debug for Constraint { impl std::fmt::Debug for Constraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -798,6 +843,9 @@ impl std::fmt::Debug for Constraint {
Self::Resolve(arg0) => { Self::Resolve(arg0) => {
write!(f, "Resolve({:?})", arg0) write!(f, "Resolve({:?})", arg0)
} }
Self::CheckCycle(arg0, arg1) => {
write!(f, "CheckCycle({:?}, {:?})", arg0, arg1)
}
} }
} }
} }

View file

@ -28,6 +28,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind; use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::IllegalCycleMark;
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasKind; use roc_types::types::AliasKind;
use roc_types::types::AliasVar; use roc_types::types::AliasVar;
@ -158,8 +159,10 @@ impl PendingTypeDef<'_> {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum Declaration { pub enum Declaration {
Declare(Def), Declare(Def),
DeclareRec(Vec<Def>), DeclareRec(Vec<Def>, IllegalCycleMark),
Builtin(Def), Builtin(Def),
/// If we know a cycle is illegal during canonicalization.
/// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`].
InvalidCycle(Vec<CycleEntry>), InvalidCycle(Vec<CycleEntry>),
} }
@ -168,7 +171,7 @@ impl Declaration {
use Declaration::*; use Declaration::*;
match self { match self {
Declare(_) => 1, Declare(_) => 1,
DeclareRec(defs) => defs.len(), DeclareRec(defs, _) => defs.len(),
InvalidCycle { .. } => 0, InvalidCycle { .. } => 0,
Builtin(_) => 0, Builtin(_) => 0,
} }
@ -748,6 +751,7 @@ impl DefOrdering {
#[inline(always)] #[inline(always)]
pub(crate) fn sort_can_defs( pub(crate) fn sort_can_defs(
env: &mut Env<'_>, env: &mut Env<'_>,
var_store: &mut VarStore,
defs: CanDefs, defs: CanDefs,
mut output: Output, mut output: Output,
) -> (Vec<Declaration>, Output) { ) -> (Vec<Declaration>, Output) {
@ -810,7 +814,10 @@ pub(crate) fn sort_can_defs(
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def); debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def);
// this function calls itself, and must be typechecked as a recursive def // this function calls itself, and must be typechecked as a recursive def
Declaration::DeclareRec(vec![mark_def_recursive(def)]) Declaration::DeclareRec(
vec![mark_def_recursive(def)],
IllegalCycleMark::new(var_store),
)
} else { } else {
Declaration::Declare(def) Declaration::Declare(def)
}; };
@ -827,7 +834,9 @@ pub(crate) fn sort_can_defs(
// //
// boom = \{} -> boom {} // boom = \{} -> boom {}
// //
// In general we cannot spot faulty recursion (halting problem) so this is our best attempt // In general we cannot spot faulty recursion (halting problem), so this is our
// purely-syntactic heuristic. We'll have a second attempt once we know the types in
// the cycle.
let direct_sccs = def_ordering let direct_sccs = def_ordering
.direct_references .direct_references
.strongly_connected_components_subset(group); .strongly_connected_components_subset(group);
@ -857,7 +866,7 @@ pub(crate) fn sort_can_defs(
.map(|index| mark_def_recursive(take_def!(index))) .map(|index| mark_def_recursive(take_def!(index)))
.collect(); .collect();
Declaration::DeclareRec(rec_defs) Declaration::DeclareRec(rec_defs, IllegalCycleMark::new(var_store))
}; };
declarations.push(declaration); declarations.push(declaration);
@ -1288,7 +1297,7 @@ pub fn can_defs_with_return<'a>(
} }
} }
let (declarations, output) = sort_can_defs(env, unsorted, output); let (declarations, output) = sort_can_defs(env, var_store, unsorted, output);
let mut loc_expr: Loc<Expr> = ret_expr; let mut loc_expr: Loc<Expr> = ret_expr;
@ -1305,7 +1314,9 @@ pub fn can_defs_with_return<'a>(
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Expr { fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Expr {
match decl { match decl {
Declaration::Declare(def) => Expr::LetNonRec(Box::new(def), Box::new(loc_ret)), Declaration::Declare(def) => Expr::LetNonRec(Box::new(def), Box::new(loc_ret)),
Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret)), Declaration::DeclareRec(defs, cycle_mark) => {
Expr::LetRec(defs, Box::new(loc_ret), cycle_mark)
}
Declaration::InvalidCycle(entries) => { Declaration::InvalidCycle(entries) => {
Expr::RuntimeError(RuntimeError::CircularDef(entries)) Expr::RuntimeError(RuntimeError::CircularDef(entries))
} }

View file

@ -8,7 +8,7 @@ use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{AliasKind, LambdaSet, OptAbleType, OptAbleVar, Type, TypeExtension}; use roc_types::types::{AliasKind, LambdaSet, OptAbleType, OptAbleVar, Type, TypeExtension};
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
@ -72,13 +72,19 @@ pub(crate) fn build_effect_builtins(
// Effect.forever : Effect a -> Effect b // Effect.forever : Effect a -> Effect b
if generated_functions.forever { if generated_functions.forever {
let def = helper!(build_effect_forever); let def = helper!(build_effect_forever);
declarations.push(Declaration::DeclareRec(vec![def])); declarations.push(Declaration::DeclareRec(
vec![def],
IllegalCycleMark::new(var_store),
));
} }
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b // Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b
if generated_functions.loop_ { if generated_functions.loop_ {
let def = helper!(build_effect_loop); let def = helper!(build_effect_loop);
declarations.push(Declaration::DeclareRec(vec![def])); declarations.push(Declaration::DeclareRec(
vec![def],
IllegalCycleMark::new(var_store),
));
} }
// Useful when working on functions in this module. By default symbols that we named do now // Useful when working on functions in this module. By default symbols that we named do now

View file

@ -19,7 +19,7 @@ use roc_parse::ast::{self, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*; use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::{char, u32}; use std::{char, u32};
@ -115,7 +115,7 @@ pub enum Expr {
}, },
// Let // Let
LetRec(Vec<Def>, Box<Loc<Expr>>), LetRec(Vec<Def>, Box<Loc<Expr>>, IllegalCycleMark),
LetNonRec(Box<Def>, Box<Loc<Expr>>), LetNonRec(Box<Def>, Box<Loc<Expr>>),
/// This is *only* for calling functions, not for tag application. /// This is *only* for calling functions, not for tag application.
@ -224,7 +224,7 @@ impl Expr {
&Self::AbilityMember(sym, _, _) => Category::Lookup(sym), &Self::AbilityMember(sym, _, _) => Category::Lookup(sym),
Self::When { .. } => Category::When, Self::When { .. } => Category::When,
Self::If { .. } => Category::If, Self::If { .. } => Category::If,
Self::LetRec(_, expr) => expr.value.category(), Self::LetRec(_, expr, _) => expr.value.category(),
Self::LetNonRec(_, expr) => expr.value.category(), Self::LetNonRec(_, expr) => expr.value.category(),
&Self::Call(_, _, called_via) => Category::CallResult(None, called_via), &Self::Call(_, _, called_via) => Category::CallResult(None, called_via),
&Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op), &Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op),
@ -1516,7 +1516,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
Expect(Box::new(loc_condition), Box::new(loc_expr)) Expect(Box::new(loc_condition), Box::new(loc_expr))
} }
LetRec(defs, loc_expr) => { LetRec(defs, loc_expr, mark) => {
let mut new_defs = Vec::with_capacity(defs.len()); let mut new_defs = Vec::with_capacity(defs.len());
for def in defs { for def in defs {
@ -1537,7 +1537,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
value: inline_calls(var_store, scope, loc_expr.value), value: inline_calls(var_store, scope, loc_expr.value),
}; };
LetRec(new_defs, Box::new(loc_expr)) LetRec(new_defs, Box::new(loc_expr), mark)
} }
LetNonRec(def, loc_expr) => { LetNonRec(def, loc_expr) => {

View file

@ -351,7 +351,7 @@ pub fn canonicalize_module_defs<'a>(
..Default::default() ..Default::default()
}; };
let (mut declarations, mut output) = sort_can_defs(&mut env, defs, new_output); let (mut declarations, mut output) = sort_can_defs(&mut env, var_store, defs, new_output);
let symbols_from_requires = symbols_from_requires let symbols_from_requires = symbols_from_requires
.iter() .iter()
@ -463,7 +463,7 @@ pub fn canonicalize_module_defs<'a>(
} }
} }
} }
DeclareRec(defs) => { DeclareRec(defs, _) => {
for def in defs { for def in defs {
for (symbol, _) in def.pattern_vars.iter() { for (symbol, _) in def.pattern_vars.iter() {
if exposed_but_not_defined.contains(symbol) { if exposed_but_not_defined.contains(symbol) {
@ -559,7 +559,9 @@ pub fn canonicalize_module_defs<'a>(
for declaration in declarations.iter_mut() { for declaration in declarations.iter_mut() {
match declaration { match declaration {
Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()), Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()),
DeclareRec(defs) => fix_values_captured_in_closure_defs(defs, &mut VecSet::default()), DeclareRec(defs, _) => {
fix_values_captured_in_closure_defs(defs, &mut VecSet::default())
}
InvalidCycle(_) | Builtin(_) => {} InvalidCycle(_) | Builtin(_) => {}
} }
} }
@ -667,7 +669,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_def(def, no_capture_symbols); fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
} }
LetRec(defs, loc_expr) => { LetRec(defs, loc_expr, _) => {
// LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases), // LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_defs(defs, no_capture_symbols); fix_values_captured_in_closure_defs(defs, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);

View file

@ -28,7 +28,7 @@ pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: &Declaration) {
Declaration::Declare(def) => { Declaration::Declare(def) => {
visitor.visit_def(def); visitor.visit_def(def);
} }
Declaration::DeclareRec(defs) => { Declaration::DeclareRec(defs, _cycle_mark) => {
visit_list!(visitor, visit_def, defs) visit_list!(visitor, visit_def, defs)
} }
Declaration::Builtin(def) => visitor.visit_def(def), Declaration::Builtin(def) => visitor.visit_def(def),
@ -92,7 +92,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
branch_var, branch_var,
final_else, final_else,
} => walk_if(visitor, *cond_var, branches, *branch_var, final_else), } => walk_if(visitor, *cond_var, branches, *branch_var, final_else),
Expr::LetRec(defs, body) => { Expr::LetRec(defs, body, _cycle_mark) => {
defs.iter().for_each(|def| visitor.visit_def(def)); defs.iter().for_each(|def| visitor.visit_def(def));
visitor.visit_expr(&body.value, body.region, var); visitor.visit_expr(&body.value, body.region, var);
} }

View file

@ -11,11 +11,12 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{AccessorData, AnnotatedMark, ClosureData, Field, WhenBranch}; use roc_can::expr::{AccessorData, AnnotatedMark, ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
use roc_collections::all::{HumanIndex, MutMap, SendMap}; use roc_collections::all::{HumanIndex, MutMap, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::{IllegalCycleMark, Variable};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{ use roc_types::types::{
AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField, TypeExtension, AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField, TypeExtension,
@ -897,7 +898,7 @@ pub fn constrain_expr(
cons, cons,
) )
} }
LetRec(defs, loc_ret) => { LetRec(defs, loc_ret, cycle_mark) => {
let body_con = constrain_expr( let body_con = constrain_expr(
constraints, constraints,
env, env,
@ -906,7 +907,7 @@ pub fn constrain_expr(
expected.clone(), expected.clone(),
); );
constrain_recursive_defs(constraints, env, defs, body_con) constrain_recursive_defs(constraints, env, defs, body_con, *cycle_mark)
} }
LetNonRec(def, loc_ret) => { LetNonRec(def, loc_ret) => {
let mut stack = Vec::with_capacity(1); let mut stack = Vec::with_capacity(1);
@ -1285,8 +1286,9 @@ pub fn constrain_decls(
Declaration::Declare(def) | Declaration::Builtin(def) => { Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = constrain_def(constraints, &mut env, def, constraint); constraint = constrain_def(constraints, &mut env, def, constraint);
} }
Declaration::DeclareRec(defs) => { Declaration::DeclareRec(defs, cycle_mark) => {
constraint = constrain_recursive_defs(constraints, &mut env, defs, constraint); constraint =
constrain_recursive_defs(constraints, &mut env, defs, constraint, *cycle_mark);
} }
Declaration::InvalidCycle(_) => { Declaration::InvalidCycle(_) => {
// invalid cycles give a canonicalization error. we skip them here. // invalid cycles give a canonicalization error. we skip them here.
@ -1883,6 +1885,7 @@ fn constrain_recursive_defs(
env: &mut Env, env: &mut Env,
defs: &[Def], defs: &[Def],
body_con: Constraint, body_con: Constraint,
cycle_mark: IllegalCycleMark,
) -> Constraint { ) -> Constraint {
rec_defs_help( rec_defs_help(
constraints, constraints,
@ -1891,6 +1894,7 @@ fn constrain_recursive_defs(
body_con, body_con,
Info::with_capacity(defs.len()), Info::with_capacity(defs.len()),
Info::with_capacity(defs.len()), Info::with_capacity(defs.len()),
cycle_mark,
) )
} }
@ -1901,6 +1905,7 @@ pub fn rec_defs_help(
body_con: Constraint, body_con: Constraint,
mut rigid_info: Info, mut rigid_info: Info,
mut flex_info: Info, mut flex_info: Info,
cycle_mark: IllegalCycleMark,
) -> Constraint { ) -> Constraint {
for def in defs { for def in defs {
let expr_var = def.expr_var; let expr_var = def.expr_var;
@ -2130,8 +2135,21 @@ pub fn rec_defs_help(
flex_constraints, flex_constraints,
); );
let ((symbols, symbol_regions), expr_regions): ((Vec<_>, Vec<_>), Vec<_>) = defs
.iter()
.map(|def| {
symbols_introduced_from_pattern(&def.loc_pattern)
.map(move |loc_symbol| ((loc_symbol.value, loc_symbol.region), def.loc_expr.region))
})
.flatten()
.unzip();
let cycle_constraint =
constraints.check_cycle(symbols, symbol_regions, expr_regions, cycle_mark);
let rigid_constraints = { let rigid_constraints = {
let mut temp = rigid_info.constraints; let mut temp = rigid_info.constraints;
temp.push(cycle_constraint);
temp.push(body_con); temp.push(body_con);
constraints.and_constraint(temp) constraints.and_constraint(temp)

View file

@ -4238,7 +4238,7 @@ fn build_pending_specializations<'a>(
&exposed_to_host.values, &exposed_to_host.values,
false, false,
), ),
DeclareRec(defs) => { DeclareRec(defs, cycle_mark) if !cycle_mark.is_illegal(mono_env.subs) => {
for def in defs { for def in defs {
add_def_to_module( add_def_to_module(
&mut layout_cache, &mut layout_cache,
@ -4251,7 +4251,7 @@ fn build_pending_specializations<'a>(
) )
} }
} }
InvalidCycle(_entries) => { InvalidCycle(_) | DeclareRec(..) => {
// do nothing? // do nothing?
// this may mean the loc_symbols are not defined during codegen; is that a problem? // this may mean the loc_symbols are not defined during codegen; is that a problem?
} }

View file

@ -117,7 +117,7 @@ pub fn deep_copy_type_vars_into_expr<'a>(
final_else: Box::new(final_else.map(go_help)), final_else: Box::new(final_else.map(go_help)),
}, },
LetRec(defs, body) => LetRec( LetRec(defs, body, cycle_mark) => LetRec(
defs.iter() defs.iter()
.map( .map(
|Def { |Def {
@ -139,6 +139,7 @@ pub fn deep_copy_type_vars_into_expr<'a>(
) )
.collect(), .collect(),
Box::new(body.map(go_help)), Box::new(body.map(go_help)),
*cycle_mark,
), ),
LetNonRec(def, body) => { LetNonRec(def, body) => {
let Def { let Def {

View file

@ -2190,7 +2190,7 @@ fn from_can_let<'a>(
lower_rest!(variable, new_outer) lower_rest!(variable, new_outer)
} }
LetRec(nested_defs, nested_cont) => { LetRec(nested_defs, nested_cont, cycle_mark) => {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
// We must transform // We must transform
// //
@ -2223,7 +2223,7 @@ fn from_can_let<'a>(
let new_inner = LetNonRec(Box::new(new_def), cont); let new_inner = LetNonRec(Box::new(new_def), cont);
let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner))); let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner)), cycle_mark);
lower_rest!(variable, new_outer) lower_rest!(variable, new_outer)
} }
@ -3833,7 +3833,7 @@ pub fn with_hole<'a>(
variable, variable,
Some((assigned, hole)), Some((assigned, hole)),
), ),
LetRec(defs, cont) => { LetRec(defs, cont, _cycle_mark) => {
// because Roc is strict, only functions can be recursive! // because Roc is strict, only functions can be recursive!
for def in defs.into_iter() { for def in defs.into_iter() {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
@ -5957,7 +5957,7 @@ pub fn from_can<'a>(
) )
} }
LetRec(defs, cont) => { LetRec(defs, cont, _cycle_mark) => {
// because Roc is strict, only functions can be recursive! // because Roc is strict, only functions can be recursive!
for def in defs.into_iter() { for def in defs.into_iter() {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {

View file

@ -13,6 +13,7 @@ roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_problem = { path = "../problem" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_debug_flags = { path = "../debug_flags" } roc_debug_flags = { path = "../debug_flags" }
arrayvec = "0.7.2" arrayvec = "0.7.2"

View file

@ -5,13 +5,14 @@ use crate::ability::{
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::abilities::{AbilitiesStore, MemberSpecialization}; use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::{Constraints, LetConstraint, OpportunisticResolve}; use roc_can::constraint::{Constraints, Cycle, LetConstraint, OpportunisticResolve};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_debug_flags::{dbg_do, ROC_VERIFY_RIGID_LET_GENERALIZED}; use roc_debug_flags::{dbg_do, ROC_VERIFY_RIGID_LET_GENERALIZED};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_problem::can::CycleEntry;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::solved_types::Solved; use roc_types::solved_types::Solved;
use roc_types::subs::{ use roc_types::subs::{
@ -89,6 +90,7 @@ pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>), BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>), BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
CircularType(Region, Symbol, ErrorType), CircularType(Region, Symbol, ErrorType),
CircularDef(Vec<CycleEntry>),
BadType(roc_types::types::Problem), BadType(roc_types::types::Problem),
UnexposedLookup(Symbol), UnexposedLookup(Symbol),
IncompleteAbilityImplementation(IncompleteAbilityImplementation), IncompleteAbilityImplementation(IncompleteAbilityImplementation),
@ -1393,6 +1395,46 @@ fn solve(
}); });
} }
state
}
CheckCycle(cycle, cycle_mark) => {
let Cycle {
symbols,
symbol_regions,
expr_regions,
} = &constraints.cycles[cycle.index()];
let symbols = &constraints.symbols[symbols.indices()];
let mut any_is_bad = false;
for symbol in symbols {
// If the type of a symbol is not a function, that's an error.
// Roc is strict, so only functions can be mutually recursive.
let var = env.get_var_by_symbol(symbol).expect("Symbol not solved!");
any_is_bad = any_is_bad
|| !matches!(
subs.get_content_without_compacting(var),
Content::Error | Content::Structure(FlatType::Func(..))
);
}
if any_is_bad {
let symbol_regions = &constraints.regions[symbol_regions.indices()];
let expr_regions = &constraints.regions[expr_regions.indices()];
let cycle = (symbols.iter())
.zip(symbol_regions.iter())
.zip(expr_regions.iter())
.map(|((&symbol, &symbol_region), &expr_region)| CycleEntry {
symbol,
symbol_region,
expr_region,
})
.collect();
problems.push(TypeError::CircularDef(cycle));
cycle_mark.set_illegal(subs);
}
state state
} }
}; };
@ -1592,11 +1634,15 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
def_types_slice: roc_can::constraint::DefTypes, def_types_slice: roc_can::constraint::DefTypes,
) -> Self { ) -> Self {
let types_slice = &constraints.types[def_types_slice.types.indices()]; let types_slice = &constraints.types[def_types_slice.types.indices()];
let loc_symbols_slice = &constraints.loc_symbols[def_types_slice.loc_symbols.indices()]; let symbols_slice = &constraints.symbols[def_types_slice.symbols.indices()];
let regions_slice = &constraints.regions[def_types_slice.regions.indices()];
let mut local_def_vars = Self::with_length(types_slice.len()); let mut local_def_vars = Self::with_length(types_slice.len());
for ((symbol, region), typ) in loc_symbols_slice.iter().copied().zip(types_slice) { for ((&symbol, &region), typ) in (symbols_slice.iter())
.zip(regions_slice.iter())
.zip(types_slice)
{
let var = type_to_var(subs, rank, pools, aliases, typ); let var = type_to_var(subs, rank, pools, aliases, typ);
local_def_vars.push((symbol, Loc { value: var, region })); local_def_vars.push((symbol, Loc { value: var, region }));

View file

@ -1037,6 +1037,28 @@ pub fn new_marks(var_store: &mut VarStore) -> (RedundantMark, ExhaustiveMark) {
) )
} }
/// Marks whether a recursive let-cycle was determined to be illegal during solving.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IllegalCycleMark(Variable);
impl IllegalCycleMark {
pub fn new(var_store: &mut VarStore) -> Self {
Self(var_store.fresh())
}
pub fn variable_for_introduction(&self) -> Variable {
self.0
}
pub fn set_illegal(&self, subs: &mut Subs) {
subs.set_content(self.0, Content::Error);
}
pub fn is_illegal(&self, subs: &Subs) -> bool {
matches!(subs.get_content_without_compacting(self.0), Content::Error)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Variable(u32); pub struct Variable(u32);

View file

@ -25,7 +25,7 @@ const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION";
const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME"; const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME";
const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME";
const INVALID_UNICODE: &str = "INVALID UNICODE"; const INVALID_UNICODE: &str = "INVALID UNICODE";
const CIRCULAR_DEF: &str = "CIRCULAR DEFINITION"; pub const CIRCULAR_DEF: &str = "CIRCULAR DEFINITION";
const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const DUPLICATE_NAME: &str = "DUPLICATE NAME";
const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED";
const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED";
@ -1718,7 +1718,7 @@ fn pretty_runtime_error<'b>(
(doc, title) (doc, title)
} }
fn to_circular_def_doc<'b>( pub fn to_circular_def_doc<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
lines: &LineInfo, lines: &LineInfo,
entries: &[roc_problem::can::CycleEntry], entries: &[roc_problem::can::CycleEntry],

View file

@ -1,3 +1,4 @@
use crate::error::canonicalize::{to_circular_def_doc, CIRCULAR_DEF};
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_collections::all::{HumanIndex, MutSet, SendMap};
@ -200,6 +201,18 @@ pub fn type_problem<'b>(
Some(report) Some(report)
} }
Exhaustive(problem) => Some(exhaustive_problem(alloc, lines, filename, problem)), Exhaustive(problem) => Some(exhaustive_problem(alloc, lines, filename, problem)),
CircularDef(entries) => {
let doc = to_circular_def_doc(alloc, lines, &entries);
let title = CIRCULAR_DEF.to_string();
let severity = Severity::RuntimeError;
Some(Report {
title,
filename,
doc,
severity,
})
}
} }
} }

View file

@ -9906,4 +9906,42 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn cycle_through_non_function() {
new_report_problem_as(
"cycle_through_non_function",
indoc!(
r#"
force : ({} -> I64) -> I64
force = \eval -> eval {}
t1 = \_ -> force (\_ -> t2)
t2 = t1 {}
t2
"#
),
indoc!(
r#"
CIRCULAR DEFINITION /code/proj/Main.roc
The `t1` definition is causing a very tricky infinite loop:
7 t1 = \_ -> force (\_ -> t2)
^^
The `t1` value depends on itself through the following chain of
definitions:
t1
t2
"#
),
)
}
} }