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

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

View file

@ -5,13 +5,14 @@ use crate::ability::{
use bumpalo::Bump;
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
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_collections::all::MutMap;
use roc_debug_flags::{dbg_do, ROC_VERIFY_RIGID_LET_GENERALIZED};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_problem::can::CycleEntry;
use roc_region::all::{Loc, Region};
use roc_types::solved_types::Solved;
use roc_types::subs::{
@ -89,6 +90,7 @@ pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
CircularType(Region, Symbol, ErrorType),
CircularDef(Vec<CycleEntry>),
BadType(roc_types::types::Problem),
UnexposedLookup(Symbol),
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
}
};
@ -1592,11 +1634,15 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
def_types_slice: roc_can::constraint::DefTypes,
) -> Self {
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());
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);
local_def_vars.push((symbol, Loc { value: var, region }));