mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
Merge remote-tracking branch 'origin/trunk' into low-level-ops
This commit is contained in:
commit
1cd49689c2
29 changed files with 1630 additions and 440 deletions
26
compiler/builtins/README.md
Normal file
26
compiler/builtins/README.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# So you want to add a builtin?
|
||||||
|
|
||||||
|
Builtins are the functions and modules that are implicitly imported into every module. Some of them compile down to llvm, others need to be constructed and defined. Making a new builtin means touching many files. Here is what it takes:
|
||||||
|
|
||||||
|
### module/src/symbol.rs
|
||||||
|
|
||||||
|
Towards the bottom of a file there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
|
||||||
|
|
||||||
|
Some of these have `#` inside their name (`first#list`, #lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
|
||||||
|
|
||||||
|
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
|
||||||
|
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Int -> elem` in LLVM
|
||||||
|
- ..writing `List elem, Int -> Result elem [ OutOfBounds ]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Int` index exists.
|
||||||
|
|
||||||
|
## Bottom level LLVM values and functions
|
||||||
|
### gen/src/llvm/build.rs
|
||||||
|
This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If its simple fundamental stuff like `INT_ADD` then it certainly should be written here.
|
||||||
|
|
||||||
|
## More abstract values and functions that likely return tags.
|
||||||
|
### can/src/builtins.rs
|
||||||
|
If the function you are making is _not_ low level or returns something like a tag, then it should probably be written here by means of lower level functions written in `build.rs`.
|
||||||
|
|
||||||
|
## Letting the compiler know these functions exist
|
||||||
|
Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in these modules:
|
||||||
|
### builtins/src/std.rs
|
||||||
|
### builtins/src/unique.rs
|
|
@ -301,12 +301,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
|
SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Int module
|
// maxInt : Int
|
||||||
|
|
||||||
// highest : Int
|
|
||||||
add_type(Symbol::NUM_MAX_INT, int_type());
|
add_type(Symbol::NUM_MAX_INT, int_type());
|
||||||
|
|
||||||
// lowest : Int
|
// minInt : Int
|
||||||
add_type(Symbol::NUM_MIN_INT, int_type());
|
add_type(Symbol::NUM_MIN_INT, int_type());
|
||||||
|
|
||||||
// div : Int, Int -> Result Int [ DivByZero ]*
|
// div : Int, Int -> Result Int [ DivByZero ]*
|
||||||
|
@ -399,10 +397,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
SolvedType::Func(vec![float_type()], Box::new(float_type())),
|
SolvedType::Func(vec![float_type()], Box::new(float_type())),
|
||||||
);
|
);
|
||||||
|
|
||||||
// highest : Float
|
// maxFloat : Float
|
||||||
add_type(Symbol::NUM_MAX_FLOAT, float_type());
|
add_type(Symbol::NUM_MAX_FLOAT, float_type());
|
||||||
|
|
||||||
// lowest : Float
|
// minFloat : Float
|
||||||
add_type(Symbol::NUM_MIN_FLOAT, float_type());
|
add_type(Symbol::NUM_MIN_FLOAT, float_type());
|
||||||
|
|
||||||
// Bool module
|
// Bool module
|
||||||
|
@ -536,6 +534,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// reverse : List elem -> List elem
|
||||||
|
add_type(
|
||||||
|
Symbol::LIST_REVERSE,
|
||||||
|
SolvedType::Func(
|
||||||
|
vec![list_type(flex(TVAR1))],
|
||||||
|
Box::new(list_type(flex(TVAR1))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// len : List * -> Int
|
// len : List * -> Int
|
||||||
add_type(
|
add_type(
|
||||||
Symbol::LIST_LEN,
|
Symbol::LIST_LEN,
|
||||||
|
|
|
@ -571,6 +571,28 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// reverse : Attr * (List (Attr * a)) -> Attr * (List (Attr * a))
|
||||||
|
add_type(Symbol::LIST_REVERSE, {
|
||||||
|
let_tvars! { a, star1, star2 };
|
||||||
|
|
||||||
|
unique_function(
|
||||||
|
vec![SolvedType::Apply(
|
||||||
|
Symbol::ATTR_ATTR,
|
||||||
|
vec![
|
||||||
|
flex(star1),
|
||||||
|
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
|
||||||
|
],
|
||||||
|
)],
|
||||||
|
SolvedType::Apply(
|
||||||
|
Symbol::ATTR_ATTR,
|
||||||
|
vec![
|
||||||
|
boolean(star2),
|
||||||
|
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// To repeat an item, it must be shared!
|
// To repeat an item, it must be shared!
|
||||||
//
|
//
|
||||||
// repeat : Attr * Int
|
// repeat : Attr * Int
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
|
||||||
use crate::procedure::References;
|
use crate::procedure::References;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
|
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||||
use roc_module::ident::{Ident, Lowercase};
|
use roc_module::ident::Lowercase;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_parse::ast;
|
use roc_parse::ast;
|
||||||
use roc_parse::pattern::PatternType;
|
use roc_parse::pattern::PatternType;
|
||||||
|
@ -41,9 +41,7 @@ pub struct Annotation {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CanDefs {
|
pub struct CanDefs {
|
||||||
// TODO don't store the Ident in here (lots of cloning!) - instead,
|
pub refs_by_symbol: MutMap<Symbol, (Region, References)>,
|
||||||
// make refs_by_symbol be something like MutMap<Symbol, (Region, References)>
|
|
||||||
pub refs_by_symbol: MutMap<Symbol, (Located<Ident>, References)>,
|
|
||||||
pub can_defs_by_symbol: MutMap<Symbol, Def>,
|
pub can_defs_by_symbol: MutMap<Symbol, Def>,
|
||||||
pub aliases: SendMap<Symbol, Alias>,
|
pub aliases: SendMap<Symbol, Alias>,
|
||||||
}
|
}
|
||||||
|
@ -79,7 +77,10 @@ enum PendingDef<'a> {
|
||||||
ann: &'a Located<ast::TypeAnnotation<'a>>,
|
ann: &'a Located<ast::TypeAnnotation<'a>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
ShadowedAlias,
|
/// An invalid alias, that is ignored in the rest of the pipeline
|
||||||
|
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
|
||||||
|
/// with an incorrect pattern
|
||||||
|
InvalidAlias,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -87,11 +88,8 @@ enum PendingDef<'a> {
|
||||||
pub enum Declaration {
|
pub enum Declaration {
|
||||||
Declare(Def),
|
Declare(Def),
|
||||||
DeclareRec(Vec<Def>),
|
DeclareRec(Vec<Def>),
|
||||||
InvalidCycle(
|
|
||||||
Vec<Located<Ident>>,
|
|
||||||
Vec<(Region /* pattern */, Region /* expr */)>,
|
|
||||||
),
|
|
||||||
Builtin(Def),
|
Builtin(Def),
|
||||||
|
InvalidCycle(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Declaration {
|
impl Declaration {
|
||||||
|
@ -155,7 +153,7 @@ pub fn canonicalize_defs<'a>(
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(Located {
|
Some(Located {
|
||||||
value: Body(body_pattern, body_expr),
|
value: Body(body_pattern, body_expr),
|
||||||
..
|
region: body_region,
|
||||||
}) => {
|
}) => {
|
||||||
if pattern.value.equivalent(&body_pattern.value) {
|
if pattern.value.equivalent(&body_pattern.value) {
|
||||||
iter.next();
|
iter.next();
|
||||||
|
@ -169,8 +167,20 @@ pub fn canonicalize_defs<'a>(
|
||||||
&mut scope,
|
&mut scope,
|
||||||
pattern_type,
|
pattern_type,
|
||||||
)
|
)
|
||||||
|
} else if loc_def.region.lines_between(body_region) > 1 {
|
||||||
|
// there is a line of whitespace between the annotation and the body
|
||||||
|
// treat annotation and body separately
|
||||||
|
to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type)
|
||||||
} else {
|
} else {
|
||||||
panic!("TODO gracefully handle the case where a type annotation appears immediately before a body def, but the patterns are different. This should be an error; put a newline or comment between them!");
|
// the pattern of the annotation does not match the pattern of the body directly below it
|
||||||
|
env.problems.push(Problem::SignatureDefMismatch {
|
||||||
|
annotation_pattern: pattern.region,
|
||||||
|
def_pattern: body_pattern.region,
|
||||||
|
});
|
||||||
|
|
||||||
|
// both the annotation and definition are skipped!
|
||||||
|
iter.next();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type),
|
_ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type),
|
||||||
|
@ -551,17 +561,18 @@ pub fn sort_can_defs(
|
||||||
|
|
||||||
if is_invalid_cycle {
|
if is_invalid_cycle {
|
||||||
// We want to show the entire cycle in the error message, so expand it out.
|
// 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();
|
let mut loc_symbols = Vec::new();
|
||||||
|
|
||||||
for symbol in cycle {
|
for symbol in cycle {
|
||||||
let refs = refs_by_symbol.get(&symbol).unwrap_or_else(|| {
|
match refs_by_symbol.get(&symbol) {
|
||||||
panic!(
|
None => unreachable!(
|
||||||
"Symbol not found in refs_by_symbol: {:?} - refs_by_symbol was: {:?}",
|
r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#,
|
||||||
symbol, refs_by_symbol
|
symbol, refs_by_symbol
|
||||||
)
|
),
|
||||||
});
|
Some((region, _)) => {
|
||||||
|
loc_symbols.push(Located::at(*region, symbol));
|
||||||
loc_idents_in_cycle.push(refs.0.clone());
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||||
|
@ -569,16 +580,19 @@ pub fn sort_can_defs(
|
||||||
regions.push((def.loc_pattern.region, def.loc_expr.region));
|
regions.push((def.loc_pattern.region, def.loc_expr.region));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort them to make the report more helpful.
|
// Sort them by line number to make the report more helpful.
|
||||||
loc_idents_in_cycle.sort();
|
loc_symbols.sort();
|
||||||
regions.sort();
|
regions.sort();
|
||||||
|
|
||||||
|
let symbols_in_cycle: Vec<Symbol> =
|
||||||
|
loc_symbols.into_iter().map(|s| s.value).collect();
|
||||||
|
|
||||||
problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
|
problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
|
||||||
loc_idents_in_cycle.clone(),
|
symbols_in_cycle.clone(),
|
||||||
regions.clone(),
|
regions.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
declarations.push(Declaration::InvalidCycle(loc_idents_in_cycle, regions));
|
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
||||||
} else {
|
} else {
|
||||||
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
||||||
group_to_declaration(
|
group_to_declaration(
|
||||||
|
@ -711,6 +725,7 @@ fn pattern_to_vars_by_symbol(
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => {}
|
| UnsupportedPattern(_) => {}
|
||||||
|
|
||||||
Shadowed(_, _) => {}
|
Shadowed(_, _) => {}
|
||||||
|
@ -727,7 +742,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>,
|
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
|
||||||
aliases: &mut SendMap<Symbol, Alias>,
|
aliases: &mut SendMap<Symbol, Alias>,
|
||||||
) -> Output {
|
) -> Output {
|
||||||
use PendingDef::*;
|
use PendingDef::*;
|
||||||
|
@ -893,9 +908,8 @@ fn canonicalize_pending_def<'a>(
|
||||||
.union(&can_ann.introduced_variables);
|
.union(&can_ann.introduced_variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowedAlias => {
|
InvalidAlias => {
|
||||||
// Since this alias was shadowed, it gets ignored and has no
|
// invalid aliases (shadowed, incorrect patterns) get ignored
|
||||||
// effect on the output.
|
|
||||||
}
|
}
|
||||||
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
|
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
|
||||||
let ann =
|
let ann =
|
||||||
|
@ -995,7 +1009,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
|
|
||||||
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
|
||||||
// which defined names reference each other.
|
// which defined names reference each other.
|
||||||
for (ident, (symbol, region)) in scope.idents() {
|
for (_, (symbol, region)) in scope.idents() {
|
||||||
if !vars_by_symbol.contains_key(&symbol) {
|
if !vars_by_symbol.contains_key(&symbol) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1010,16 +1024,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
can_output.references.clone()
|
can_output.references.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
refs_by_symbol.insert(
|
refs_by_symbol.insert(*symbol, (*region, refs));
|
||||||
*symbol,
|
|
||||||
(
|
|
||||||
Located {
|
|
||||||
value: ident.clone(),
|
|
||||||
region: *region,
|
|
||||||
},
|
|
||||||
refs,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
can_defs_by_symbol.insert(
|
can_defs_by_symbol.insert(
|
||||||
*symbol,
|
*symbol,
|
||||||
|
@ -1140,23 +1145,7 @@ fn canonicalize_pending_def<'a>(
|
||||||
can_output.references.clone()
|
can_output.references.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let ident = env
|
refs_by_symbol.insert(symbol, (region, refs));
|
||||||
.ident_ids
|
|
||||||
.get_name(symbol.ident_id())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
panic!("Could not find {:?} in env.ident_ids", symbol);
|
|
||||||
});
|
|
||||||
|
|
||||||
refs_by_symbol.insert(
|
|
||||||
symbol,
|
|
||||||
(
|
|
||||||
Located {
|
|
||||||
value: ident.clone().into(),
|
|
||||||
region,
|
|
||||||
},
|
|
||||||
refs,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
can_defs_by_symbol.insert(
|
can_defs_by_symbol.insert(
|
||||||
symbol,
|
symbol,
|
||||||
|
@ -1363,7 +1352,13 @@ fn to_pending_def<'a>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("TODO gracefully handle an invalid pattern appearing where a type alias rigid var should be.");
|
// any other pattern in this position is a syntax error.
|
||||||
|
env.problems.push(Problem::InvalidAliasRigid {
|
||||||
|
alias_name: symbol,
|
||||||
|
region: loc_var.region,
|
||||||
|
});
|
||||||
|
|
||||||
|
return PendingDef::InvalidAlias;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1384,7 +1379,7 @@ fn to_pending_def<'a>(
|
||||||
shadow: loc_shadowed_symbol,
|
shadow: loc_shadowed_symbol,
|
||||||
});
|
});
|
||||||
|
|
||||||
PendingDef::ShadowedAlias
|
PendingDef::InvalidAlias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ use roc_types::subs::{VarStore, Variable};
|
||||||
use roc_types::types::Alias;
|
use roc_types::types::Alias;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::i64;
|
use std::i64;
|
||||||
use std::ops::Neg;
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
|
@ -184,12 +183,13 @@ pub fn canonicalize_expr<'a>(
|
||||||
|
|
||||||
let (expr, output) = match expr {
|
let (expr, output) = match expr {
|
||||||
ast::Expr::Num(string) => {
|
ast::Expr::Num(string) => {
|
||||||
let answer = num_expr_from_result(var_store, finish_parsing_int(*string), env);
|
let answer = num_expr_from_result(var_store, finish_parsing_int(*string), region, env);
|
||||||
|
|
||||||
(answer, Output::default())
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
ast::Expr::Float(string) => {
|
ast::Expr::Float(string) => {
|
||||||
let answer = float_expr_from_result(var_store, finish_parsing_float(string), env);
|
let answer =
|
||||||
|
float_expr_from_result(var_store, finish_parsing_float(string), region, env);
|
||||||
|
|
||||||
(answer, Output::default())
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
|
@ -630,13 +630,9 @@ pub fn canonicalize_expr<'a>(
|
||||||
base,
|
base,
|
||||||
is_negative,
|
is_negative,
|
||||||
} => {
|
} => {
|
||||||
let mut result = finish_parsing_base(string, *base);
|
// the minus sign is added before parsing, to get correct overflow/underflow behavior
|
||||||
|
let result = finish_parsing_base(string, *base, *is_negative);
|
||||||
if *is_negative {
|
let answer = int_expr_from_result(var_store, result, region, *base, env);
|
||||||
result = result.map(i64::neg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let answer = int_expr_from_result(var_store, result, env);
|
|
||||||
|
|
||||||
(answer, Output::default())
|
(answer, Output::default())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,30 @@ use crate::expr::Expr;
|
||||||
use roc_parse::ast::Base;
|
use roc_parse::ast::Base;
|
||||||
use roc_problem::can::Problem;
|
use roc_problem::can::Problem;
|
||||||
use roc_problem::can::RuntimeError::*;
|
use roc_problem::can::RuntimeError::*;
|
||||||
|
use roc_problem::can::{FloatErrorKind, IntErrorKind};
|
||||||
|
use roc_region::all::Region;
|
||||||
use roc_types::subs::VarStore;
|
use roc_types::subs::VarStore;
|
||||||
use std::i64;
|
use std::i64;
|
||||||
|
|
||||||
|
// TODO use rust's integer parsing again
|
||||||
|
//
|
||||||
|
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
|
||||||
|
// There is a nightly API for exposing the parse error.
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn num_expr_from_result(
|
pub fn num_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<i64, &str>,
|
result: Result<i64, (&str, IntErrorKind)>,
|
||||||
|
region: Region,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
match result {
|
match result {
|
||||||
Ok(int) => Expr::Num(var_store.fresh(), int),
|
Ok(int) => Expr::Num(var_store.fresh(), int),
|
||||||
Err(raw) => {
|
Err((raw, error)) => {
|
||||||
// (Num *) compiles to Int if it doesn't
|
// (Num *) compiles to Int if it doesn't
|
||||||
// get specialized to something else first,
|
// get specialized to something else first,
|
||||||
// so use int's overflow bounds here.
|
// so use int's overflow bounds here.
|
||||||
let runtime_error = IntOutsideRange(raw.into());
|
let runtime_error = InvalidInt(error, Base::Decimal, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -30,14 +38,16 @@ pub fn num_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn int_expr_from_result(
|
pub fn int_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<i64, &str>,
|
result: Result<i64, (&str, IntErrorKind)>,
|
||||||
|
region: Region,
|
||||||
|
base: Base,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
// Int stores a variable to generate better error messages
|
// Int stores a variable to generate better error messages
|
||||||
match result {
|
match result {
|
||||||
Ok(int) => Expr::Int(var_store.fresh(), int),
|
Ok(int) => Expr::Int(var_store.fresh(), int),
|
||||||
Err(raw) => {
|
Err((raw, error)) => {
|
||||||
let runtime_error = IntOutsideRange(raw.into());
|
let runtime_error = InvalidInt(error, base, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -49,14 +59,15 @@ pub fn int_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn float_expr_from_result(
|
pub fn float_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<f64, &str>,
|
result: Result<f64, (&str, FloatErrorKind)>,
|
||||||
|
region: Region,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
// Float stores a variable to generate better error messages
|
// Float stores a variable to generate better error messages
|
||||||
match result {
|
match result {
|
||||||
Ok(float) => Expr::Float(var_store.fresh(), float),
|
Ok(float) => Expr::Float(var_store.fresh(), float),
|
||||||
Err(raw) => {
|
Err((raw, error)) => {
|
||||||
let runtime_error = FloatOutsideRange(raw.into());
|
let runtime_error = InvalidFloat(error, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -66,28 +77,178 @@ pub fn float_expr_from_result(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_int(raw: &str) -> Result<i64, &str> {
|
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
|
||||||
// Ignore underscores.
|
// Ignore underscores.
|
||||||
raw.replace("_", "").parse::<i64>().map_err(|_| raw)
|
let radix = 10;
|
||||||
|
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_base(raw: &str, base: Base) -> Result<i64, &str> {
|
pub fn finish_parsing_base(
|
||||||
|
raw: &str,
|
||||||
|
base: Base,
|
||||||
|
is_negative: bool,
|
||||||
|
) -> Result<i64, (&str, IntErrorKind)> {
|
||||||
let radix = match base {
|
let radix = match base {
|
||||||
Base::Hex => 16,
|
Base::Hex => 16,
|
||||||
|
Base::Decimal => 10,
|
||||||
Base::Octal => 8,
|
Base::Octal => 8,
|
||||||
Base::Binary => 2,
|
Base::Binary => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore underscores.
|
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior
|
||||||
i64::from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|_| raw)
|
(if is_negative {
|
||||||
|
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
|
||||||
|
} else {
|
||||||
|
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
|
||||||
|
})
|
||||||
|
.map_err(|e| (raw, e.kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_float(raw: &str) -> Result<f64, &str> {
|
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
|
||||||
// Ignore underscores.
|
// Ignore underscores.
|
||||||
match raw.replace("_", "").parse::<f64>() {
|
match raw.replace("_", "").parse::<f64>() {
|
||||||
Ok(float) if float.is_finite() => Ok(float),
|
Ok(float) if float.is_finite() => Ok(float),
|
||||||
_ => Err(raw),
|
Ok(float) => {
|
||||||
|
if float.is_sign_positive() {
|
||||||
|
Err((raw, FloatErrorKind::PositiveInfinity))
|
||||||
|
} else {
|
||||||
|
Err((raw, FloatErrorKind::NegativeInfinity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err((raw, FloatErrorKind::Error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integer parsing code taken from the rust stdlib,
|
||||||
|
/// pulled in so we can give custom error messages
|
||||||
|
|
||||||
|
trait FromStrRadixHelper: PartialOrd + Copy {
|
||||||
|
fn min_value() -> Self;
|
||||||
|
fn max_value() -> Self;
|
||||||
|
fn from_u32(u: u32) -> Self;
|
||||||
|
fn checked_mul(&self, other: u32) -> Option<Self>;
|
||||||
|
fn checked_sub(&self, other: u32) -> Option<Self>;
|
||||||
|
fn checked_add(&self, other: u32) -> Option<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! doit {
|
||||||
|
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
|
||||||
|
#[inline]
|
||||||
|
fn min_value() -> Self { Self::min_value() }
|
||||||
|
#[inline]
|
||||||
|
fn max_value() -> Self { Self::max_value() }
|
||||||
|
#[inline]
|
||||||
|
fn from_u32(u: u32) -> Self { u as Self }
|
||||||
|
#[inline]
|
||||||
|
fn checked_mul(&self, other: u32) -> Option<Self> {
|
||||||
|
Self::checked_mul(*self, other as Self)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn checked_sub(&self, other: u32) -> Option<Self> {
|
||||||
|
Self::checked_sub(*self, other as Self)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn checked_add(&self, other: u32) -> Option<Self> {
|
||||||
|
Self::checked_add(*self, other as Self)
|
||||||
|
}
|
||||||
|
})*)
|
||||||
|
}
|
||||||
|
// We only need the i64 implementation, but libcore defines
|
||||||
|
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
|
||||||
|
doit! { i64 }
|
||||||
|
|
||||||
|
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
|
||||||
|
use self::IntErrorKind::*;
|
||||||
|
use self::ParseIntError as PIE;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
radix >= 2 && radix <= 36,
|
||||||
|
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
|
||||||
|
radix
|
||||||
|
);
|
||||||
|
|
||||||
|
if src.is_empty() {
|
||||||
|
return Err(PIE { kind: Empty });
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_signed_ty = T::from_u32(0) > T::min_value();
|
||||||
|
|
||||||
|
// all valid digits are ascii, so we will just iterate over the utf8 bytes
|
||||||
|
// and cast them to chars. .to_digit() will safely return None for anything
|
||||||
|
// other than a valid ascii digit for the given radix, including the first-byte
|
||||||
|
// of multi-byte sequences
|
||||||
|
let src = src.as_bytes();
|
||||||
|
|
||||||
|
let (is_positive, digits) = match src[0] {
|
||||||
|
b'+' => (true, &src[1..]),
|
||||||
|
b'-' if is_signed_ty => (false, &src[1..]),
|
||||||
|
_ => (true, src),
|
||||||
|
};
|
||||||
|
|
||||||
|
if digits.is_empty() {
|
||||||
|
return Err(PIE { kind: Empty });
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = T::from_u32(0);
|
||||||
|
if is_positive {
|
||||||
|
// The number is positive
|
||||||
|
for &c in digits {
|
||||||
|
let x = match (c as char).to_digit(radix) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => return Err(PIE { kind: InvalidDigit }),
|
||||||
|
};
|
||||||
|
result = match result.checked_mul(radix) {
|
||||||
|
Some(result) => result,
|
||||||
|
None => return Err(PIE { kind: Overflow }),
|
||||||
|
};
|
||||||
|
result = match result.checked_add(x) {
|
||||||
|
Some(result) => result,
|
||||||
|
None => return Err(PIE { kind: Overflow }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The number is negative
|
||||||
|
for &c in digits {
|
||||||
|
let x = match (c as char).to_digit(radix) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => return Err(PIE { kind: InvalidDigit }),
|
||||||
|
};
|
||||||
|
result = match result.checked_mul(radix) {
|
||||||
|
Some(result) => result,
|
||||||
|
None => return Err(PIE { kind: Underflow }),
|
||||||
|
};
|
||||||
|
result = match result.checked_sub(x) {
|
||||||
|
Some(result) => result,
|
||||||
|
None => return Err(PIE { kind: Underflow }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error which can be returned when parsing an integer.
|
||||||
|
///
|
||||||
|
/// This error is used as the error type for the `from_str_radix()` functions
|
||||||
|
/// on the primitive integer types, such as [`i8::from_str_radix`].
|
||||||
|
///
|
||||||
|
/// # Potential causes
|
||||||
|
///
|
||||||
|
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
|
||||||
|
/// in the string e.g., when it is obtained from the standard input.
|
||||||
|
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
|
||||||
|
///
|
||||||
|
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
|
||||||
|
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ParseIntError {
|
||||||
|
kind: IntErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseIntError {
|
||||||
|
/// Outputs the detailed cause of parsing an integer failing.
|
||||||
|
pub fn kind(&self) -> &IntErrorKind {
|
||||||
|
&self.kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_parse::ast;
|
use roc_parse::ast;
|
||||||
use roc_parse::pattern::PatternType;
|
use roc_parse::pattern::PatternType;
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::{VarStore, Variable};
|
use roc_types::subs::{VarStore, Variable};
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ pub enum Pattern {
|
||||||
Shadowed(Region, Located<Ident>),
|
Shadowed(Region, Located<Ident>),
|
||||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
UnsupportedPattern(Region),
|
UnsupportedPattern(Region),
|
||||||
|
// parse error patterns
|
||||||
|
MalformedPattern(MalformedPatternProblem, Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -76,6 +78,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||||
| FloatLiteral(_)
|
| FloatLiteral(_)
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => {}
|
| UnsupportedPattern(_) => {}
|
||||||
|
|
||||||
Shadowed(_, _) => {}
|
Shadowed(_, _) => {}
|
||||||
|
@ -165,32 +168,30 @@ pub fn canonicalize_pattern<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
FloatLiteral(ref string) => match pattern_type {
|
FloatLiteral(ref string) => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => match finish_parsing_float(string) {
|
||||||
let float = finish_parsing_float(string)
|
Err(_error) => {
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern"));
|
let problem = MalformedPatternProblem::MalformedFloat;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
Pattern::FloatLiteral(float)
|
}
|
||||||
}
|
Ok(float) => Pattern::FloatLiteral(float),
|
||||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
},
|
||||||
unsupported_pattern(env, ptype, region)
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Underscore => match pattern_type {
|
Underscore => match pattern_type {
|
||||||
WhenBranch | FunctionArg => Pattern::Underscore,
|
WhenBranch | FunctionArg => Pattern::Underscore,
|
||||||
ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region),
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
NumLiteral(string) => match pattern_type {
|
NumLiteral(string) => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => match finish_parsing_int(string) {
|
||||||
let int = finish_parsing_int(string)
|
Err(_error) => {
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern"));
|
let problem = MalformedPatternProblem::MalformedInt;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
Pattern::NumLiteral(var_store.fresh(), int)
|
}
|
||||||
}
|
Ok(int) => Pattern::NumLiteral(var_store.fresh(), int),
|
||||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
},
|
||||||
unsupported_pattern(env, ptype, region)
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
NonBase10Literal {
|
NonBase10Literal {
|
||||||
|
@ -198,29 +199,33 @@ pub fn canonicalize_pattern<'a>(
|
||||||
base,
|
base,
|
||||||
is_negative,
|
is_negative,
|
||||||
} => match pattern_type {
|
} => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
|
||||||
let int = finish_parsing_base(string, *base)
|
Err(_error) => {
|
||||||
.unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base));
|
let problem = MalformedPatternProblem::MalformedBase(*base);
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
if *is_negative {
|
|
||||||
Pattern::IntLiteral(-int)
|
|
||||||
} else {
|
|
||||||
Pattern::IntLiteral(int)
|
|
||||||
}
|
}
|
||||||
}
|
Ok(int) => {
|
||||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
if *is_negative {
|
||||||
unsupported_pattern(env, ptype, region)
|
Pattern::IntLiteral(-int)
|
||||||
}
|
} else {
|
||||||
|
Pattern::IntLiteral(int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
StrLiteral(_string) => match pattern_type {
|
StrLiteral(string) => match pattern_type {
|
||||||
WhenBranch => {
|
WhenBranch => {
|
||||||
panic!("TODO check whether string pattern is malformed.");
|
// TODO report whether string was malformed
|
||||||
// Pattern::StrLiteral((*string).into())
|
Pattern::StrLiteral((*string).into())
|
||||||
}
|
|
||||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
|
||||||
unsupported_pattern(env, ptype, region)
|
|
||||||
}
|
}
|
||||||
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
|
},
|
||||||
|
|
||||||
|
BlockStrLiteral(_lines) => match pattern_type {
|
||||||
|
WhenBranch => todo!("TODO block string literal pattern"),
|
||||||
|
ptype => unsupported_pattern(env, ptype, region),
|
||||||
},
|
},
|
||||||
|
|
||||||
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
||||||
|
@ -288,7 +293,7 @@ pub fn canonicalize_pattern<'a>(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => panic!("invalid pattern in record"),
|
_ => unreachable!("Any other pattern should have given a parse error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +309,15 @@ pub fn canonicalize_pattern<'a>(
|
||||||
unreachable!("should have been handled in RecordDestructure");
|
unreachable!("should have been handled in RecordDestructure");
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
|
Malformed(_str) => {
|
||||||
|
let problem = MalformedPatternProblem::Unknown;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
|
|
||||||
|
QualifiedIdentifier { .. } => {
|
||||||
|
let problem = MalformedPatternProblem::QualifiedIdentifier;
|
||||||
|
malformed_pattern(env, problem, region)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Located {
|
Located {
|
||||||
|
@ -325,6 +338,20 @@ fn unsupported_pattern<'a>(
|
||||||
Pattern::UnsupportedPattern(region)
|
Pattern::UnsupportedPattern(region)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When we detect a malformed pattern like `3.X` or `0b5`,
|
||||||
|
/// report it to Env and return an UnsupportedPattern runtime error pattern.
|
||||||
|
fn malformed_pattern<'a>(
|
||||||
|
env: &mut Env<'a>,
|
||||||
|
problem: MalformedPatternProblem,
|
||||||
|
region: Region,
|
||||||
|
) -> Pattern {
|
||||||
|
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
|
||||||
|
problem, region,
|
||||||
|
)));
|
||||||
|
|
||||||
|
Pattern::MalformedPattern(problem, region)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)>
|
pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a Located<Pattern>>,
|
I: Iterator<Item = &'a Located<Pattern>>,
|
||||||
|
@ -374,6 +401,7 @@ fn add_bindings_from_patterns(
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| Underscore
|
| Underscore
|
||||||
| Shadowed(_, _)
|
| Shadowed(_, _)
|
||||||
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_) => (),
|
| UnsupportedPattern(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ mod test_can {
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_can::expr::Expr::{self, *};
|
use roc_can::expr::Expr::{self, *};
|
||||||
use roc_can::expr::Recursive;
|
use roc_can::expr::Recursive;
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::Region;
|
||||||
use std::{f64, i64};
|
use std::{f64, i64};
|
||||||
|
|
||||||
fn assert_can(input: &str, expected: Expr) {
|
fn assert_can(input: &str, expected: Expr) {
|
||||||
|
@ -73,41 +73,65 @@ mod test_can {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_too_large() {
|
fn int_too_large() {
|
||||||
|
use roc_parse::ast::Base;
|
||||||
|
|
||||||
let string = (i64::MAX as i128 + 1).to_string();
|
let string = (i64::MAX as i128 + 1).to_string();
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::IntOutsideRange(string.into())),
|
RuntimeError(RuntimeError::InvalidInt(
|
||||||
|
IntErrorKind::Overflow,
|
||||||
|
Base::Decimal,
|
||||||
|
Region::zero(),
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_too_small() {
|
fn int_too_small() {
|
||||||
|
use roc_parse::ast::Base;
|
||||||
|
|
||||||
let string = (i64::MIN as i128 - 1).to_string();
|
let string = (i64::MIN as i128 - 1).to_string();
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::IntOutsideRange(string.into())),
|
RuntimeError(RuntimeError::InvalidInt(
|
||||||
|
IntErrorKind::Underflow,
|
||||||
|
Base::Decimal,
|
||||||
|
Region::zero(),
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_too_large() {
|
fn float_too_large() {
|
||||||
let string = format!("{}1.0", f64::MAX);
|
let string = format!("{}1.0", f64::MAX);
|
||||||
|
let region = Region::zero();
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::FloatOutsideRange(string.into())),
|
RuntimeError(RuntimeError::InvalidFloat(
|
||||||
|
FloatErrorKind::PositiveInfinity,
|
||||||
|
region,
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_too_small() {
|
fn float_too_small() {
|
||||||
let string = format!("{}1.0", f64::MIN);
|
let string = format!("{}1.0", f64::MIN);
|
||||||
|
let region = Region::zero();
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::FloatOutsideRange(string.into())),
|
RuntimeError(RuntimeError::InvalidFloat(
|
||||||
|
FloatErrorKind::NegativeInfinity,
|
||||||
|
region,
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +155,46 @@ mod test_can {
|
||||||
assert_can_float("-0.0", -0.0);
|
assert_can_float("-0.0", -0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn num_max() {
|
||||||
|
assert_can_num(&(i64::MAX.to_string()), i64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn num_min() {
|
||||||
|
assert_can_num(&(i64::MIN.to_string()), i64::MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hex_max() {
|
||||||
|
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hex_min() {
|
||||||
|
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oct_max() {
|
||||||
|
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oct_min() {
|
||||||
|
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bin_max() {
|
||||||
|
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bin_min() {
|
||||||
|
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hex_zero() {
|
fn hex_zero() {
|
||||||
assert_can_int("0x0", 0x0);
|
assert_can_int("0x0", 0x0);
|
||||||
|
@ -505,10 +569,14 @@ mod test_can {
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let home = test_home();
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut {
|
let CanExprOut {
|
||||||
loc_expr, problems, ..
|
loc_expr,
|
||||||
} = can_expr_with(&arena, test_home(), src);
|
problems,
|
||||||
|
interns,
|
||||||
|
..
|
||||||
|
} = can_expr_with(&arena, home, src);
|
||||||
|
|
||||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
||||||
{
|
{
|
||||||
|
@ -518,7 +586,7 @@ mod test_can {
|
||||||
};
|
};
|
||||||
|
|
||||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||||
vec![Located::at(Region::new(0, 0, 0, 1), "x".into())],
|
vec![interns.symbol(home, "x".into())],
|
||||||
vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))],
|
vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -537,16 +605,20 @@ mod test_can {
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
let home = test_home();
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut {
|
let CanExprOut {
|
||||||
loc_expr, problems, ..
|
loc_expr,
|
||||||
} = can_expr_with(&arena, test_home(), src);
|
problems,
|
||||||
|
interns,
|
||||||
|
..
|
||||||
|
} = can_expr_with(&arena, home, src);
|
||||||
|
|
||||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||||
vec![
|
vec![
|
||||||
Located::at(Region::new(0, 0, 0, 1), "x".into()),
|
interns.symbol(home, "x".into()),
|
||||||
Located::at(Region::new(1, 1, 0, 1), "y".into()),
|
interns.symbol(home, "y".into()),
|
||||||
Located::at(Region::new(2, 2, 0, 1), "z".into()),
|
interns.symbol(home, "z".into()),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)),
|
(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)),
|
||||||
|
|
|
@ -118,8 +118,8 @@ pub fn constrain_expr(
|
||||||
let record_type = Type::Record(
|
let record_type = Type::Record(
|
||||||
field_types,
|
field_types,
|
||||||
// TODO can we avoid doing Box::new on every single one of these?
|
// TODO can we avoid doing Box::new on every single one of these?
|
||||||
// For example, could we have a single lazy_static global Box they
|
// We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a
|
||||||
// could all share?
|
// lifetime parameter on `Type`
|
||||||
Box::new(Type::EmptyRec),
|
Box::new(Type::EmptyRec),
|
||||||
);
|
);
|
||||||
let record_con = Eq(record_type, expected.clone(), Category::Record, region);
|
let record_con = Eq(record_type, expected.clone(), Category::Record, region);
|
||||||
|
@ -600,11 +600,7 @@ pub fn constrain_expr(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check for exhaustiveness. If this `case` is non-exaustive, then:
|
// exhautiveness checking happens when converting to mono::Expr
|
||||||
//
|
|
||||||
// 1. Record a Problem.
|
|
||||||
// 2. Add an extra _ branch at the end which throws a runtime error.
|
|
||||||
|
|
||||||
exists(vec![cond_var, *expr_var], And(constraints))
|
exists(vec![cond_var, *expr_var], And(constraints))
|
||||||
}
|
}
|
||||||
Access {
|
Access {
|
||||||
|
@ -843,7 +839,6 @@ fn constrain_when_branch(
|
||||||
constraints: Vec::with_capacity(1),
|
constraints: Vec::with_capacity(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO ensure this is correct
|
|
||||||
// TODO investigate for error messages, is it better to unify all branches with a variable,
|
// TODO investigate for error messages, is it better to unify all branches with a variable,
|
||||||
// then unify that variable with the expectation?
|
// then unify that variable with the expectation?
|
||||||
for loc_pattern in &when_branch.patterns {
|
for loc_pattern in &when_branch.patterns {
|
||||||
|
@ -917,38 +912,35 @@ pub fn constrain_decls(
|
||||||
) -> Constraint {
|
) -> Constraint {
|
||||||
let mut constraint = Constraint::SaveTheEnvironment;
|
let mut constraint = Constraint::SaveTheEnvironment;
|
||||||
|
|
||||||
|
let mut env = Env {
|
||||||
|
home,
|
||||||
|
rigids: ImMap::default(),
|
||||||
|
};
|
||||||
|
|
||||||
for decl in decls.iter().rev() {
|
for decl in decls.iter().rev() {
|
||||||
// NOTE: rigids are empty because they are not shared between top-level definitions
|
// Clear the rigids from the previous iteration.
|
||||||
|
// rigids are not shared between top-level definitions
|
||||||
|
env.rigids.clear();
|
||||||
|
|
||||||
match decl {
|
match decl {
|
||||||
Declaration::Declare(def) | Declaration::Builtin(def) => {
|
Declaration::Declare(def) | Declaration::Builtin(def) => {
|
||||||
constraint = exists_with_aliases(
|
constraint = exists_with_aliases(
|
||||||
aliases.clone(),
|
aliases.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
constrain_def(
|
constrain_def(&env, def, constraint),
|
||||||
&Env {
|
|
||||||
home,
|
|
||||||
rigids: ImMap::default(),
|
|
||||||
},
|
|
||||||
def,
|
|
||||||
constraint,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Declaration::DeclareRec(defs) => {
|
Declaration::DeclareRec(defs) => {
|
||||||
constraint = exists_with_aliases(
|
constraint = exists_with_aliases(
|
||||||
aliases.clone(),
|
aliases.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
constrain_recursive_defs(
|
constrain_recursive_defs(&env, defs, constraint),
|
||||||
&Env {
|
|
||||||
home,
|
|
||||||
rigids: ImMap::default(),
|
|
||||||
},
|
|
||||||
defs,
|
|
||||||
constraint,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"),
|
Declaration::InvalidCycle(_, _) => {
|
||||||
|
// invalid cycles give a canonicalization error. we skip them here.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,8 +1007,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
expr_type,
|
expr_type,
|
||||||
annotation_expected.clone(),
|
annotation_expected.clone(),
|
||||||
Category::Storage,
|
Category::Storage,
|
||||||
// TODO proper region
|
annotation.region,
|
||||||
Region::zero(),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
constrain_expr(
|
constrain_expr(
|
||||||
|
|
|
@ -207,16 +207,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
|
||||||
Type::Variable(var)
|
Type::Variable(var)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Flex(var_id) => {
|
Flex(var_id) => Type::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)),
|
||||||
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
|
|
||||||
Type::Variable(*var)
|
|
||||||
} else {
|
|
||||||
let var = var_store.fresh();
|
|
||||||
free_vars.unnamed_vars.insert(*var_id, var);
|
|
||||||
|
|
||||||
Type::Variable(var)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Wildcard => {
|
Wildcard => {
|
||||||
let var = var_store.fresh();
|
let var = var_store.fresh();
|
||||||
free_vars.wildcards.push(var);
|
free_vars.wildcards.push(var);
|
||||||
|
@ -274,11 +265,11 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
|
||||||
}
|
}
|
||||||
Boolean(SolvedBool::SolvedShared) => Type::Boolean(Bool::Shared),
|
Boolean(SolvedBool::SolvedShared) => Type::Boolean(Bool::Shared),
|
||||||
Boolean(SolvedBool::SolvedContainer(solved_cvar, solved_mvars)) => {
|
Boolean(SolvedBool::SolvedContainer(solved_cvar, solved_mvars)) => {
|
||||||
let cvar = var_id_to_var(*solved_cvar, free_vars, var_store);
|
let cvar = var_id_to_flex_var(*solved_cvar, free_vars, var_store);
|
||||||
|
|
||||||
let mvars = solved_mvars
|
let mvars = solved_mvars
|
||||||
.iter()
|
.iter()
|
||||||
.map(|var_id| var_id_to_var(*var_id, free_vars, var_store));
|
.map(|var_id| var_id_to_flex_var(*var_id, free_vars, var_store));
|
||||||
|
|
||||||
Type::Boolean(Bool::container(cvar, mvars))
|
Type::Boolean(Bool::container(cvar, mvars))
|
||||||
}
|
}
|
||||||
|
@ -298,7 +289,11 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn var_id_to_var(var_id: VarId, free_vars: &mut FreeVars, var_store: &mut VarStore) -> Variable {
|
fn var_id_to_flex_var(
|
||||||
|
var_id: VarId,
|
||||||
|
free_vars: &mut FreeVars,
|
||||||
|
var_store: &mut VarStore,
|
||||||
|
) -> Variable {
|
||||||
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
|
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
|
||||||
*var
|
*var
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -52,6 +52,7 @@ fn headers_from_annotation_help(
|
||||||
}
|
}
|
||||||
Underscore
|
Underscore
|
||||||
| Shadowed(_, _)
|
| Shadowed(_, _)
|
||||||
|
| MalformedPattern(_, _)
|
||||||
| UnsupportedPattern(_)
|
| UnsupportedPattern(_)
|
||||||
| NumLiteral(_, _)
|
| NumLiteral(_, _)
|
||||||
| IntLiteral(_)
|
| IntLiteral(_)
|
||||||
|
@ -117,7 +118,7 @@ pub fn constrain_pattern(
|
||||||
state: &mut PatternState,
|
state: &mut PatternState,
|
||||||
) {
|
) {
|
||||||
match pattern {
|
match pattern {
|
||||||
Underscore | UnsupportedPattern(_) | Shadowed(_, _) => {
|
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
|
||||||
// Neither the _ pattern nor erroneous ones add any constraints.
|
// Neither the _ pattern nor erroneous ones add any constraints.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,24 +78,33 @@ pub fn constrain_decls(
|
||||||
sharing::annotate_usage(&def.loc_expr.value, &mut var_usage);
|
sharing::annotate_usage(&def.loc_expr.value, &mut var_usage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"),
|
Declaration::InvalidCycle(_, _) => {
|
||||||
|
// any usage of a value defined in an invalid cycle will blow up
|
||||||
|
// so for the analysis usage by such values doesn't count
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases_to_attr_type(var_store, &mut aliases);
|
aliases_to_attr_type(var_store, &mut aliases);
|
||||||
|
|
||||||
|
let mut env = Env {
|
||||||
|
home,
|
||||||
|
rigids: ImMap::default(),
|
||||||
|
};
|
||||||
|
|
||||||
for decl in decls.iter().rev() {
|
for decl in decls.iter().rev() {
|
||||||
// NOTE: rigids are empty because they are not shared between top-level definitions
|
// clear the set of rigids from the previous iteration.
|
||||||
|
// rigids are not shared between top-level definitions.
|
||||||
|
env.rigids.clear();
|
||||||
|
|
||||||
match decl {
|
match decl {
|
||||||
Declaration::Declare(def) | Declaration::Builtin(def) => {
|
Declaration::Declare(def) | Declaration::Builtin(def) => {
|
||||||
constraint = exists_with_aliases(
|
constraint = exists_with_aliases(
|
||||||
aliases.clone(),
|
aliases.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
constrain_def(
|
constrain_def(
|
||||||
&Env {
|
&env,
|
||||||
home,
|
|
||||||
rigids: ImMap::default(),
|
|
||||||
},
|
|
||||||
var_store,
|
var_store,
|
||||||
&var_usage,
|
&var_usage,
|
||||||
&mut ImSet::default(),
|
&mut ImSet::default(),
|
||||||
|
@ -109,10 +118,7 @@ pub fn constrain_decls(
|
||||||
aliases.clone(),
|
aliases.clone(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
constrain_recursive_defs(
|
constrain_recursive_defs(
|
||||||
&Env {
|
&env,
|
||||||
home,
|
|
||||||
rigids: ImMap::default(),
|
|
||||||
},
|
|
||||||
var_store,
|
var_store,
|
||||||
&var_usage,
|
&var_usage,
|
||||||
&mut ImSet::default(),
|
&mut ImSet::default(),
|
||||||
|
@ -121,7 +127,10 @@ pub fn constrain_decls(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"),
|
Declaration::InvalidCycle(_, _) => {
|
||||||
|
// invalid cycles give a canonicalization error. we skip them here.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +342,7 @@ fn constrain_pattern(
|
||||||
state.constraints.push(tag_con);
|
state.constraints.push(tag_con);
|
||||||
}
|
}
|
||||||
|
|
||||||
Underscore | Shadowed(_, _) | UnsupportedPattern(_) => {
|
Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => {
|
||||||
// no constraints
|
// no constraints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1385,10 +1394,7 @@ pub fn constrain_expr(
|
||||||
And(vec![Eq(fn_type, expected, category, region), record_con]),
|
And(vec![Eq(fn_type, expected, category, region), record_con]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RuntimeError(_) => {
|
RuntimeError(_) => True,
|
||||||
// Runtime Errors have no constraints because they're going to crash.
|
|
||||||
True
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1980,6 +1986,9 @@ fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol,
|
||||||
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
|
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that if the alias is a recursive tag union, all structures containing the
|
||||||
|
// recursion variable get the same uniqueness as the recursion variable (and thus as the
|
||||||
|
// recursive tag union itself)
|
||||||
if let Some(b) = &alias.uniqueness {
|
if let Some(b) = &alias.uniqueness {
|
||||||
fix_mutual_recursive_alias(&mut alias.typ, b);
|
fix_mutual_recursive_alias(&mut alias.typ, b);
|
||||||
}
|
}
|
||||||
|
@ -2407,10 +2416,12 @@ fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, int
|
||||||
}
|
}
|
||||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||||
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
|
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
|
||||||
tags.iter_mut()
|
|
||||||
.map(|v| v.1.iter_mut())
|
for (_tag, args) in tags.iter_mut() {
|
||||||
.flatten()
|
for arg in args.iter_mut() {
|
||||||
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
|
fix_mutual_recursive_alias_help(rec_var, attribute, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Record(fields, ext) => {
|
Record(fields, ext) => {
|
||||||
|
@ -2420,7 +2431,8 @@ fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, int
|
||||||
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
|
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
|
||||||
}
|
}
|
||||||
Alias(_, _, actual_type) => {
|
Alias(_, _, actual_type) => {
|
||||||
fix_mutual_recursive_alias_help(rec_var, attribute, actual_type);
|
// call help_help, because actual_type is not wrapped in ATTR
|
||||||
|
fix_mutual_recursive_alias_help_help(rec_var, attribute, actual_type);
|
||||||
}
|
}
|
||||||
Apply(_, args) => {
|
Apply(_, args) => {
|
||||||
args.iter_mut()
|
args.iter_mut()
|
||||||
|
|
|
@ -100,13 +100,12 @@ pub fn fmt_expr<'a>(
|
||||||
buf.push('-');
|
buf.push('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push('0');
|
match base {
|
||||||
|
Base::Hex => buf.push_str("0x"),
|
||||||
buf.push(match base {
|
Base::Octal => buf.push_str("0o"),
|
||||||
Base::Hex => 'x',
|
Base::Binary => buf.push_str("0b"),
|
||||||
Base::Octal => 'o',
|
Base::Decimal => { /* nothing */ }
|
||||||
Base::Binary => 'b',
|
}
|
||||||
});
|
|
||||||
|
|
||||||
buf.push_str(string);
|
buf.push_str(string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,13 +66,12 @@ pub fn fmt_pattern<'a>(
|
||||||
buf.push('-');
|
buf.push('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push('0');
|
match base {
|
||||||
|
Base::Hex => buf.push_str("0x"),
|
||||||
buf.push(match base {
|
Base::Octal => buf.push_str("0o"),
|
||||||
Base::Hex => 'x',
|
Base::Binary => buf.push_str("0b"),
|
||||||
Base::Octal => 'o',
|
Base::Decimal => { /* nothing */ }
|
||||||
Base::Binary => 'b',
|
}
|
||||||
});
|
|
||||||
|
|
||||||
buf.push_str(string);
|
buf.push_str(string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,94 @@ mod gen_list {
|
||||||
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
|
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_push() {
|
||||||
|
assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]);
|
||||||
|
assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]);
|
||||||
|
assert_evals_to!("List.push [] 3", &[3], &'static [i64]);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
initThrees : List Int
|
||||||
|
initThrees =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.push (List.push initThrees 3) 3
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[3, 3],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
"List.push [ True, False ] True",
|
||||||
|
&[true, false, true],
|
||||||
|
&'static [bool]
|
||||||
|
);
|
||||||
|
assert_evals_to!(
|
||||||
|
"List.push [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23",
|
||||||
|
&[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_single() {
|
||||||
|
assert_evals_to!("List.single 1", &[1], &'static [i64]);
|
||||||
|
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_repeat() {
|
||||||
|
assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]);
|
||||||
|
assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]);
|
||||||
|
|
||||||
|
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
noStrs : List Str
|
||||||
|
noStrs =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.repeat 2 noStrs
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[&[], &[]],
|
||||||
|
&'static [&'static [i64]]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
"List.repeat 15 4",
|
||||||
|
&[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_reverse() {
|
||||||
|
assert_evals_to!(
|
||||||
|
"List.reverse [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]",
|
||||||
|
&[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]);
|
||||||
|
assert_evals_to!("List.reverse [4]", &[4], &'static [i64]);
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
emptyList : List Int
|
||||||
|
emptyList =
|
||||||
|
[]
|
||||||
|
|
||||||
|
List.reverse emptyList
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
&[],
|
||||||
|
&'static [i64]
|
||||||
|
);
|
||||||
|
assert_evals_to!("List.reverse []", &[], &'static [i64]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_list_len() {
|
fn empty_list_len() {
|
||||||
assert_evals_to!("List.len []", 0, usize);
|
assert_evals_to!("List.len []", 0, usize);
|
||||||
|
@ -365,33 +453,6 @@ mod gen_list {
|
||||||
&[4, 7, 19, 21],
|
&[4, 7, 19, 21],
|
||||||
&'static [i64]
|
&'static [i64]
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list_push() {
|
|
||||||
assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]);
|
|
||||||
assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]);
|
|
||||||
assert_evals_to!("List.push [] 3", &[3], &'static [i64]);
|
|
||||||
assert_evals_to!(
|
|
||||||
"List.push [ True, False ] True",
|
|
||||||
&[true, false, true],
|
|
||||||
&'static [bool]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list_single() {
|
|
||||||
assert_evals_to!("List.single 1", &[1], &'static [i64]);
|
|
||||||
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn list_repeat() {
|
|
||||||
assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]);
|
|
||||||
assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]);
|
|
||||||
|
|
||||||
assert_evals_to!("List.repeat 0 []", &[], &'static [i64]);
|
|
||||||
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,12 +200,12 @@ mod test_uniq_load {
|
||||||
loaded_module,
|
loaded_module,
|
||||||
hashmap! {
|
hashmap! {
|
||||||
"floatTest" => "Attr Shared Float",
|
"floatTest" => "Attr Shared Float",
|
||||||
"divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * Float)",
|
"divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*)))",
|
||||||
"divisionTest" => "Attr * Float",
|
"divisionTest" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||||
"intTest" => "Attr * Int",
|
"intTest" => "Attr * Int",
|
||||||
"x" => "Attr * Float",
|
"x" => "Attr * Float",
|
||||||
"constantNum" => "Attr * (Num (Attr * *))",
|
"constantNum" => "Attr * (Num (Attr * *))",
|
||||||
"divDep1ByDep2" => "Attr * Float",
|
"divDep1ByDep2" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||||
"fromDep2" => "Attr * Float",
|
"fromDep2" => "Attr * Float",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -287,7 +287,24 @@ mod test_uniq_load {
|
||||||
"w" => "Attr * (Dep1.Identity (Attr * {}))",
|
"w" => "Attr * (Dep1.Identity (Attr * {}))",
|
||||||
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
|
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
|
||||||
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
|
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
|
||||||
"withDefault" => "Attr * (Attr (* | * | *) (Res.Res (Attr * a) (Attr * *)), Attr * a -> Attr * a)",
|
"withDefault" => "Attr * (Attr (* | b | c) (Res.Res (Attr b a) (Attr c *)), Attr b a -> Attr b a)",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_custom_res() {
|
||||||
|
test_async(async {
|
||||||
|
let subs_by_module = MutMap::default();
|
||||||
|
let loaded_module = load_fixture("interface_with_deps", "Res", subs_by_module).await;
|
||||||
|
|
||||||
|
expect_types(
|
||||||
|
loaded_module,
|
||||||
|
hashmap! {
|
||||||
|
"withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)",
|
||||||
|
"map" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr e b) -> Attr * (Res (Attr e b) (Attr d err)))",
|
||||||
|
"andThen" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr f (Res (Attr e b) (Attr d err))) -> Attr f (Res (Attr e b) (Attr d err)))",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -655,6 +655,7 @@ define_builtins! {
|
||||||
11 LIST_FIRST: "first"
|
11 LIST_FIRST: "first"
|
||||||
12 LIST_SINGLE: "single"
|
12 LIST_SINGLE: "single"
|
||||||
13 LIST_REPEAT: "repeat"
|
13 LIST_REPEAT: "repeat"
|
||||||
|
14 LIST_REVERSE: "reverse"
|
||||||
}
|
}
|
||||||
5 RESULT: "Result" => {
|
5 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||||
|
|
|
@ -438,6 +438,12 @@ fn pattern_to_when<'a>(
|
||||||
(env.unique_symbol(), Located::at_zero(RuntimeError(error)))
|
(env.unique_symbol(), Located::at_zero(RuntimeError(error)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MalformedPattern(problem, region) => {
|
||||||
|
// create the runtime error here, instead of delegating to When.
|
||||||
|
let error = roc_problem::can::RuntimeError::MalformedPattern(*problem, *region);
|
||||||
|
(env.unique_symbol(), Located::at_zero(RuntimeError(error)))
|
||||||
|
}
|
||||||
|
|
||||||
AppliedTag { .. } | RecordDestructure { .. } => {
|
AppliedTag { .. } | RecordDestructure { .. } => {
|
||||||
let symbol = env.unique_symbol();
|
let symbol = env.unique_symbol();
|
||||||
|
|
||||||
|
@ -1512,7 +1518,10 @@ fn from_can_pattern<'a>(
|
||||||
StrLiteral(v) => Pattern::StrLiteral(v.clone()),
|
StrLiteral(v) => Pattern::StrLiteral(v.clone()),
|
||||||
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()),
|
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()),
|
||||||
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region),
|
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region),
|
||||||
|
MalformedPattern(_problem, region) => {
|
||||||
|
// TODO preserve malformed problem information here?
|
||||||
|
Pattern::UnsupportedPattern(*region)
|
||||||
|
}
|
||||||
NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) {
|
NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) {
|
||||||
IntOrFloat::IntType => Pattern::IntLiteral(*num),
|
IntOrFloat::IntType => Pattern::IntLiteral(*num),
|
||||||
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64),
|
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64),
|
||||||
|
|
|
@ -346,6 +346,7 @@ pub enum Base {
|
||||||
Octal,
|
Octal,
|
||||||
Binary,
|
Binary,
|
||||||
Hex,
|
Hex,
|
||||||
|
Decimal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Pattern<'a> {
|
impl<'a> Pattern<'a> {
|
||||||
|
|
|
@ -3,6 +3,7 @@ use roc_collections::all::MutSet;
|
||||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||||
use roc_module::operator::BinOp;
|
use roc_module::operator::BinOp;
|
||||||
use roc_module::symbol::{ModuleId, Symbol};
|
use roc_module::symbol::{ModuleId, Symbol};
|
||||||
|
use roc_parse::ast::Base;
|
||||||
use roc_parse::pattern::PatternType;
|
use roc_parse::pattern::PatternType;
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
|
||||||
|
@ -46,6 +47,14 @@ pub enum Problem {
|
||||||
replaced_region: Region,
|
replaced_region: Region,
|
||||||
},
|
},
|
||||||
RuntimeError(RuntimeError),
|
RuntimeError(RuntimeError),
|
||||||
|
SignatureDefMismatch {
|
||||||
|
annotation_pattern: Region,
|
||||||
|
def_pattern: Region,
|
||||||
|
},
|
||||||
|
InvalidAliasRigid {
|
||||||
|
alias_name: Symbol,
|
||||||
|
region: Region,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -53,6 +62,35 @@ pub enum PrecedenceProblem {
|
||||||
BothNonAssociative(Region, Located<BinOp>, Located<BinOp>),
|
BothNonAssociative(Region, Located<BinOp>, Located<BinOp>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum to store the various types of errors that can cause parsing an integer to fail.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum IntErrorKind {
|
||||||
|
/// Value being parsed is empty.
|
||||||
|
///
|
||||||
|
/// Among other causes, this variant will be constructed when parsing an empty string.
|
||||||
|
Empty,
|
||||||
|
/// Contains an invalid digit.
|
||||||
|
///
|
||||||
|
/// Among other causes, this variant will be constructed when parsing a string that
|
||||||
|
/// contains a letter.
|
||||||
|
InvalidDigit,
|
||||||
|
/// Integer is too large to store in target integer type.
|
||||||
|
Overflow,
|
||||||
|
/// Integer is too small to store in target integer type.
|
||||||
|
Underflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum to store the various types of errors that can cause parsing a float to fail.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FloatErrorKind {
|
||||||
|
/// Probably an invalid digit
|
||||||
|
Error,
|
||||||
|
/// the literal is too small for f64
|
||||||
|
NegativeInfinity,
|
||||||
|
/// the literal is too large for f64
|
||||||
|
PositiveInfinity,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum RuntimeError {
|
pub enum RuntimeError {
|
||||||
Shadowing {
|
Shadowing {
|
||||||
|
@ -61,7 +99,8 @@ pub enum RuntimeError {
|
||||||
},
|
},
|
||||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
UnsupportedPattern(Region),
|
UnsupportedPattern(Region),
|
||||||
UnrecognizedFunctionName(Located<InlinableString>),
|
// Example: when 1 is 1.X -> 32
|
||||||
|
MalformedPattern(MalformedPatternProblem, Region),
|
||||||
LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
|
LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
|
||||||
ValueNotExposed {
|
ValueNotExposed {
|
||||||
module_name: InlinableString,
|
module_name: InlinableString,
|
||||||
|
@ -76,17 +115,19 @@ pub enum RuntimeError {
|
||||||
InvalidPrecedence(PrecedenceProblem, Region),
|
InvalidPrecedence(PrecedenceProblem, Region),
|
||||||
MalformedIdentifier(Box<str>, Region),
|
MalformedIdentifier(Box<str>, Region),
|
||||||
MalformedClosure(Region),
|
MalformedClosure(Region),
|
||||||
FloatOutsideRange(Box<str>),
|
InvalidFloat(FloatErrorKind, Region, Box<str>),
|
||||||
IntOutsideRange(Box<str>),
|
InvalidInt(IntErrorKind, Base, Region, Box<str>),
|
||||||
InvalidHex(std::num::ParseIntError, Box<str>),
|
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
||||||
InvalidOctal(std::num::ParseIntError, Box<str>),
|
|
||||||
InvalidBinary(std::num::ParseIntError, Box<str>),
|
|
||||||
QualifiedPatternIdent(InlinableString),
|
|
||||||
CircularDef(
|
|
||||||
Vec<Located<Ident>>,
|
|
||||||
Vec<(Region /* pattern */, Region /* expr */)>,
|
|
||||||
),
|
|
||||||
|
|
||||||
/// When the author specifies a type annotation but no implementation
|
/// When the author specifies a type annotation but no implementation
|
||||||
NoImplementation,
|
NoImplementation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum MalformedPatternProblem {
|
||||||
|
MalformedInt,
|
||||||
|
MalformedFloat,
|
||||||
|
MalformedBase(Base),
|
||||||
|
Unknown,
|
||||||
|
QualifiedIdentifier,
|
||||||
|
}
|
||||||
|
|
|
@ -78,6 +78,17 @@ impl Region {
|
||||||
Self::zero()
|
Self::zero()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lines_between(&self, other: &Region) -> u32 {
|
||||||
|
if self.end_line <= other.start_line {
|
||||||
|
other.start_line - self.end_line
|
||||||
|
} else if self.start_line >= other.end_line {
|
||||||
|
self.start_line - other.end_line
|
||||||
|
} else {
|
||||||
|
// intersection
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use roc_collections::all::MutSet;
|
use roc_collections::all::MutSet;
|
||||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||||
|
use roc_region::all::Region;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||||
|
@ -238,6 +239,29 @@ pub fn can_problem<'b>(
|
||||||
alloc.reflow(" definitions from this tag union type."),
|
alloc.reflow(" definitions from this tag union type."),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
Problem::SignatureDefMismatch {
|
||||||
|
ref annotation_pattern,
|
||||||
|
ref def_pattern,
|
||||||
|
} => alloc.stack(vec![
|
||||||
|
alloc.reflow("This annotation does not match the definition immediately following it:"),
|
||||||
|
alloc.region(Region::span_across(annotation_pattern, def_pattern)),
|
||||||
|
alloc.reflow("Is it a typo? If not, put either a newline or comment between them."),
|
||||||
|
]),
|
||||||
|
Problem::InvalidAliasRigid { alias_name, region } => alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This pattern in the definition of "),
|
||||||
|
alloc.symbol_unqualified(alias_name),
|
||||||
|
alloc.reflow(" is not what I expect:"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Only type variables like "),
|
||||||
|
alloc.type_variable("a".into()),
|
||||||
|
alloc.reflow(" or "),
|
||||||
|
alloc.type_variable("value".into()),
|
||||||
|
alloc.reflow(" can occur in this position."),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -274,13 +298,13 @@ fn pretty_runtime_error<'b>(
|
||||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||||
}
|
}
|
||||||
RuntimeError::CircularDef(mut idents, regions) => {
|
RuntimeError::CircularDef(mut symbols, regions) => {
|
||||||
let first = idents.remove(0);
|
let first = symbols.remove(0);
|
||||||
|
|
||||||
if idents.is_empty() {
|
if symbols.is_empty() {
|
||||||
alloc
|
alloc
|
||||||
.reflow("The ")
|
.reflow("The ")
|
||||||
.append(alloc.ident(first.value))
|
.append(alloc.symbol_unqualified(first))
|
||||||
.append(alloc.reflow(
|
.append(alloc.reflow(
|
||||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||||
))
|
))
|
||||||
|
@ -290,62 +314,193 @@ fn pretty_runtime_error<'b>(
|
||||||
alloc.stack(vec![
|
alloc.stack(vec![
|
||||||
alloc
|
alloc
|
||||||
.reflow("The ")
|
.reflow("The ")
|
||||||
.append(alloc.ident(first.value.clone()))
|
.append(alloc.symbol_unqualified(first))
|
||||||
.append(
|
.append(
|
||||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||||
),
|
),
|
||||||
alloc.region(regions[0].0),
|
alloc.region(regions[0].0),
|
||||||
alloc
|
alloc
|
||||||
.reflow("The ")
|
.reflow("The ")
|
||||||
.append(alloc.ident(first.value.clone()))
|
.append(alloc.symbol_unqualified(first))
|
||||||
.append(alloc.reflow(
|
.append(alloc.reflow(
|
||||||
" value depends on itself through the following chain of definitions:",
|
" value depends on itself through the following chain of definitions:",
|
||||||
)),
|
)),
|
||||||
crate::report::cycle(
|
crate::report::cycle(
|
||||||
alloc,
|
alloc,
|
||||||
4,
|
4,
|
||||||
alloc.ident(first.value),
|
alloc.symbol_unqualified(first),
|
||||||
idents
|
symbols
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|ident| alloc.ident(ident.value))
|
.map(|s| alloc.symbol_unqualified(s))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
// TODO hint?
|
// TODO hint?
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other => {
|
RuntimeError::MalformedPattern(problem, region) => {
|
||||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
use roc_parse::ast::Base;
|
||||||
// UnsupportedPattern(Region),
|
use roc_problem::can::MalformedPatternProblem::*;
|
||||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
|
||||||
// SymbolNotExposed {
|
let name = match problem {
|
||||||
// module_name: InlinableString,
|
MalformedInt => " integer ",
|
||||||
// ident: InlinableString,
|
MalformedFloat => " float ",
|
||||||
// region: Region,
|
MalformedBase(Base::Hex) => " hex integer ",
|
||||||
// },
|
MalformedBase(Base::Binary) => " binary integer ",
|
||||||
// ModuleNotImported {
|
MalformedBase(Base::Octal) => " octal integer ",
|
||||||
// module_name: InlinableString,
|
MalformedBase(Base::Decimal) => " integer ",
|
||||||
// ident: InlinableString,
|
Unknown => " ",
|
||||||
// region: Region,
|
QualifiedIdentifier => " qualified ",
|
||||||
// },
|
};
|
||||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
|
||||||
// MalformedIdentifier(Box<str>, Region),
|
let hint = match problem {
|
||||||
// MalformedClosure(Region),
|
MalformedInt | MalformedFloat | MalformedBase(_) => alloc
|
||||||
// FloatOutsideRange(Box<str>),
|
.hint()
|
||||||
// IntOutsideRange(Box<str>),
|
.append(alloc.reflow("Learn more about number literals at TODO")),
|
||||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
Unknown => alloc.nil(),
|
||||||
// InvalidOctal(std::num::ParseIntError, Box<str>),
|
QualifiedIdentifier => alloc.hint().append(
|
||||||
// InvalidBinary(std::num::ParseIntError, Box<str>),
|
alloc.reflow("In patterns, only private and global tags can be qualified"),
|
||||||
// QualifiedPatternIdent(InlinableString),
|
),
|
||||||
// CircularDef(
|
};
|
||||||
// Vec<Located<Ident>>,
|
|
||||||
// Vec<(Region /* pattern */, Region /* expr */)>,
|
alloc.stack(vec![
|
||||||
// ),
|
alloc.concat(vec![
|
||||||
//
|
alloc.reflow("This"),
|
||||||
// /// When the author specifies a type annotation but no implementation
|
alloc.text(name),
|
||||||
// NoImplementation,
|
alloc.reflow("pattern is malformed:"),
|
||||||
todo!("TODO implement run time error reporting for {:?}", other)
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
RuntimeError::UnsupportedPattern(_) => {
|
||||||
|
todo!("unsupported patterns are currently not parsed!")
|
||||||
|
}
|
||||||
|
RuntimeError::ValueNotExposed { .. } => todo!("value not exposed"),
|
||||||
|
RuntimeError::ModuleNotImported { .. } => todo!("module not imported"),
|
||||||
|
RuntimeError::InvalidPrecedence(_, _) => {
|
||||||
|
// do nothing, reported with PrecedenceProblem
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
RuntimeError::MalformedIdentifier(_, _) => {
|
||||||
|
todo!("malformed identifier, currently gives a parse error and thus is unreachable")
|
||||||
|
}
|
||||||
|
RuntimeError::MalformedClosure(_) => todo!(""),
|
||||||
|
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||||
|
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
let big_or_small = if let FloatErrorKind::PositiveInfinity = sign {
|
||||||
|
"big"
|
||||||
|
} else {
|
||||||
|
"small"
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This float literal is too "),
|
||||||
|
alloc.text(big_or_small),
|
||||||
|
alloc.reflow(":"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between"),
|
||||||
|
alloc.text(format!("{:e}", f64::MIN)),
|
||||||
|
alloc.reflow(" and "),
|
||||||
|
alloc.text(format!("{:e}", f64::MAX)),
|
||||||
|
]),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This float literal contains an invalid digit:"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
|
||||||
|
]),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidInt(IntErrorKind::Empty, _base, _region, _raw_str) => {
|
||||||
|
unreachable!("would never parse an empty int literal")
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidInt(IntErrorKind::InvalidDigit, base, region, _raw_str) => {
|
||||||
|
use roc_parse::ast::Base::*;
|
||||||
|
|
||||||
|
let name = match base {
|
||||||
|
Decimal => "integer",
|
||||||
|
Octal => "octal integer",
|
||||||
|
Hex => "hex integer",
|
||||||
|
Binary => "binary integer",
|
||||||
|
};
|
||||||
|
|
||||||
|
let plurals = match base {
|
||||||
|
Decimal => "Integer literals",
|
||||||
|
Octal => "Octal (base-8) integer literals",
|
||||||
|
Hex => "Hexadecimal (base-16) integer literals",
|
||||||
|
Binary => "Binary (base-2) integer literals",
|
||||||
|
};
|
||||||
|
|
||||||
|
let charset = match base {
|
||||||
|
Decimal => "0-9",
|
||||||
|
Octal => "0-7",
|
||||||
|
Hex => "0-9, a-f and A-F",
|
||||||
|
Binary => "0 and 1",
|
||||||
|
};
|
||||||
|
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This "),
|
||||||
|
alloc.text(name),
|
||||||
|
alloc.reflow(" literal contains an invalid digit:"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.text(plurals),
|
||||||
|
alloc.reflow(" can only contain the digits "),
|
||||||
|
alloc.text(charset),
|
||||||
|
alloc.text("."),
|
||||||
|
]),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
|
||||||
|
| RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => {
|
||||||
|
let big_or_small = if let IntErrorKind::Underflow = error_kind {
|
||||||
|
"small"
|
||||||
|
} else {
|
||||||
|
"big"
|
||||||
|
};
|
||||||
|
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This integer literal is too "),
|
||||||
|
alloc.text(big_or_small),
|
||||||
|
alloc.reflow(":"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1468,6 +1468,131 @@ mod test_reporting {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_int_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
100A -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer pattern is malformed:
|
||||||
|
|
||||||
|
2 ┆ 100A -> 3
|
||||||
|
┆ ^^^^
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_float_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
2.X -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float pattern is malformed:
|
||||||
|
|
||||||
|
2 ┆ 2.X -> 3
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_hex_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
0xZ -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This hex integer pattern is malformed:
|
||||||
|
|
||||||
|
2 ┆ 0xZ -> 3
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_oct_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
0o9 -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This octal integer pattern is malformed:
|
||||||
|
|
||||||
|
2 ┆ 0o9 -> 3
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_bin_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
0b4 -> 3
|
||||||
|
_ -> 4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This binary integer pattern is malformed:
|
||||||
|
|
||||||
|
2 ┆ 0b4 -> 3
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_fields() {
|
fn missing_fields() {
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
|
@ -1702,6 +1827,7 @@ mod test_reporting {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circular_definition_self() {
|
fn circular_definition_self() {
|
||||||
|
// invalid recursion
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -1723,6 +1849,7 @@ mod test_reporting {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circular_definition() {
|
fn circular_definition() {
|
||||||
|
// invalid mutual recursion
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -2684,6 +2811,85 @@ mod test_reporting {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annotation_definition_mismatch() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
bar : Int
|
||||||
|
foo = \x -> x
|
||||||
|
|
||||||
|
# NOTE: neither bar or foo are defined at this point
|
||||||
|
4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This annotation does not match the definition immediately following
|
||||||
|
it:
|
||||||
|
|
||||||
|
1 ┆> bar : Int
|
||||||
|
2 ┆> foo = \x -> x
|
||||||
|
|
||||||
|
Is it a typo? If not, put either a newline or comment between them.
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annotation_newline_body_is_fine() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
bar : Int
|
||||||
|
|
||||||
|
foo = \x -> x
|
||||||
|
|
||||||
|
foo bar
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(""),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_alias_rigid_var_pattern() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
MyAlias 1 : Int
|
||||||
|
|
||||||
|
4
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This pattern in the definition of `MyAlias` is not what I expect:
|
||||||
|
|
||||||
|
1 ┆ MyAlias 1 : Int
|
||||||
|
┆ ^
|
||||||
|
|
||||||
|
Only type variables like `a` or `value` can occur in this position.
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
`MyAlias` is not used anywhere in your code.
|
||||||
|
|
||||||
|
1 ┆ MyAlias 1 : Int
|
||||||
|
┆ ^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If you didn't intend on using `MyAlias` then remove it so future readers
|
||||||
|
of your code don't wonder why it is there.
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_num() {
|
fn invalid_num() {
|
||||||
report_problem_as(
|
report_problem_as(
|
||||||
|
@ -2918,4 +3124,217 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn integer_out_of_range() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 9_223_372_036_854_775_807_000
|
||||||
|
|
||||||
|
y = -9_223_372_036_854_775_807_000
|
||||||
|
|
||||||
|
h = 0x8FFF_FFFF_FFFF_FFFF
|
||||||
|
l = -0x8FFF_FFFF_FFFF_FFFF
|
||||||
|
|
||||||
|
minlit = -9_223_372_036_854_775_808
|
||||||
|
maxlit = 9_223_372_036_854_775_807
|
||||||
|
|
||||||
|
x + y + h + l + minlit + maxlit
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer literal is too small:
|
||||||
|
|
||||||
|
3 ┆ y = -9_223_372_036_854_775_807_000
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit integers, allowing values between
|
||||||
|
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer literal is too big:
|
||||||
|
|
||||||
|
5 ┆ h = 0x8FFF_FFFF_FFFF_FFFF
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit integers, allowing values between
|
||||||
|
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer literal is too small:
|
||||||
|
|
||||||
|
6 ┆ l = -0x8FFF_FFFF_FFFF_FFFF
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit integers, allowing values between
|
||||||
|
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer literal is too big:
|
||||||
|
|
||||||
|
1 ┆ x = 9_223_372_036_854_775_807_000
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit integers, allowing values between
|
||||||
|
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn float_out_of_range() {
|
||||||
|
report_problem_as(
|
||||||
|
&format!(
|
||||||
|
r#"
|
||||||
|
overflow = 1{:e}
|
||||||
|
underflow = -1{:e}
|
||||||
|
|
||||||
|
overflow + underflow
|
||||||
|
"#,
|
||||||
|
f64::MAX,
|
||||||
|
f64::MAX,
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal is too small:
|
||||||
|
|
||||||
|
3 ┆ underflow = -11.7976931348623157e308
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit floating points, allowing values
|
||||||
|
between-1.7976931348623157e308 and 1.7976931348623157e308
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal is too big:
|
||||||
|
|
||||||
|
2 ┆ overflow = 11.7976931348623157e308
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit floating points, allowing values
|
||||||
|
between-1.7976931348623157e308 and 1.7976931348623157e308
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn integer_malformed() {
|
||||||
|
// the generated messages here are incorrect. Waiting for a rust nightly feature to land,
|
||||||
|
// see https://github.com/rust-lang/rust/issues/22639
|
||||||
|
// this test is here to spot regressions in error reporting
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
dec = 100A
|
||||||
|
|
||||||
|
hex = 0xZZZ
|
||||||
|
|
||||||
|
oct = 0o9
|
||||||
|
|
||||||
|
bin = 0b2
|
||||||
|
|
||||||
|
dec + hex + oct + bin
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This hex integer literal contains an invalid digit:
|
||||||
|
|
||||||
|
3 ┆ hex = 0xZZZ
|
||||||
|
┆ ^^^^^
|
||||||
|
|
||||||
|
Hexadecimal (base-16) integer literals can only contain the digits
|
||||||
|
0-9, a-f and A-F.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This octal integer literal contains an invalid digit:
|
||||||
|
|
||||||
|
5 ┆ oct = 0o9
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Octal (base-8) integer literals can only contain the digits 0-7.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This binary integer literal contains an invalid digit:
|
||||||
|
|
||||||
|
7 ┆ bin = 0b2
|
||||||
|
┆ ^^^
|
||||||
|
|
||||||
|
Binary (base-2) integer literals can only contain the digits 0 and 1.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This integer literal contains an invalid digit:
|
||||||
|
|
||||||
|
1 ┆ dec = 100A
|
||||||
|
┆ ^^^^
|
||||||
|
|
||||||
|
Integer literals can only contain the digits 0-9.
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn float_malformed() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 3.0A
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal contains an invalid digit:
|
||||||
|
|
||||||
|
1 ┆ x = 3.0A
|
||||||
|
┆ ^^^^
|
||||||
|
|
||||||
|
Floating point literals can only contain the digits 0-9, or use
|
||||||
|
scientific notation 10e4
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1017,7 +1017,7 @@ mod solve_uniq_expr {
|
||||||
.left
|
.left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,7 +1029,7 @@ mod solve_uniq_expr {
|
||||||
\rec -> rec.left
|
\rec -> rec.left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1041,7 +1041,7 @@ mod solve_uniq_expr {
|
||||||
\{ left, right } -> { left, right }
|
\{ left, right } -> { left, right }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a | b) { left : (Attr a c), right : (Attr b d) }* -> Attr * { left : (Attr a c), right : (Attr b d) })"
|
"Attr * (Attr (* | b | d) { left : (Attr b a), right : (Attr d c) }* -> Attr * { left : (Attr b a), right : (Attr d c) })"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1068,7 +1068,7 @@ mod solve_uniq_expr {
|
||||||
),
|
),
|
||||||
// NOTE: Foo loses the relation to the uniqueness attribute `a`
|
// NOTE: Foo loses the relation to the uniqueness attribute `a`
|
||||||
// That is fine. Whenever we try to extract from it, the relation will be enforced
|
// That is fine. Whenever we try to extract from it, the relation will be enforced
|
||||||
"Attr * (Attr (* | a) [ Foo (Attr a b) ]* -> Attr * [ Foo (Attr a b) ]*)",
|
"Attr * (Attr (* | b) [ Foo (Attr b a) ]* -> Attr * [ Foo (Attr b a) ]*)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1083,8 +1083,7 @@ mod solve_uniq_expr {
|
||||||
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
|
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
|
||||||
// i.e. the `b` could be ignored in this example, is that true in general?
|
// i.e. the `b` could be ignored in this example, is that true in general?
|
||||||
// seems like it because we don't really extract anything.
|
// seems like it because we don't really extract anything.
|
||||||
// "Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)"
|
"Attr * (Attr (* | b | c) [ Foo (Attr b a) (Attr c *) ]* -> Attr * [ Foo (Attr b a) (Attr * Str) ]*)"
|
||||||
"Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,7 +1136,7 @@ mod solve_uniq_expr {
|
||||||
\{ left } -> left
|
\{ left } -> left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1149,7 +1148,7 @@ mod solve_uniq_expr {
|
||||||
\{ left } -> left
|
\{ left } -> left
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1164,7 +1163,7 @@ mod solve_uniq_expr {
|
||||||
numIdentity
|
numIdentity
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))",
|
"Attr * (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,7 +1178,7 @@ mod solve_uniq_expr {
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1194,7 +1193,7 @@ mod solve_uniq_expr {
|
||||||
x
|
x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)",
|
"Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1212,7 +1211,7 @@ mod solve_uniq_expr {
|
||||||
{ numIdentity, p, q }
|
{ numIdentity, p, q }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * { numIdentity : (Attr Shared (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }"
|
"Attr * { numIdentity : (Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1228,7 +1227,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)",
|
"Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1244,7 +1243,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)",
|
"Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1263,7 +1262,7 @@ mod solve_uniq_expr {
|
||||||
p)
|
p)
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)"
|
"Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1279,7 +1278,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)",
|
"Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1291,7 +1290,7 @@ mod solve_uniq_expr {
|
||||||
\r -> { r & x: r.x, y: r.x }
|
\r -> { r & x: r.x, y: r.x }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)"
|
"Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1307,7 +1306,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)"
|
"Attr * (Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e -> Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,7 +1324,7 @@ mod solve_uniq_expr {
|
||||||
r
|
r
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)"
|
"Attr * (Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d -> Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,7 +1343,7 @@ mod solve_uniq_expr {
|
||||||
s
|
s
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)",
|
"Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1360,8 +1359,7 @@ mod solve_uniq_expr {
|
||||||
r.tic.tac.toe
|
r.tic.tac.toe
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (d | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
|
"Attr * (Attr (* | b | c | d | e | f) { foo : (Attr (d | b | c) { bar : (Attr (c | b) { baz : (Attr b a) }*) }*), tic : (Attr (f | b | e) { tac : (Attr (e | b) { toe : (Attr b a) }*) }*) }* -> Attr b a)"
|
||||||
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (a | b | d) { bar : (Attr (d | b) { baz : (Attr b f) }*) }*), tic : (Attr (c | b | e) { tac : (Attr (e | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1694,7 +1692,7 @@ mod solve_uniq_expr {
|
||||||
map
|
map
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a) [ Cons (Attr a b) (Attr (d | a) e), Nil ]* as e -> Attr f [ Cons c (Attr f g), Nil ]* as g)" ,
|
"Attr Shared (Attr Shared (Attr b a -> c), Attr (e | b) [ Cons (Attr b a) (Attr (e | b) d), Nil ]* as d -> Attr g [ Cons c (Attr g f), Nil ]* as f)" ,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1735,7 +1733,7 @@ mod solve_uniq_expr {
|
||||||
map
|
map
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr Shared (Attr a [ S (Attr a b), Z ]* as b -> Attr c [ S (Attr c d), Z ]* as d)",
|
"Attr Shared (Attr b [ S (Attr b a), Z ]* as a -> Attr d [ S (Attr d c), Z ]* as c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1874,30 +1872,76 @@ mod solve_uniq_expr {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn assoc_list_map() {
|
fn alias_assoc_list_head() {
|
||||||
// infer_eq(
|
infer_eq(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// ConsList a : [ Cons a (ConsList a), Nil ]
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
// AssocList a b : ConsList { key: a, value : b }
|
AssocList a b : ConsList { key: a, value : b }
|
||||||
//
|
Maybe a : [ Just a, Nothing ]
|
||||||
// map : AssocList k v -> AssocList k v
|
|
||||||
// map = \list ->
|
# AssocList2 a b : [ Cons { key: a, value : b } (AssocList2 a b), Nil ]
|
||||||
// when list is
|
|
||||||
// Cons { key, value } xs ->
|
head : AssocList k v -> Maybe { key: k , value: v }
|
||||||
// Cons {key: key, value: value } xs
|
head = \alist ->
|
||||||
//
|
when alist is
|
||||||
// Nil ->
|
Cons first _ ->
|
||||||
// Nil
|
Just first
|
||||||
//
|
|
||||||
// map
|
Nil ->
|
||||||
// "#
|
Nothing
|
||||||
// ),
|
|
||||||
// // "Attr Shared (Attr Shared (Attr Shared k, Attr a v -> Attr b v2), Attr (c | d | e) (AssocList (Attr Shared k) (Attr a v)) -> Attr (c | d | e) (AssocList (Attr Shared k) (Attr b v2)))"
|
head
|
||||||
// "Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr (* | a) (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))",
|
"#
|
||||||
// );
|
),
|
||||||
// }
|
"Attr * (Attr (* | c) (AssocList (Attr a k) (Attr b v)) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cons_list_as_assoc_list_head() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
|
Maybe a : [ Just a, Nothing ]
|
||||||
|
|
||||||
|
head : ConsList { key: k, value: v } -> Maybe { key: k , value: v }
|
||||||
|
head = \alist ->
|
||||||
|
when alist is
|
||||||
|
Cons first _ ->
|
||||||
|
Just first
|
||||||
|
|
||||||
|
Nil ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
head
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * (Attr (* | c) (ConsList (Attr c { key : (Attr a k), value : (Attr b v) })) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assoc_list_map() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
|
|
||||||
|
map : ConsList a -> ConsList a
|
||||||
|
map = \list ->
|
||||||
|
when list is
|
||||||
|
Cons r xs -> Cons r xs
|
||||||
|
Nil -> Nil
|
||||||
|
|
||||||
|
map
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr * (Attr (c | b) (ConsList (Attr b a)) -> Attr (c | b) (ConsList (Attr b a)))",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn same_uniqueness_builtin_list() {
|
fn same_uniqueness_builtin_list() {
|
||||||
|
@ -1965,6 +2009,40 @@ mod solve_uniq_expr {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typecheck_triple_mutually_recursive_tag_union() {
|
||||||
|
infer_eq(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
ListA a b : [ Cons a (ListB b a), Nil ]
|
||||||
|
ListB a b : [ Cons a (ListC b a), Nil ]
|
||||||
|
ListC a b : [ Cons a (ListA b a), Nil ]
|
||||||
|
|
||||||
|
ConsList q : [ Cons q (ConsList q), Nil ]
|
||||||
|
|
||||||
|
toAs : (q -> p), ListA p q -> ConsList p
|
||||||
|
toAs =
|
||||||
|
\f, lista ->
|
||||||
|
when lista is
|
||||||
|
Nil -> Nil
|
||||||
|
Cons a listb ->
|
||||||
|
when listb is
|
||||||
|
Nil -> Nil
|
||||||
|
Cons b listc ->
|
||||||
|
when listc is
|
||||||
|
Nil ->
|
||||||
|
Nil
|
||||||
|
|
||||||
|
Cons c newListA ->
|
||||||
|
Cons a (Cons (f b) (Cons c (toAs f newListA)))
|
||||||
|
|
||||||
|
toAs
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Attr Shared (Attr Shared (Attr a q -> Attr b p), Attr (* | a | b) (ListA (Attr b p) (Attr a q)) -> Attr * (ConsList (Attr b p)))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn infer_mutually_recursive_tag_union() {
|
fn infer_mutually_recursive_tag_union() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
|
@ -1982,7 +2060,7 @@ mod solve_uniq_expr {
|
||||||
toAs
|
toAs
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a | e) [ Cons (Attr e f) (Attr (d | a | e) [ Cons (Attr a b) (Attr (d | a | e) g), Nil ]*), Nil ]* as g -> Attr h [ Cons (Attr e f) (Attr * [ Cons c (Attr h i) ]*), Nil ]* as i)"
|
"Attr Shared (Attr Shared (Attr b a -> c), Attr (g | b | e) [ Cons (Attr e d) (Attr (g | b | e) [ Cons (Attr b a) (Attr (g | b | e) f), Nil ]*), Nil ]* as f -> Attr i [ Cons (Attr e d) (Attr * [ Cons c (Attr i h) ]*), Nil ]* as h)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2016,10 +2094,10 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Num.maxFloat / Num.maxFloat
|
Num.maxFloat / Num.maxFloat
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * Float",
|
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2028,10 +2106,10 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
3.0 / 4.0
|
3.0 / 4.0
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * Float",
|
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2040,10 +2118,10 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
3.0 / Num.maxFloat
|
3.0 / Num.maxFloat
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * Float",
|
"Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2052,8 +2130,8 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Num.maxInt // Num.maxInt
|
Num.maxInt // Num.maxInt
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
|
@ -2064,8 +2142,8 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
3 // 4
|
3 // 4
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
|
@ -2076,8 +2154,8 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
3 // Num.maxInt
|
3 // Num.maxInt
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
|
||||||
);
|
);
|
||||||
|
@ -2088,12 +2166,12 @@ mod solve_uniq_expr {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
\list ->
|
\list ->
|
||||||
p = List.get list 1
|
p = List.get list 1
|
||||||
q = List.get list 1
|
q = List.get list 1
|
||||||
|
|
||||||
{ p, q }
|
{ p, q }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })"
|
"Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })"
|
||||||
);
|
);
|
||||||
|
@ -2113,7 +2191,7 @@ mod solve_uniq_expr {
|
||||||
list
|
list
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a (List (Attr Shared (Num (Attr Shared b)))) -> Attr a (List (Attr Shared (Num (Attr Shared b)))))",
|
"Attr * (Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2129,7 +2207,7 @@ mod solve_uniq_expr {
|
||||||
List.set list 0 42
|
List.set list 0 42
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (a | b) (List (Attr b (Num (Attr c d)))) -> Attr (a | b) (List (Attr b (Num (Attr c d)))))",
|
"Attr * (Attr (d | c) (List (Attr c (Num (Attr b a)))) -> Attr (d | c) (List (Attr c (Num (Attr b a)))))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2151,7 +2229,7 @@ mod solve_uniq_expr {
|
||||||
sum
|
sum
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) (List (Attr a (Num (Attr a b)))) -> Attr c (Num (Attr c b)))",
|
"Attr * (Attr (* | b) (List (Attr b (Num (Attr b a)))) -> Attr c (Num (Attr c a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2159,7 +2237,7 @@ mod solve_uniq_expr {
|
||||||
fn num_add() {
|
fn num_add() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
"Num.add",
|
"Num.add",
|
||||||
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))",
|
"Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2175,12 +2253,12 @@ mod solve_uniq_expr {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_get() {
|
fn list_get() {
|
||||||
infer_eq("List.get", "Attr * (Attr (* | a) (List (Attr a b)), Attr * Int -> Attr * (Result (Attr a b) (Attr * [ OutOfBounds ]*)))");
|
infer_eq("List.get", "Attr * (Attr (* | b) (List (Attr b a)), Attr * Int -> Attr * (Result (Attr b a) (Attr * [ OutOfBounds ]*)))");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_set() {
|
fn list_set() {
|
||||||
infer_eq("List.set", "Attr * (Attr (* | a | b) (List (Attr a c)), Attr * Int, Attr (a | b) c -> Attr * (List (Attr a c)))");
|
infer_eq("List.set", "Attr * (Attr (* | b | c) (List (Attr b a)), Attr * Int, Attr (b | c) a -> Attr * (List (Attr b a)))");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2216,7 +2294,7 @@ mod solve_uniq_expr {
|
||||||
fn list_foldr() {
|
fn list_foldr() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
"List.foldr",
|
"List.foldr",
|
||||||
"Attr * (Attr (* | a) (List (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)",
|
"Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2240,10 +2318,11 @@ mod solve_uniq_expr {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
reverse = \list -> List.foldr list (\e, l -> List.push l e) []
|
reverse = \list -> List.foldr list (\e, l -> List.push l e) []
|
||||||
|
|
||||||
reverse
|
reverse
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a) (List (Attr a b)) -> Attr * (List (Attr a b)))",
|
"Attr * (Attr (* | b) (List (Attr b a)) -> Attr * (List (Attr b a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2291,7 +2370,7 @@ mod solve_uniq_expr {
|
||||||
fn set_foldl() {
|
fn set_foldl() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
"Set.foldl",
|
"Set.foldl",
|
||||||
"Attr * (Attr (* | a) (Set (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)",
|
"Attr * (Attr (* | b) (Set (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2304,7 +2383,7 @@ mod solve_uniq_expr {
|
||||||
fn set_remove() {
|
fn set_remove() {
|
||||||
infer_eq(
|
infer_eq(
|
||||||
"Set.remove",
|
"Set.remove",
|
||||||
"Attr * (Attr * (Set (Attr a b)), Attr * b -> Attr * (Set (Attr a b)))",
|
"Attr * (Attr * (Set (Attr b a)), Attr * a -> Attr * (Set (Attr b a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2320,7 +2399,7 @@ mod solve_uniq_expr {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_get() {
|
fn map_get() {
|
||||||
infer_eq("Map.get", "Attr * (Attr (* | a) (Map (Attr * b) (Attr a c)), Attr * b -> Attr * (Result (Attr a c) (Attr * [ KeyNotFound ]*)))");
|
infer_eq("Map.get", "Attr * (Attr (* | c) (Map (Attr * a) (Attr c b)), Attr * a -> Attr * (Result (Attr c b) (Attr * [ KeyNotFound ]*)))");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2498,8 +2577,7 @@ mod solve_uniq_expr {
|
||||||
withDefault
|
withDefault
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// "Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)",
|
"Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)",
|
||||||
"Attr * (Attr (* | b | c) (Result (Attr c a) (Attr b e)), Attr c a -> Attr c a)",
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2514,8 +2592,7 @@ mod solve_uniq_expr {
|
||||||
Err _ -> default
|
Err _ -> default
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
// "Attr * (Attr (* | a | b) [ Err (Attr a *), Ok (Attr b c) ]*, Attr b c -> Attr b c)",
|
"Attr * (Attr (* | a | c) [ Err (Attr a *), Ok (Attr c b) ]*, Attr c b -> Attr c b)",
|
||||||
"Attr * (Attr (* | a | b) [ Err (Attr b *), Ok (Attr a c) ]*, Attr a c -> Attr a c)",
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2603,7 +2680,7 @@ mod solve_uniq_expr {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))",
|
"Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2630,7 +2707,7 @@ mod solve_uniq_expr {
|
||||||
\x -> Num.abs x
|
\x -> Num.abs x
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr a (Num (Attr a b)) -> Attr c (Num (Attr c b)))",
|
"Attr * (Attr b (Num (Attr b a)) -> Attr c (Num (Attr c a)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2648,8 +2725,8 @@ mod solve_uniq_expr {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
|
//"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
|
||||||
// "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))"
|
"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,11 @@ fn find_names_needed(
|
||||||
// We must not accidentally generate names that collide with them!
|
// We must not accidentally generate names that collide with them!
|
||||||
names_taken.insert(name);
|
names_taken.insert(name);
|
||||||
}
|
}
|
||||||
|
Structure(Apply(Symbol::ATTR_ATTR, args)) => {
|
||||||
|
// assign uniqueness var names based on when they occur in the base type
|
||||||
|
find_names_needed(args[1], subs, roots, root_appearances, names_taken);
|
||||||
|
find_names_needed(args[0], subs, roots, root_appearances, names_taken);
|
||||||
|
}
|
||||||
Structure(Apply(_, args)) => {
|
Structure(Apply(_, args)) => {
|
||||||
for var in args {
|
for var in args {
|
||||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||||
|
@ -153,21 +158,30 @@ fn find_names_needed(
|
||||||
find_names_needed(ret_var, subs, roots, root_appearances, names_taken);
|
find_names_needed(ret_var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
Structure(Record(fields, ext_var)) => {
|
Structure(Record(fields, ext_var)) => {
|
||||||
for (_, var) in fields {
|
let mut sorted_fields: Vec<_> = fields.iter().collect();
|
||||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
sorted_fields.sort();
|
||||||
|
|
||||||
|
for (_, var) in sorted_fields {
|
||||||
|
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
|
|
||||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
Structure(TagUnion(tags, ext_var)) => {
|
Structure(TagUnion(tags, ext_var)) => {
|
||||||
for var in tags.values().flatten() {
|
let mut sorted_tags: Vec<_> = tags.iter().collect();
|
||||||
|
sorted_tags.sort();
|
||||||
|
|
||||||
|
for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() {
|
||||||
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
|
|
||||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
|
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
|
||||||
for var in tags.values().flatten() {
|
let mut sorted_tags: Vec<_> = tags.iter().collect();
|
||||||
|
sorted_tags.sort();
|
||||||
|
|
||||||
|
for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() {
|
||||||
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +192,7 @@ fn find_names_needed(
|
||||||
Bool::Shared => {}
|
Bool::Shared => {}
|
||||||
Bool::Container(cvar, mvars) => {
|
Bool::Container(cvar, mvars) => {
|
||||||
find_names_needed(cvar, subs, roots, root_appearances, names_taken);
|
find_names_needed(cvar, subs, roots, root_appearances, names_taken);
|
||||||
|
|
||||||
for var in mvars {
|
for var in mvars {
|
||||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
|
@ -188,10 +203,11 @@ fn find_names_needed(
|
||||||
find_names_needed(args[0].1, subs, roots, root_appearances, names_taken);
|
find_names_needed(args[0].1, subs, roots, root_appearances, names_taken);
|
||||||
find_names_needed(args[1].1, subs, roots, root_appearances, names_taken);
|
find_names_needed(args[1].1, subs, roots, root_appearances, names_taken);
|
||||||
} else {
|
} else {
|
||||||
// TODO should we also look in the actual variable?
|
|
||||||
for (_, var) in args {
|
for (_, var) in args {
|
||||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
|
// TODO should we also look in the actual variable?
|
||||||
|
// find_names_needed(_actual, subs, roots, root_appearances, names_taken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => {
|
Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => {
|
||||||
|
@ -211,7 +227,7 @@ pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) {
|
||||||
|
|
||||||
for root in roots {
|
for root in roots {
|
||||||
// show the type variable number instead of `*`. useful for debugging
|
// show the type variable number instead of `*`. useful for debugging
|
||||||
// set_root_name(root, &(format!("<{:?}>", root).into()), subs);
|
// set_root_name(root, (format!("<{:?}>", root).into()), subs);
|
||||||
if let Some(Appearances::Multiple) = appearances.get(&root) {
|
if let Some(Appearances::Multiple) = appearances.get(&root) {
|
||||||
letters_used = name_root(letters_used, root, subs, &mut taken);
|
letters_used = name_root(letters_used, root, subs, &mut taken);
|
||||||
}
|
}
|
||||||
|
@ -226,21 +242,19 @@ fn name_root(
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
let (generated_name, new_letters_used) = name_type_var(letters_used, taken);
|
let (generated_name, new_letters_used) = name_type_var(letters_used, taken);
|
||||||
|
|
||||||
set_root_name(root, &generated_name, subs);
|
set_root_name(root, generated_name, subs);
|
||||||
|
|
||||||
new_letters_used
|
new_letters_used
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_root_name(root: Variable, name: &Lowercase, subs: &mut Subs) {
|
fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
|
||||||
use crate::subs::Content::*;
|
use crate::subs::Content::*;
|
||||||
|
|
||||||
let mut descriptor = subs.get_without_compacting(root);
|
let mut descriptor = subs.get_without_compacting(root);
|
||||||
|
|
||||||
match descriptor.content {
|
match descriptor.content {
|
||||||
FlexVar(None) => {
|
FlexVar(None) => {
|
||||||
descriptor.content = FlexVar(Some(name.clone()));
|
descriptor.content = FlexVar(Some(name));
|
||||||
|
|
||||||
// TODO is this necessary, or was mutating descriptor in place sufficient?
|
|
||||||
subs.set(root, descriptor);
|
subs.set(root, descriptor);
|
||||||
}
|
}
|
||||||
FlexVar(Some(_existing)) => {
|
FlexVar(Some(_existing)) => {
|
||||||
|
@ -334,8 +348,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
|
||||||
}
|
}
|
||||||
|
|
||||||
// useful for debugging
|
// useful for debugging
|
||||||
let write_out_alias = false;
|
if false {
|
||||||
if write_out_alias {
|
|
||||||
buf.push_str("[[ but really ");
|
buf.push_str("[[ but really ");
|
||||||
let content = subs.get_without_compacting(_actual).content;
|
let content = subs.get_without_compacting(_actual).content;
|
||||||
write_content(env, content, subs, buf, parens);
|
write_content(env, content, subs, buf, parens);
|
||||||
|
|
|
@ -62,16 +62,25 @@ pub enum SolvedBool {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolvedBool {
|
impl SolvedBool {
|
||||||
pub fn from_bool(boolean: &boolean_algebra::Bool) -> Self {
|
pub fn from_bool(boolean: &boolean_algebra::Bool, subs: &Subs) -> Self {
|
||||||
use boolean_algebra::Bool;
|
use boolean_algebra::Bool;
|
||||||
|
|
||||||
// NOTE we blindly trust that `cvar` is a root and has a FlexVar as content
|
|
||||||
match boolean {
|
match boolean {
|
||||||
Bool::Shared => SolvedBool::SolvedShared,
|
Bool::Shared => SolvedBool::SolvedShared,
|
||||||
Bool::Container(cvar, mvars) => SolvedBool::SolvedContainer(
|
Bool::Container(cvar, mvars) => {
|
||||||
VarId::from_var(*cvar),
|
debug_assert!(matches!(
|
||||||
mvars.iter().map(|mvar| VarId::from_var(*mvar)).collect(),
|
subs.get_without_compacting(*cvar).content,
|
||||||
),
|
crate::subs::Content::FlexVar(_)
|
||||||
|
));
|
||||||
|
|
||||||
|
SolvedBool::SolvedContainer(
|
||||||
|
VarId::from_var(*cvar, subs),
|
||||||
|
mvars
|
||||||
|
.iter()
|
||||||
|
.map(|mvar| VarId::from_var(*mvar, subs))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +164,7 @@ impl SolvedType {
|
||||||
}
|
}
|
||||||
|
|
||||||
SolvedType::RecursiveTagUnion(
|
SolvedType::RecursiveTagUnion(
|
||||||
VarId::from_var(rec_var),
|
VarId::from_var(rec_var, solved_subs.inner()),
|
||||||
solved_tags,
|
solved_tags,
|
||||||
Box::new(solved_ext),
|
Box::new(solved_ext),
|
||||||
)
|
)
|
||||||
|
@ -171,7 +180,7 @@ impl SolvedType {
|
||||||
|
|
||||||
SolvedType::Alias(symbol, solved_args, Box::new(solved_type))
|
SolvedType::Alias(symbol, solved_args, Box::new(solved_type))
|
||||||
}
|
}
|
||||||
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val)),
|
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, solved_subs.inner())),
|
||||||
Variable(var) => Self::from_var(solved_subs.inner(), var),
|
Variable(var) => Self::from_var(solved_subs.inner(), var),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +189,7 @@ impl SolvedType {
|
||||||
use crate::subs::Content::*;
|
use crate::subs::Content::*;
|
||||||
|
|
||||||
match subs.get_without_compacting(var).content {
|
match subs.get_without_compacting(var).content {
|
||||||
FlexVar(_) => SolvedType::Flex(VarId::from_var(var)),
|
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
|
||||||
RigidVar(name) => SolvedType::Rigid(name),
|
RigidVar(name) => SolvedType::Rigid(name),
|
||||||
Structure(flat_type) => Self::from_flat_type(subs, flat_type),
|
Structure(flat_type) => Self::from_flat_type(subs, flat_type),
|
||||||
Alias(symbol, args, actual_var) => {
|
Alias(symbol, args, actual_var) => {
|
||||||
|
@ -270,11 +279,15 @@ impl SolvedType {
|
||||||
|
|
||||||
let ext = Self::from_var(subs, ext_var);
|
let ext = Self::from_var(subs, ext_var);
|
||||||
|
|
||||||
SolvedType::RecursiveTagUnion(VarId::from_var(rec_var), new_tags, Box::new(ext))
|
SolvedType::RecursiveTagUnion(
|
||||||
|
VarId::from_var(rec_var, subs),
|
||||||
|
new_tags,
|
||||||
|
Box::new(ext),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
EmptyRecord => SolvedType::EmptyRecord,
|
EmptyRecord => SolvedType::EmptyRecord,
|
||||||
EmptyTagUnion => SolvedType::EmptyTagUnion,
|
EmptyTagUnion => SolvedType::EmptyTagUnion,
|
||||||
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val)),
|
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, subs)),
|
||||||
Erroneous(problem) => SolvedType::Erroneous(problem),
|
Erroneous(problem) => SolvedType::Erroneous(problem),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,8 @@ impl UnifyKey for Variable {
|
||||||
pub struct VarId(u32);
|
pub struct VarId(u32);
|
||||||
|
|
||||||
impl VarId {
|
impl VarId {
|
||||||
pub fn from_var(var: Variable) -> Self {
|
pub fn from_var(var: Variable, subs: &Subs) -> Self {
|
||||||
|
let var = subs.get_root_key_without_compacting(var);
|
||||||
let Variable(n) = var;
|
let Variable(n) = var;
|
||||||
|
|
||||||
VarId(n)
|
VarId(n)
|
||||||
|
|
|
@ -9,6 +9,10 @@ use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
pub const TYPE_NUM: &str = "Num";
|
||||||
|
pub const TYPE_INTEGER: &str = "Integer";
|
||||||
|
pub const TYPE_FLOATINGPOINT: &str = "FloatingPoint";
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
EmptyRec,
|
EmptyRec,
|
||||||
|
@ -455,31 +459,32 @@ impl Type {
|
||||||
Apply(Symbol::ATTR_ATTR, attr_args) => {
|
Apply(Symbol::ATTR_ATTR, attr_args) => {
|
||||||
use boolean_algebra::Bool;
|
use boolean_algebra::Bool;
|
||||||
|
|
||||||
let mut substitution = ImMap::default();
|
debug_assert_eq!(attr_args.len(), 2);
|
||||||
|
let mut it = attr_args.iter_mut();
|
||||||
|
let uniqueness_type = it.next().unwrap();
|
||||||
|
let base_type = it.next().unwrap();
|
||||||
|
|
||||||
if let Apply(symbol, _) = attr_args[1] {
|
// instantiate the rest
|
||||||
if let Some(alias) = aliases.get(&symbol) {
|
base_type.instantiate_aliases(region, aliases, var_store, introduced);
|
||||||
if let Some(Bool::Container(unbound_cvar, mvars1)) =
|
|
||||||
alias.uniqueness.clone()
|
// correct uniqueness type
|
||||||
|
// if this attr contains an alias of a recursive tag union, then the uniqueness
|
||||||
|
// attribute on the recursion variable must match the uniqueness of the whole tag
|
||||||
|
// union. We enforce that here.
|
||||||
|
|
||||||
|
if let Some(rec_uvar) = find_rec_var_uniqueness(base_type, aliases) {
|
||||||
|
if let Bool::Container(unbound_cvar, mvars1) = rec_uvar {
|
||||||
|
if let Type::Boolean(Bool::Container(bound_cvar, mvars2)) = uniqueness_type
|
||||||
{
|
{
|
||||||
debug_assert!(mvars1.is_empty());
|
debug_assert!(mvars1.is_empty());
|
||||||
|
debug_assert!(mvars2.is_empty());
|
||||||
|
|
||||||
if let Type::Boolean(Bool::Container(bound_cvar, mvars2)) =
|
let mut substitution = ImMap::default();
|
||||||
&attr_args[0]
|
substitution.insert(unbound_cvar, Type::Variable(*bound_cvar));
|
||||||
{
|
base_type.substitute(&substitution);
|
||||||
debug_assert!(mvars2.is_empty());
|
|
||||||
substitution.insert(unbound_cvar, Type::Variable(*bound_cvar));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for x in attr_args {
|
|
||||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
|
||||||
if !substitution.is_empty() {
|
|
||||||
x.substitute(&substitution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Apply(symbol, args) => {
|
Apply(symbol, args) => {
|
||||||
if let Some(alias) = aliases.get(symbol) {
|
if let Some(alias) = aliases.get(symbol) {
|
||||||
|
@ -686,6 +691,34 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We're looking for an alias whose actual type is a recursive tag union
|
||||||
|
/// if `base_type` is one, return the uniqueness variable of the alias.
|
||||||
|
fn find_rec_var_uniqueness(
|
||||||
|
base_type: &Type,
|
||||||
|
aliases: &ImMap<Symbol, Alias>,
|
||||||
|
) -> Option<boolean_algebra::Bool> {
|
||||||
|
use Type::*;
|
||||||
|
|
||||||
|
if let Alias(symbol, _, actual) = base_type {
|
||||||
|
match **actual {
|
||||||
|
Alias(_, _, _) => find_rec_var_uniqueness(actual, aliases),
|
||||||
|
RecursiveTagUnion(_, _, _) => {
|
||||||
|
if let Some(alias) = aliases.get(symbol) {
|
||||||
|
// alias with a recursive tag union must have its uniqueness set
|
||||||
|
debug_assert!(alias.uniqueness.is_some());
|
||||||
|
|
||||||
|
alias.uniqueness.clone()
|
||||||
|
} else {
|
||||||
|
unreachable!("aliases must be defined in the set of aliases!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RecordStructure {
|
pub struct RecordStructure {
|
||||||
pub fields: MutMap<Lowercase, Variable>,
|
pub fields: MutMap<Lowercase, Variable>,
|
||||||
pub ext: Variable,
|
pub ext: Variable,
|
||||||
|
|
|
@ -515,6 +515,17 @@ fn unify_tag_union(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is the given variable a structure. Does not consider Attr itself a structure, and instead looks
|
||||||
|
/// into it.
|
||||||
|
fn is_structure(var: Variable, subs: &mut Subs) -> bool {
|
||||||
|
match subs.get(var).content {
|
||||||
|
Content::Alias(_, _, actual) => is_structure(actual, subs),
|
||||||
|
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => is_structure(args[1], subs),
|
||||||
|
Content::Structure(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn unify_shared_tags(
|
fn unify_shared_tags(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
pool: &mut Pool,
|
pool: &mut Pool,
|
||||||
|
@ -543,17 +554,40 @@ fn unify_shared_tags(
|
||||||
//
|
//
|
||||||
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
|
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
|
||||||
// Polymorphic recursion is now a type error.
|
// Polymorphic recursion is now a type error.
|
||||||
|
//
|
||||||
|
// The strategy is to expand the recursive tag union as deeply as the non-recursive one
|
||||||
|
// is.
|
||||||
|
//
|
||||||
|
// > RecursiveTagUnion(rvar, [ Cons a rvar, Nil ], ext)
|
||||||
|
//
|
||||||
|
// Conceptually becomes
|
||||||
|
//
|
||||||
|
// > RecursiveTagUnion(rvar, [ Cons a [ Cons a rvar, Nil ], Nil ], ext)
|
||||||
|
//
|
||||||
|
// and so on until the whole non-recursive tag union can be unified with it.
|
||||||
let problems = if let Some(rvar) = recursion_var {
|
let problems = if let Some(rvar) = recursion_var {
|
||||||
if expected == rvar {
|
if expected == rvar {
|
||||||
unify_pool(subs, pool, actual, ctx.second)
|
unify_pool(subs, pool, actual, ctx.second)
|
||||||
} else {
|
} else if is_structure(actual, subs) {
|
||||||
// replace the rvar with ctx.second in expected
|
// the recursion variable is hidden behind some structure (commonly an Attr
|
||||||
|
// with uniqueness inference). Thus we must expand the recursive tag union to
|
||||||
|
// unify if with the non-recursive one. Thus:
|
||||||
|
|
||||||
|
// replace the rvar with ctx.second (the whole recursive tag union) in expected
|
||||||
subs.explicit_substitute(rvar, ctx.second, expected);
|
subs.explicit_substitute(rvar, ctx.second, expected);
|
||||||
|
|
||||||
|
// but, by the `is_structure` condition above, only if we're unifying with a structure!
|
||||||
|
// when `actual` is just a flex/rigid variable, the substitution would expand a
|
||||||
|
// recursive tag union infinitely!
|
||||||
|
|
||||||
|
unify_pool(subs, pool, actual, expected)
|
||||||
|
} else {
|
||||||
|
// unification with a non-structure is trivial
|
||||||
unify_pool(subs, pool, actual, expected)
|
unify_pool(subs, pool, actual, expected)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we always unify NonRecursive with Recursive, so this should never happen
|
// we always unify NonRecursive with Recursive, so this should never happen
|
||||||
debug_assert!(Some(actual) != recursion_var);
|
debug_assert_ne!(Some(actual), recursion_var);
|
||||||
|
|
||||||
unify_pool(subs, pool, actual, expected)
|
unify_pool(subs, pool, actual, expected)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue