mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 07:14:46 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into format-precedence-conflict
This commit is contained in:
commit
73744b3b1d
74 changed files with 3308 additions and 919 deletions
|
@ -12,7 +12,7 @@ roc_types = { path = "../types" }
|
|||
roc_can = { path = "../can" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -18,7 +18,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::env::Env;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{MutSet, SendMap};
|
||||
use roc_collections::all::{MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -292,9 +292,10 @@ fn can_annotation_help(
|
|||
|
||||
Record { fields, ext } => {
|
||||
let mut field_types = SendMap::default();
|
||||
let mut seen = MutMap::default();
|
||||
|
||||
for field in fields.iter() {
|
||||
can_assigned_field(
|
||||
let opt_field_name = can_assigned_field(
|
||||
env,
|
||||
&field.value,
|
||||
region,
|
||||
|
@ -305,6 +306,17 @@ fn can_annotation_help(
|
|||
&mut field_types,
|
||||
references,
|
||||
);
|
||||
|
||||
if let Some(added) = opt_field_name {
|
||||
if let Some(replaced_region) = seen.insert(added.clone(), field.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
|
||||
field_name: added.clone(),
|
||||
field_region: field.region,
|
||||
record_region: region,
|
||||
replaced_region,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
|
@ -325,9 +337,10 @@ fn can_annotation_help(
|
|||
}
|
||||
TagUnion { tags, ext } => {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
let mut seen = MutMap::default();
|
||||
|
||||
for tag in tags.iter() {
|
||||
can_tag(
|
||||
let opt_tag_name = can_tag(
|
||||
env,
|
||||
&tag.value,
|
||||
region,
|
||||
|
@ -338,6 +351,17 @@ fn can_annotation_help(
|
|||
&mut tag_types,
|
||||
references,
|
||||
);
|
||||
|
||||
if let Some(added) = opt_tag_name {
|
||||
if let Some(replaced_region) = seen.insert(added.clone(), tag.region) {
|
||||
env.problem(roc_problem::can::Problem::DuplicateTag {
|
||||
tag_name: added.clone(),
|
||||
tag_region: tag.region,
|
||||
tag_union_region: region,
|
||||
replaced_region,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
|
@ -388,7 +412,7 @@ fn can_assigned_field<'a>(
|
|||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
field_types: &mut SendMap<Lowercase, Type>,
|
||||
references: &mut MutSet<Symbol>,
|
||||
) {
|
||||
) -> Option<Lowercase> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
||||
match field {
|
||||
|
@ -396,16 +420,18 @@ fn can_assigned_field<'a>(
|
|||
let field_type = can_annotation_help(
|
||||
env,
|
||||
&annotation.value,
|
||||
region,
|
||||
annotation.region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
local_aliases,
|
||||
references,
|
||||
);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
|
||||
field_types.insert(label, field_type);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), field_type);
|
||||
|
||||
Some(label)
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
|
@ -420,7 +446,9 @@ fn can_assigned_field<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
field_types.insert(field_name, field_type);
|
||||
field_types.insert(field_name.clone(), field_type);
|
||||
|
||||
Some(field_name)
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field(
|
||||
env,
|
||||
|
@ -433,7 +461,7 @@ fn can_assigned_field<'a>(
|
|||
field_types,
|
||||
references,
|
||||
),
|
||||
Malformed(_) => (),
|
||||
Malformed(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,7 +477,7 @@ fn can_tag<'a>(
|
|||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
tag_types: &mut Vec<(TagName, Vec<Type>)>,
|
||||
references: &mut MutSet<Symbol>,
|
||||
) {
|
||||
) -> Option<TagName> {
|
||||
match tag {
|
||||
Tag::Global { name, args } => {
|
||||
let name = name.value.into();
|
||||
|
@ -470,7 +498,10 @@ fn can_tag<'a>(
|
|||
arg_types.push(ann);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Global(name), arg_types));
|
||||
let tag_name = TagName::Global(name);
|
||||
tag_types.push((tag_name.clone(), arg_types));
|
||||
|
||||
Some(tag_name)
|
||||
}
|
||||
Tag::Private { name, args } => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
|
||||
|
@ -492,7 +523,10 @@ fn can_tag<'a>(
|
|||
arg_types.push(ann);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Private(symbol), arg_types));
|
||||
let tag_name = TagName::Private(symbol);
|
||||
tag_types.push((tag_name.clone(), arg_types));
|
||||
|
||||
Some(tag_name)
|
||||
}
|
||||
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag(
|
||||
env,
|
||||
|
@ -505,6 +539,6 @@ fn can_tag<'a>(
|
|||
tag_types,
|
||||
references,
|
||||
),
|
||||
Tag::Malformed(_) => (),
|
||||
Tag::Malformed(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,24 +32,32 @@ impl Constraint {
|
|||
match self {
|
||||
True | SaveTheEnvironment => {}
|
||||
|
||||
Eq(typ, expected, _, _) => {
|
||||
expected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(aliases, var_store, introduced);
|
||||
Eq(typ, expected, _, region) => {
|
||||
let expected_region = expected.get_annotation_region().unwrap_or(*region);
|
||||
expected.get_type_mut_ref().instantiate_aliases(
|
||||
expected_region,
|
||||
aliases,
|
||||
var_store,
|
||||
introduced,
|
||||
);
|
||||
typ.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
Lookup(_, expected, _) => {
|
||||
expected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
Lookup(_, expected, region) => {
|
||||
let expected_region = expected.get_annotation_region().unwrap_or(*region);
|
||||
expected.get_type_mut_ref().instantiate_aliases(
|
||||
expected_region,
|
||||
aliases,
|
||||
var_store,
|
||||
introduced,
|
||||
);
|
||||
}
|
||||
|
||||
Pattern(_, _, typ, pexpected) => {
|
||||
Pattern(region, _, typ, pexpected) => {
|
||||
pexpected
|
||||
.get_type_mut_ref()
|
||||
.instantiate_aliases(aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(aliases, var_store, introduced);
|
||||
.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
typ.instantiate_aliases(*region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
And(nested) => {
|
||||
|
@ -65,8 +73,8 @@ impl Constraint {
|
|||
}
|
||||
|
||||
let mut introduced = ImSet::default();
|
||||
for Located { value: typ, .. } in letcon.def_types.iter_mut() {
|
||||
typ.instantiate_aliases(&new_aliases, var_store, &mut introduced);
|
||||
for Located { region, value: typ } in letcon.def_types.iter_mut() {
|
||||
typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced);
|
||||
}
|
||||
|
||||
letcon.defs_constraint.instantiate_aliases_help(
|
||||
|
|
|
@ -201,6 +201,7 @@ pub fn canonicalize_defs<'a>(
|
|||
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
|
||||
Vec::with_capacity(vars.len());
|
||||
|
||||
let mut is_phantom = false;
|
||||
for loc_lowercase in vars {
|
||||
if let Some(var) = can_ann
|
||||
.introduced_variables
|
||||
|
@ -212,12 +213,31 @@ pub fn canonicalize_defs<'a>(
|
|||
region: loc_lowercase.region,
|
||||
});
|
||||
} else {
|
||||
panic!("TODO handle phantom type variables, they are not allowed!\nThe {:?} variable in the definition of {:?} gives trouble", loc_lowercase, symbol);
|
||||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if is_phantom {
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
if can_ann.typ.contains_symbol(symbol) {
|
||||
make_tag_union_recursive(symbol, &mut can_ann.typ, var_store);
|
||||
make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
name.region,
|
||||
vec![],
|
||||
&mut can_ann.typ,
|
||||
var_store,
|
||||
&mut false,
|
||||
);
|
||||
}
|
||||
|
||||
let alias = roc_types::types::Alias {
|
||||
|
@ -231,7 +251,7 @@ pub fn canonicalize_defs<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
correct_mutual_recursive_type_alias(&mut aliases, &var_store);
|
||||
correct_mutual_recursive_type_alias(env, &mut aliases, &var_store);
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
// we're ready to canonicalize any body exprs.
|
||||
|
@ -836,7 +856,11 @@ fn canonicalize_pending_def<'a>(
|
|||
region: loc_lowercase.region,
|
||||
});
|
||||
} else {
|
||||
panic!("TODO handle phantom type variables, they are not allowed!");
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -852,7 +876,9 @@ fn canonicalize_pending_def<'a>(
|
|||
|
||||
scope.add_alias(symbol, name.region, can_vars, rec_type_union);
|
||||
} else {
|
||||
panic!("recursion in type alias that is not behind a Tag");
|
||||
env.problems
|
||||
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1385,7 +1411,11 @@ fn pending_typed_body<'a>(
|
|||
}
|
||||
|
||||
/// Make aliases recursive
|
||||
fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var_store: &VarStore) {
|
||||
fn correct_mutual_recursive_type_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
aliases: &mut SendMap<Symbol, Alias>,
|
||||
var_store: &VarStore,
|
||||
) {
|
||||
let mut symbols_introduced = ImSet::default();
|
||||
|
||||
for (key, _) in aliases.iter() {
|
||||
|
@ -1434,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
&mutually_recursive_symbols,
|
||||
all_successors_without_self,
|
||||
) {
|
||||
// make sure we report only one error for the cycle, not an error for every
|
||||
// alias in the cycle.
|
||||
let mut can_still_report_error = true;
|
||||
|
||||
// TODO use itertools to be more efficient here
|
||||
for rec in &cycle {
|
||||
let mut to_instantiate = ImMap::default();
|
||||
let mut others = Vec::with_capacity(cycle.len() - 1);
|
||||
for other in &cycle {
|
||||
if rec != other {
|
||||
others.push(*other);
|
||||
if let Some(alias) = originals.get(other) {
|
||||
to_instantiate.insert(*other, alias.clone());
|
||||
}
|
||||
|
@ -1447,11 +1483,20 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
|
||||
if let Some(alias) = aliases.get_mut(rec) {
|
||||
alias.typ.instantiate_aliases(
|
||||
alias.region,
|
||||
&to_instantiate,
|
||||
var_store,
|
||||
&mut ImSet::default(),
|
||||
);
|
||||
make_tag_union_recursive(*rec, &mut alias.typ, var_store);
|
||||
make_tag_union_recursive(
|
||||
env,
|
||||
*rec,
|
||||
alias.region,
|
||||
others,
|
||||
&mut alias.typ,
|
||||
var_store,
|
||||
&mut can_still_report_error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1459,14 +1504,41 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
|
|||
}
|
||||
}
|
||||
|
||||
fn make_tag_union_recursive(symbol: Symbol, typ: &mut Type, var_store: &VarStore) {
|
||||
fn make_tag_union_recursive<'a>(
|
||||
env: &mut Env<'a>,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
others: Vec<Symbol>,
|
||||
typ: &mut Type,
|
||||
var_store: &VarStore,
|
||||
can_report_error: &mut bool,
|
||||
) {
|
||||
match typ {
|
||||
Type::TagUnion(tags, ext) => {
|
||||
let rec_var = var_store.fresh();
|
||||
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
|
||||
typ.substitute_alias(symbol, &Type::Variable(rec_var));
|
||||
}
|
||||
Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store),
|
||||
_ => panic!("recursion in type alias is not behind a Tag"),
|
||||
Type::Alias(_, _, actual) => make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
region,
|
||||
others,
|
||||
actual,
|
||||
var_store,
|
||||
can_report_error,
|
||||
),
|
||||
_ => {
|
||||
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());
|
||||
*typ = Type::Erroneous(problem);
|
||||
|
||||
// ensure cyclic error is only reported for one element of the cycle
|
||||
if *can_report_error {
|
||||
*can_report_error = false;
|
||||
|
||||
let problem = Problem::CyclicAlias(symbol, region, others);
|
||||
env.problems.push(problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,15 @@ impl<T> Expected<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_annotation_region(&self) -> Option<Region> {
|
||||
match self {
|
||||
Expected::FromAnnotation(_, _, AnnotationSource::TypedBody { region }, _) => {
|
||||
Some(*region)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace<U>(self, new: U) -> Expected<U> {
|
||||
match self {
|
||||
Expected::NoExpectation(_val) => Expected::NoExpectation(new),
|
||||
|
|
|
@ -193,7 +193,8 @@ pub fn canonicalize_expr<'a>(
|
|||
let (can_update, update_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
|
||||
if let Var(symbol) = &can_update.value {
|
||||
let (can_fields, mut output) = canonicalize_fields(env, var_store, scope, fields);
|
||||
let (can_fields, mut output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
output.references = output.references.union(update_out.references);
|
||||
|
||||
|
@ -219,7 +220,8 @@ pub fn canonicalize_expr<'a>(
|
|||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields);
|
||||
let (can_fields, output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
(
|
||||
Record {
|
||||
|
@ -885,6 +887,7 @@ fn canonicalize_fields<'a>(
|
|||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
|
||||
) -> (SendMap<Lowercase, Field>, Output) {
|
||||
let mut can_fields = SendMap::default();
|
||||
|
@ -900,7 +903,16 @@ fn canonicalize_fields<'a>(
|
|||
loc_expr: Box::new(field_expr),
|
||||
};
|
||||
|
||||
can_fields.insert(label, field);
|
||||
let replaced = can_fields.insert(label.clone(), field);
|
||||
|
||||
if let Some(old) = replaced {
|
||||
env.problems.push(Problem::DuplicateRecordFieldValue {
|
||||
field_name: label,
|
||||
field_region: loc_field.region,
|
||||
record_region: region,
|
||||
replaced_region: old.region,
|
||||
});
|
||||
}
|
||||
|
||||
output.references = output.references.union(field_out.references);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_uniq = { path = "../uniq" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -277,9 +277,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarSt
|
|||
|
||||
Type::Alias(*symbol, type_variables, Box::new(actual))
|
||||
}
|
||||
Error => {
|
||||
panic!("TODO convert from SolvedType::Error to Type somehow");
|
||||
}
|
||||
Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError),
|
||||
Erroneous(problem) => Type::Erroneous(problem.clone()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -43,7 +43,7 @@ target-lexicon = "0.10"
|
|||
[dev-dependencies]
|
||||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -4,11 +4,11 @@ use inkwell::builder::Builder;
|
|||
use inkwell::context::Context;
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use inkwell::module::{Linkage, Module};
|
||||
use inkwell::passes::PassManager;
|
||||
use inkwell::passes::{PassManager, PassManagerBuilder};
|
||||
use inkwell::types::{BasicTypeEnum, IntType, StructType};
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
|
||||
|
||||
use crate::llvm::convert::{
|
||||
basic_type_from_layout, collection_wrapper, empty_collection, get_fn_type, ptr_int,
|
||||
|
@ -31,6 +31,11 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
|
|||
// TODO: experiment with different internal calling conventions, e.g. "fast"
|
||||
const DEFAULT_CALLING_CONVENTION: u32 = 0;
|
||||
|
||||
pub enum OptLevel {
|
||||
Normal,
|
||||
Optimize,
|
||||
}
|
||||
|
||||
type Scope<'a, 'ctx> = ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>;
|
||||
|
||||
pub struct Env<'a, 'ctx, 'env> {
|
||||
|
@ -56,23 +61,41 @@ pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Modu
|
|||
.unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err))
|
||||
}
|
||||
|
||||
pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>) {
|
||||
pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>, opt_level: OptLevel) {
|
||||
// tail-call elimination is always on
|
||||
fpm.add_instruction_combining_pass();
|
||||
fpm.add_tail_call_elimination_pass();
|
||||
|
||||
let pmb = PassManagerBuilder::create();
|
||||
|
||||
// Enable more optimizations when running cargo test --release
|
||||
if !cfg!(debug_assertions) {
|
||||
fpm.add_reassociate_pass();
|
||||
fpm.add_basic_alias_analysis_pass();
|
||||
fpm.add_promote_memory_to_register_pass();
|
||||
fpm.add_cfg_simplification_pass();
|
||||
fpm.add_gvn_pass();
|
||||
// TODO figure out why enabling any of these (even alone) causes LLVM to segfault
|
||||
// fpm.add_strip_dead_prototypes_pass();
|
||||
// fpm.add_dead_arg_elimination_pass();
|
||||
// fpm.add_function_inlining_pass();
|
||||
match opt_level {
|
||||
OptLevel::Normal => {
|
||||
pmb.set_optimization_level(OptimizationLevel::None);
|
||||
}
|
||||
OptLevel::Optimize => {
|
||||
// Default is O2, Aggressive is O3
|
||||
//
|
||||
// See https://llvm.org/doxygen/CodeGen_8h_source.html
|
||||
pmb.set_optimization_level(OptimizationLevel::Aggressive);
|
||||
|
||||
// TODO figure out how enabling these individually differs from
|
||||
// the broad "aggressive optimizations" setting.
|
||||
|
||||
// fpm.add_reassociate_pass();
|
||||
// fpm.add_basic_alias_analysis_pass();
|
||||
// fpm.add_promote_memory_to_register_pass();
|
||||
// fpm.add_cfg_simplification_pass();
|
||||
// fpm.add_gvn_pass();
|
||||
// TODO figure out why enabling any of these (even alone) causes LLVM to segfault
|
||||
// fpm.add_strip_dead_prototypes_pass();
|
||||
// fpm.add_dead_arg_elimination_pass();
|
||||
// fpm.add_function_inlining_pass();
|
||||
// pmb.set_inliner_with_threshold(4);
|
||||
}
|
||||
}
|
||||
|
||||
pmb.populate_function_pass_manager(&fpm);
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
|
@ -1006,6 +1029,17 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
|
||||
BasicValueEnum::FloatValue(float_val)
|
||||
}
|
||||
Symbol::FLOAT_DIV => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
let float_val = env.builder.build_float_div(
|
||||
args[0].0.into_float_value(),
|
||||
args[1].0.into_float_value(),
|
||||
"div_f64",
|
||||
);
|
||||
|
||||
BasicValueEnum::FloatValue(float_val)
|
||||
}
|
||||
Symbol::NUM_MUL => {
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
|
|
|
@ -84,6 +84,19 @@ mod gen_builtins {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_div_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
48 / 2
|
||||
"#
|
||||
),
|
||||
24.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_add_i64() {
|
||||
assert_evals_to!(
|
||||
|
@ -148,6 +161,20 @@ mod gen_builtins {
|
|||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_order_of_arithmetic_ops_complex_float() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
48 / 2 + 3
|
||||
"#
|
||||
),
|
||||
27.0,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_guard_bind_variable() {
|
||||
assert_evals_to!(
|
||||
|
|
|
@ -12,9 +12,14 @@ macro_rules! assert_llvm_evals_to {
|
|||
let context = Context::create();
|
||||
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
|
||||
let builder = context.create_builder();
|
||||
let fpm = inkwell::passes::PassManager::create(&module);
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
@ -142,12 +147,19 @@ macro_rules! assert_opt_evals_to {
|
|||
let mut unify_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
|
||||
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
|
||||
|
||||
let context = Context::create();
|
||||
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
|
||||
let builder = context.create_builder();
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
@ -271,12 +283,19 @@ macro_rules! emit_expr {
|
|||
let mut unify_problems = Vec::new();
|
||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
|
||||
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
|
||||
|
||||
let context = Context::create();
|
||||
let module = context.create_module("app");
|
||||
let builder = context.create_builder();
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
roc_gen::llvm::build::OptLevel::Normal
|
||||
} else {
|
||||
roc_gen::llvm::build::OptLevel::Optimize
|
||||
};
|
||||
let fpm = PassManager::create(&module);
|
||||
|
||||
roc_gen::llvm::build::add_passes(&fpm);
|
||||
roc_gen::llvm::build::add_passes(&fpm, opt_level);
|
||||
|
||||
fpm.initialize();
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ inlinable_string = "0.1.0"
|
|||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -12,7 +12,7 @@ use roc_constrain::module::{
|
|||
};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry, InterfaceHeader};
|
||||
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{Fail, Parser, State};
|
||||
use roc_region::all::{Located, Region};
|
||||
|
@ -44,6 +44,7 @@ pub struct Module {
|
|||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
||||
pub imported_modules: MutSet<ModuleId>,
|
||||
pub src: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -54,6 +55,8 @@ pub struct LoadedModule {
|
|||
pub can_problems: Vec<roc_problem::can::Problem>,
|
||||
pub type_problems: Vec<solve::TypeError>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
pub src: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -89,10 +92,12 @@ enum Msg {
|
|||
var_store: VarStore,
|
||||
},
|
||||
Solved {
|
||||
src: Box<str>,
|
||||
module_id: ModuleId,
|
||||
solved_types: MutMap<Symbol, SolvedType>,
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
subs: Arc<Solved<Subs>>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
problems: Vec<solve::TypeError>,
|
||||
},
|
||||
}
|
||||
|
@ -108,6 +113,7 @@ pub enum LoadingProblem {
|
|||
fail: Fail,
|
||||
},
|
||||
MsgChannelDied,
|
||||
TriedToImportAppModule,
|
||||
}
|
||||
|
||||
enum MaybeShared<'a, 'b, A, B> {
|
||||
|
@ -391,8 +397,9 @@ pub async fn load<'a>(
|
|||
solved_types,
|
||||
subs,
|
||||
problems,
|
||||
exposed_vars_by_symbol,
|
||||
aliases,
|
||||
..
|
||||
src,
|
||||
} => {
|
||||
type_problems.extend(problems);
|
||||
|
||||
|
@ -427,6 +434,8 @@ pub async fn load<'a>(
|
|||
can_problems,
|
||||
type_problems,
|
||||
declarations,
|
||||
exposed_vars_by_symbol,
|
||||
src,
|
||||
});
|
||||
} else {
|
||||
// This was a dependency. Write it down and keep processing messages.
|
||||
|
@ -515,13 +524,36 @@ fn load_filename(
|
|||
#[allow(clippy::let_and_return)]
|
||||
let answer = match roc_parse::module::header().parse(&arena, state) {
|
||||
Ok((ast::Module::Interface { header }, state)) => {
|
||||
let module_id = send_interface_header(header, state, module_ids, msg_tx);
|
||||
let module_id = send_header(
|
||||
header.name,
|
||||
header.exposes.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
state,
|
||||
module_ids,
|
||||
msg_tx,
|
||||
);
|
||||
|
||||
Ok(module_id)
|
||||
}
|
||||
Ok((ast::Module::App { .. }, _)) => {
|
||||
panic!("TODO finish loading an App module");
|
||||
}
|
||||
Ok((ast::Module::App { header }, state)) => match module_ids {
|
||||
MaybeShared::Shared(_, _) => {
|
||||
// If this is Shared, it means we're trying to import
|
||||
// an app module which is not the root. Not alllowed!
|
||||
Err(LoadingProblem::TriedToImportAppModule)
|
||||
}
|
||||
unique_modules @ MaybeShared::Unique(_, _) => {
|
||||
let module_id = send_header(
|
||||
header.name,
|
||||
header.provides.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
state,
|
||||
unique_modules,
|
||||
msg_tx,
|
||||
);
|
||||
|
||||
Ok(module_id)
|
||||
}
|
||||
},
|
||||
Err((fail, _)) => Err(LoadingProblem::ParsingFailed { filename, fail }),
|
||||
};
|
||||
|
||||
|
@ -534,36 +566,37 @@ fn load_filename(
|
|||
}
|
||||
}
|
||||
|
||||
fn send_interface_header<'a>(
|
||||
header: InterfaceHeader<'a>,
|
||||
fn send_header<'a>(
|
||||
name: Located<roc_parse::header::ModuleName<'a>>,
|
||||
exposes: &'a [Located<ExposesEntry<'a>>],
|
||||
imports: &'a [Located<ImportsEntry<'a>>],
|
||||
state: State<'a>,
|
||||
shared_modules: SharedModules<'_, '_>,
|
||||
msg_tx: MsgSender,
|
||||
) -> ModuleId {
|
||||
use MaybeShared::*;
|
||||
|
||||
let declared_name: ModuleName = header.name.value.as_str().into();
|
||||
let declared_name: ModuleName = name.value.as_str().into();
|
||||
|
||||
// TODO check to see if declared_name is consistent with filename.
|
||||
// If it isn't, report a problem!
|
||||
|
||||
let mut imports: Vec<(ModuleName, Vec<Ident>, Region)> =
|
||||
Vec::with_capacity(header.imports.len());
|
||||
let mut imported: Vec<(ModuleName, Vec<Ident>, Region)> = Vec::with_capacity(imports.len());
|
||||
let mut imported_modules: MutSet<ModuleId> = MutSet::default();
|
||||
let mut scope_size = 0;
|
||||
|
||||
for loc_entry in header.imports {
|
||||
for loc_entry in imports {
|
||||
let (module_name, exposed) = exposed_from_import(&loc_entry.value);
|
||||
|
||||
scope_size += exposed.len();
|
||||
|
||||
imports.push((module_name, exposed, loc_entry.region));
|
||||
imported.push((module_name, exposed, loc_entry.region));
|
||||
}
|
||||
|
||||
let num_exposes = header.exposes.len();
|
||||
let num_exposes = exposes.len();
|
||||
let mut deps_by_name: MutMap<ModuleName, ModuleId> =
|
||||
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
|
||||
let mut exposes: Vec<Symbol> = Vec::with_capacity(num_exposes);
|
||||
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
|
||||
|
||||
// Make sure the module_ids has ModuleIds for all our deps,
|
||||
// then record those ModuleIds in can_module_ids for later.
|
||||
|
@ -592,7 +625,7 @@ fn send_interface_header<'a>(
|
|||
// For each of our imports, add an entry to deps_by_name
|
||||
//
|
||||
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name
|
||||
for (module_name, exposed, region) in imports.into_iter() {
|
||||
for (module_name, exposed, region) in imported.into_iter() {
|
||||
let cloned_module_name = module_name.clone();
|
||||
let module_id = module_ids.get_or_insert(&module_name.into());
|
||||
|
||||
|
@ -618,7 +651,7 @@ fn send_interface_header<'a>(
|
|||
//
|
||||
// We must *not* add them to scope yet, or else the Defs will
|
||||
// incorrectly think they're shadowing them!
|
||||
for loc_exposed in header.exposes.iter() {
|
||||
for loc_exposed in exposes.iter() {
|
||||
// Use get_or_insert here because the ident_ids may already
|
||||
// created an IdentId for this, when it was imported exposed
|
||||
// in a dependent module.
|
||||
|
@ -629,7 +662,7 @@ fn send_interface_header<'a>(
|
|||
let ident_id = ident_ids.get_or_insert(&loc_exposed.value.as_str().into());
|
||||
let symbol = Symbol::new(home, ident_id);
|
||||
|
||||
exposes.push(symbol);
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
|
@ -648,7 +681,7 @@ fn send_interface_header<'a>(
|
|||
// and also add any exposed values to scope.
|
||||
//
|
||||
// e.g. for `imports [ Foo.{ bar } ]`, add `Foo` to deps_by_name and `bar` to scope.
|
||||
for (module_name, exposed, region) in imports.into_iter() {
|
||||
for (module_name, exposed, region) in imported.into_iter() {
|
||||
let module_id = module_ids.get_or_insert(&module_name.clone().into());
|
||||
|
||||
deps_by_name.insert(module_name, module_id);
|
||||
|
@ -672,11 +705,11 @@ fn send_interface_header<'a>(
|
|||
//
|
||||
// We must *not* add them to scope yet, or else the Defs will
|
||||
// incorrectly think they're shadowing them!
|
||||
for loc_exposed in header.exposes.iter() {
|
||||
for loc_exposed in exposes.iter() {
|
||||
let ident_id = ident_ids.add(loc_exposed.value.as_str().into());
|
||||
let symbol = Symbol::new(home, ident_id);
|
||||
|
||||
exposes.push(symbol);
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
|
@ -712,7 +745,7 @@ fn send_interface_header<'a>(
|
|||
module_name: declared_name,
|
||||
imported_modules,
|
||||
deps_by_name,
|
||||
exposes,
|
||||
exposes: exposed,
|
||||
src,
|
||||
exposed_imports: scope,
|
||||
}))
|
||||
|
@ -860,6 +893,7 @@ fn solve_module(
|
|||
aliases: module.aliases,
|
||||
};
|
||||
|
||||
let src = module.src;
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
for (var, name) in module.rigid_variables {
|
||||
|
@ -898,10 +932,10 @@ fn solve_module(
|
|||
// annotations which are decoupled from our Subs, because that's how
|
||||
// other modules will generate constraints for imported values
|
||||
// within the context of their own Subs.
|
||||
for (symbol, var) in exposed_vars_by_symbol {
|
||||
let solved_type = SolvedType::new(&solved_subs, var);
|
||||
for (symbol, var) in exposed_vars_by_symbol.iter() {
|
||||
let solved_type = SolvedType::new(&solved_subs, *var);
|
||||
|
||||
solved_types.insert(symbol, solved_type);
|
||||
solved_types.insert(*symbol, solved_type);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
@ -909,8 +943,10 @@ fn solve_module(
|
|||
|
||||
// Send the subs to the main thread for processing,
|
||||
tx.send(Msg::Solved {
|
||||
src,
|
||||
module_id: home,
|
||||
subs: Arc::new(solved_subs),
|
||||
exposed_vars_by_symbol,
|
||||
solved_types,
|
||||
problems,
|
||||
aliases: env.aliases,
|
||||
|
@ -1044,6 +1080,7 @@ fn parse_and_constrain(
|
|||
aliases,
|
||||
rigid_variables,
|
||||
imported_modules: header.imported_modules,
|
||||
src: header.src,
|
||||
};
|
||||
|
||||
(module, ident_ids, constraint, problems)
|
||||
|
|
111
compiler/load/tests/fixtures/build/app_with_deps/AStar.roc
vendored
Normal file
111
compiler/load/tests/fixtures/build/app_with_deps/AStar.roc
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
interface AStar
|
||||
exposes [ initialModel, reconstructPath, updateCost, cheapestOpen, astar, findPath ]
|
||||
imports []
|
||||
|
||||
|
||||
# a port of https://github.com/krisajenkins/elm-astar/blob/2.1.3/src/AStar/Generalised.elm
|
||||
|
||||
Model position :
|
||||
{ evaluated : Set position
|
||||
, openSet : Set position
|
||||
, costs : Map.Map position Float
|
||||
, cameFrom : Map.Map position position
|
||||
}
|
||||
|
||||
|
||||
initialModel : position -> Model position
|
||||
initialModel = \start ->
|
||||
{ evaluated : Set.empty
|
||||
, openSet : Set.singleton start
|
||||
, costs : Map.singleton start 0.0
|
||||
, cameFrom : Map.empty
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]*
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \position, resSmallestSoFar ->
|
||||
when Map.get model.costs position is
|
||||
Err e ->
|
||||
Err e
|
||||
|
||||
Ok cost ->
|
||||
positionCost = costFunction position
|
||||
|
||||
when resSmallestSoFar is
|
||||
Err _ -> Ok { position, cost: cost + positionCost }
|
||||
Ok smallestSoFar ->
|
||||
if positionCost + cost < smallestSoFar.cost then
|
||||
Ok { position, cost: cost + positionCost }
|
||||
|
||||
else
|
||||
Ok smallestSoFar
|
||||
|
||||
Set.foldl model.openSet folder (Err KeyNotFound)
|
||||
|> Result.map (\x -> x.position)
|
||||
|
||||
|
||||
|
||||
reconstructPath : Map position position, position -> List position
|
||||
reconstructPath = \cameFrom, goal ->
|
||||
when Map.get cameFrom goal is
|
||||
Err KeyNotFound ->
|
||||
[]
|
||||
|
||||
Ok next ->
|
||||
List.push (reconstructPath cameFrom next) goal
|
||||
|
||||
updateCost : position, position, Model position -> Model position
|
||||
updateCost = \current, neighbour, model ->
|
||||
newCameFrom = Map.insert model.cameFrom neighbour current
|
||||
|
||||
newCosts = Map.insert model.costs neighbour distanceTo
|
||||
|
||||
distanceTo = reconstructPath newCameFrom neighbour
|
||||
|> List.len
|
||||
|> Num.toFloat
|
||||
|
||||
newModel = { model & costs : newCosts , cameFrom : newCameFrom }
|
||||
|
||||
when Map.get model.costs neighbour is
|
||||
Err KeyNotFound ->
|
||||
newModel
|
||||
|
||||
Ok previousDistance ->
|
||||
if distanceTo < previousDistance then
|
||||
newModel
|
||||
|
||||
else
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]*
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]*
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
Err KeyNotFound
|
||||
|
||||
Ok current ->
|
||||
if current == goal then
|
||||
Ok (reconstructPath model.cameFrom goal)
|
||||
|
||||
else
|
||||
|
||||
modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current }
|
||||
|
||||
neighbours = moveFn current
|
||||
|
||||
newNeighbours = Set.diff neighbours modelPopped.evaluated
|
||||
|
||||
modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours }
|
||||
|
||||
modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours
|
||||
|
||||
astar costFn moveFn goal modelWithCosts
|
||||
|
15
compiler/load/tests/fixtures/build/app_with_deps/Dep1.roc
vendored
Normal file
15
compiler/load/tests/fixtures/build/app_with_deps/Dep1.roc
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
interface Dep1
|
||||
exposes [ three, str, Unit, Identity, one, two ]
|
||||
imports [ Dep3.Blah.{ foo } ]
|
||||
|
||||
one = 1
|
||||
|
||||
two = foo
|
||||
|
||||
three = 3.0
|
||||
|
||||
str = "string!"
|
||||
|
||||
Unit : [ Unit ]
|
||||
|
||||
Identity a : [ Identity a ]
|
10
compiler/load/tests/fixtures/build/app_with_deps/Dep2.roc
vendored
Normal file
10
compiler/load/tests/fixtures/build/app_with_deps/Dep2.roc
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
interface Dep2
|
||||
exposes [ one, two, blah ]
|
||||
imports [ Dep3.Blah.{ foo, bar } ]
|
||||
|
||||
one = 1
|
||||
|
||||
blah = foo
|
||||
|
||||
two = 2.0
|
||||
|
10
compiler/load/tests/fixtures/build/app_with_deps/Dep3/Blah.roc
vendored
Normal file
10
compiler/load/tests/fixtures/build/app_with_deps/Dep3/Blah.roc
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
interface Dep3.Blah
|
||||
exposes [ one, two, foo, bar ]
|
||||
imports []
|
||||
|
||||
one = 1
|
||||
|
||||
two = 2
|
||||
|
||||
foo = "foo from Dep3"
|
||||
bar = "bar from Dep3"
|
7
compiler/load/tests/fixtures/build/app_with_deps/ImportAlias.roc
vendored
Normal file
7
compiler/load/tests/fixtures/build/app_with_deps/ImportAlias.roc
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
interface ImportAlias
|
||||
exposes [ unit ]
|
||||
imports [ Dep1 ]
|
||||
|
||||
unit : Dep1.Unit
|
||||
unit = Unit
|
||||
|
18
compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc
vendored
Normal file
18
compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
interface ManualAttr
|
||||
exposes []
|
||||
imports []
|
||||
|
||||
# manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different
|
||||
# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when
|
||||
# signatures are given.
|
||||
|
||||
map =
|
||||
unAttr = \Attr _ foobar -> foobar
|
||||
|
||||
r = Attr unknown "bar"
|
||||
|
||||
s = Attr unknown2 { left : Attr Shared "foo" }
|
||||
|
||||
when True is
|
||||
_ -> { y : r }
|
||||
_ -> { y : (unAttr s).left }
|
5
compiler/load/tests/fixtures/build/app_with_deps/OneDep.roc
vendored
Normal file
5
compiler/load/tests/fixtures/build/app_with_deps/OneDep.roc
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
interface OneDep
|
||||
exposes [ str ]
|
||||
imports [ Dep3.Blah.{ foo } ]
|
||||
|
||||
str = foo
|
31
compiler/load/tests/fixtures/build/app_with_deps/Primary.roc
vendored
Normal file
31
compiler/load/tests/fixtures/build/app_with_deps/Primary.roc
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
app Primary
|
||||
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ]
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
||||
|
||||
str = Dep1.str
|
||||
|
||||
# alwaysThree = \_ -> Dep1.three # TODO FIXME for some reason this infers as a circular type
|
||||
alwaysThree = \_ -> "foo"
|
||||
|
||||
identity = \a -> a
|
||||
|
||||
z = identity (alwaysThree {})
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
||||
succeed : a -> Dep1.Identity a
|
||||
succeed = \x -> Identity x
|
||||
|
||||
withDefault = Res.withDefault
|
||||
|
||||
yay : Res.Res {} err
|
||||
yay =
|
||||
ok = Ok "foo"
|
||||
|
||||
f = \_ -> {}
|
||||
|
||||
Res.map ok f
|
49
compiler/load/tests/fixtures/build/app_with_deps/Quicksort.roc
vendored
Normal file
49
compiler/load/tests/fixtures/build/app_with_deps/Quicksort.roc
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
app Quicksort
|
||||
provides [ swap, partition, quicksort ]
|
||||
imports []
|
||||
|
||||
quicksort : List (Num a), Int, Int -> List (Num a)
|
||||
quicksort = \list, low, high ->
|
||||
when partition low high list is
|
||||
Pair partitionIndex partitioned ->
|
||||
partitioned
|
||||
|> quicksort low (partitionIndex - 1)
|
||||
|> quicksort (partitionIndex + 1) high
|
||||
|
||||
|
||||
swap : Int, Int, List a -> List a
|
||||
swap = \i, j, list ->
|
||||
when Pair (List.get list i) (List.get list j) is
|
||||
Pair (Ok atI) (Ok atJ) ->
|
||||
list
|
||||
|> List.set i atJ
|
||||
|> List.set j atI
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
||||
partition = \low, high, initialList ->
|
||||
when List.get initialList high is
|
||||
Ok pivot ->
|
||||
go = \i, j, list ->
|
||||
if j < high then
|
||||
when List.get list j is
|
||||
Ok value ->
|
||||
if value <= pivot then
|
||||
go (i + 1) (j + 1) (swap (i + 1) j list)
|
||||
else
|
||||
go i (j + 1) list
|
||||
|
||||
Err _ ->
|
||||
Pair i list
|
||||
else
|
||||
Pair i list
|
||||
|
||||
when go (low - 1) low initialList is
|
||||
Pair newI newList ->
|
||||
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||
|
||||
Err _ ->
|
||||
Pair (low - 1) initialList
|
8
compiler/load/tests/fixtures/build/app_with_deps/Records.roc
vendored
Normal file
8
compiler/load/tests/fixtures/build/app_with_deps/Records.roc
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
interface Records
|
||||
exposes [ intVal ]
|
||||
imports []
|
||||
|
||||
intVal =
|
||||
foo = \{ x } -> x
|
||||
|
||||
foo { x: 5 }
|
32
compiler/load/tests/fixtures/build/app_with_deps/Res.roc
vendored
Normal file
32
compiler/load/tests/fixtures/build/app_with_deps/Res.roc
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
interface Res
|
||||
exposes [ Res, withDefault, map, andThen, ConsList ]
|
||||
imports []
|
||||
|
||||
Res ok err : [ Ok ok, Err err ]
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
# TODO FIXME for some reason, exposing this causes a stack overflow
|
||||
# listMap : ConsList a, (a -> b) -> ConsList b
|
||||
# listMap = \list, f ->
|
||||
# when list is
|
||||
# Nil -> Nil
|
||||
# Cons x xs -> Cons (f x) (listMap xs f)
|
||||
|
||||
map : Res a err, (a -> b) -> Res b err
|
||||
map = \result, transform ->
|
||||
when result is
|
||||
Ok ok -> Ok (transform ok)
|
||||
Err err -> Err err
|
||||
|
||||
withDefault : Res a err, a -> a
|
||||
withDefault = \result, default ->
|
||||
when result is
|
||||
Ok ok -> ok
|
||||
Err _ -> default
|
||||
|
||||
andThen : Res a err, (a -> Res b err) -> Res b err
|
||||
andThen = \result, transform ->
|
||||
when result is
|
||||
Ok ok -> transform ok
|
||||
Err err -> Err err
|
19
compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc
vendored
Normal file
19
compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
interface WithBuiltins
|
||||
exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ]
|
||||
imports [ Dep1, Dep2.{ two } ]
|
||||
|
||||
floatTest = Float.highest
|
||||
|
||||
divisionFn = Float.div
|
||||
|
||||
x = 5.0
|
||||
|
||||
divisionTest = Float.highest / x
|
||||
|
||||
intTest = Int.highest
|
||||
|
||||
constantNum = 5
|
||||
|
||||
fromDep2 = Dep2.two
|
||||
|
||||
divDep1ByDep2 = Dep1.three / fromDep2
|
|
@ -3,7 +3,7 @@ interface Primary
|
|||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar # TODO FIXME does work as Dep3.Blah.bar, some scoping issue
|
||||
blah3 = bar
|
||||
|
||||
str = Dep1.str
|
||||
|
||||
|
@ -12,11 +12,7 @@ alwaysThree = \_ -> "foo"
|
|||
|
||||
identity = \a -> a
|
||||
|
||||
# z = identity (alwaysThree {}) # TODO FIXME for some reason this infers as a circular type
|
||||
# z = identity 3 # TODO FIXME for some reason this also infers as a circular type
|
||||
|
||||
z : Dep1.Unit
|
||||
z = Unit
|
||||
z = identity (alwaysThree {})
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
|
|
@ -212,7 +212,7 @@ mod test_load {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn load_and_typecheck_quicksort() {
|
||||
fn iface_quicksort() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
|
@ -229,6 +229,23 @@ mod test_load {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_quicksort() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Quicksort", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"swap" => "Int, Int, List a -> List a",
|
||||
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
|
||||
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_astar() {
|
||||
test_async(async {
|
||||
|
@ -266,7 +283,7 @@ mod test_load {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn load_dep_types() {
|
||||
fn iface_dep_types() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
|
@ -280,7 +297,31 @@ mod test_load {
|
|||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
"identity" => "a -> a",
|
||||
"z" => "Dep1.Unit",
|
||||
"z" => "Str",
|
||||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
"withDefault" => "Res.Res a *, a -> a",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_dep_types() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Primary", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah2" => "Float",
|
||||
"blah3" => "Str",
|
||||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
"identity" => "a -> a",
|
||||
"z" => "Str",
|
||||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
|
|
|
@ -275,7 +275,7 @@ mod test_uniq_load {
|
|||
"str" => "Attr * Str",
|
||||
"alwaysThree" => "Attr * (* -> Attr * Str)",
|
||||
"identity" => "Attr * (a -> a)",
|
||||
"z" => "Attr * Dep1.Unit",
|
||||
"z" => "Attr * Str",
|
||||
"w" => "Attr * (Dep1.Identity (Attr * {}))",
|
||||
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
|
||||
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
|
||||
|
|
|
@ -12,6 +12,6 @@ inlinable_string = "0.1.0"
|
|||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
|
|
|
@ -19,7 +19,7 @@ roc_constrain = { path = "../constrain" }
|
|||
roc_builtins = { path = "../builtins" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -10,10 +10,27 @@ use roc_region::all::{Located, Region};
|
|||
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProc<'a> {
|
||||
pub annotation: Variable,
|
||||
pub patterns: Vec<'a, Symbol>,
|
||||
pub body: roc_can::expr::Expr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub name: Symbol,
|
||||
pub args: &'a [(Layout<'a>, Symbol)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct Procs<'a> {
|
||||
user_defined: MutMap<Symbol, PartialProc<'a>>,
|
||||
anonymous: MutMap<Symbol, Option<Proc<'a>>>,
|
||||
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
|
||||
builtin: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
|
@ -28,14 +45,11 @@ impl<'a> Procs<'a> {
|
|||
|
||||
fn insert_specialization(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
hash: ContentHash,
|
||||
spec_name: Symbol,
|
||||
proc: Option<Proc<'a>>,
|
||||
) {
|
||||
self.user_defined
|
||||
.get_mut(&symbol)
|
||||
.map(|partial_proc| partial_proc.specializations.insert(hash, (spec_name, proc)));
|
||||
self.specializations.insert(hash, (spec_name, proc));
|
||||
}
|
||||
|
||||
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
|
||||
|
@ -44,11 +58,7 @@ impl<'a> Procs<'a> {
|
|||
|
||||
pub fn len(&self) -> usize {
|
||||
let anonymous: usize = self.anonymous.len();
|
||||
let user_defined: usize = self
|
||||
.user_defined
|
||||
.values()
|
||||
.map(|v| v.specializations.len())
|
||||
.sum();
|
||||
let user_defined: usize = self.specializations.len();
|
||||
|
||||
anonymous + user_defined
|
||||
}
|
||||
|
@ -64,10 +74,8 @@ impl<'a> Procs<'a> {
|
|||
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> {
|
||||
let mut result = MutMap::default();
|
||||
|
||||
for partial_proc in self.user_defined.values() {
|
||||
for (_, (symbol, opt_proc)) in partial_proc.specializations.clone().into_iter() {
|
||||
result.insert(symbol, opt_proc);
|
||||
}
|
||||
for (symbol, opt_proc) in self.specializations.values() {
|
||||
result.insert(*symbol, opt_proc.clone());
|
||||
}
|
||||
|
||||
for (symbol, proc) in self.anonymous.clone().into_iter() {
|
||||
|
@ -82,23 +90,6 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PartialProc<'a> {
|
||||
pub annotation: Variable,
|
||||
pub patterns: Vec<'a, Symbol>,
|
||||
pub body: roc_can::expr::Expr,
|
||||
pub specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
pub name: Symbol,
|
||||
pub args: &'a [(Layout<'a>, Symbol)],
|
||||
pub body: Expr<'a>,
|
||||
pub closes_over: Layout<'a>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
}
|
||||
|
||||
pub struct Env<'a, 'i> {
|
||||
pub arena: &'a Bump,
|
||||
pub subs: &'a mut Subs,
|
||||
|
@ -458,7 +449,6 @@ fn from_can<'a>(
|
|||
annotation,
|
||||
patterns: arg_symbols,
|
||||
body: body.value,
|
||||
specializations: MutMap::default(),
|
||||
},
|
||||
);
|
||||
symbol
|
||||
|
@ -831,8 +821,9 @@ fn from_can<'a>(
|
|||
elems: elems.into_bump_slice(),
|
||||
}
|
||||
}
|
||||
Accessor { .. } => todo!("record accessor"),
|
||||
Update { .. } => todo!("record update"),
|
||||
RuntimeError(error) => Expr::RuntimeError(env.arena.alloc(format!("{:?}", error))),
|
||||
other => panic!("TODO convert canonicalized {:?} to mono::Expr", other),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1304,36 +1295,42 @@ fn call_by_name<'a>(
|
|||
Vec<'a, Symbol>,
|
||||
)>;
|
||||
|
||||
let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) {
|
||||
let content_hash = ContentHash::from_var(fn_var, env.subs);
|
||||
let specialized_proc_name = match procs.get_user_defined(proc_name) {
|
||||
Some(partial_proc) => {
|
||||
let content_hash = ContentHash::from_var(fn_var, env.subs);
|
||||
|
||||
if let Some(specialization) = partial_proc.specializations.get(&content_hash) {
|
||||
match procs.specializations.get(&content_hash) {
|
||||
Some(specialization) => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// a specialization with this type hash already exists, use its symbol
|
||||
specialization.0
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = Some((
|
||||
content_hash,
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
|
||||
// generate a symbol for this specialization
|
||||
env.fresh_symbol()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// a specialization with this type hash already exists, use its symbol
|
||||
specialization.0
|
||||
} else {
|
||||
opt_specialize_body = Some((
|
||||
content_hash,
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
|
||||
// generate a symbol for this specialization
|
||||
env.fresh_symbol()
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
proc_name
|
||||
}
|
||||
} else {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
proc_name
|
||||
};
|
||||
|
||||
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body {
|
||||
// register proc, so specialization doesn't loop infinitely
|
||||
procs.insert_specialization(proc_name, content_hash, specialized_proc_name, None);
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, None);
|
||||
|
||||
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
|
||||
|
||||
|
@ -1350,7 +1347,7 @@ fn call_by_name<'a>(
|
|||
)
|
||||
.ok();
|
||||
|
||||
procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc);
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, proc);
|
||||
}
|
||||
|
||||
// generate actual call
|
||||
|
|
|
@ -12,7 +12,7 @@ bumpalo = { version = "3.2", features = ["collections"] }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
|
|
@ -36,9 +36,14 @@ pub struct WhenBranch<'a> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||
pub before_provides: &'a [CommentOrNewline<'a>],
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
|
|
@ -1244,10 +1244,14 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
move |arena, state, (loc_ident, opt_extras)| {
|
||||
// This appears to be a var, keyword, or function application.
|
||||
match opt_extras {
|
||||
(Some(_loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => {
|
||||
// We got args with an '=' after them, e.g. `foo a b = ...`
|
||||
// This is a syntax error!
|
||||
panic!("TODO gracefully handle parse error for defs like `foo a b = ...`");
|
||||
(Some(loc_args), Some((_spaces_before_equals, Either::First(_equals_indent)))) => {
|
||||
// We got args with an '=' after them, e.g. `foo a b = ...` This is a syntax error!
|
||||
let region = Region::across_all(loc_args.iter().map(|v| &v.region));
|
||||
let fail = Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::ArgumentsBeforeEquals(region),
|
||||
};
|
||||
Err((fail, state))
|
||||
}
|
||||
(None, Some((spaces_before_equals, Either::First(equals_indent)))) => {
|
||||
// We got '=' with no args before it
|
||||
|
|
|
@ -127,9 +127,30 @@ pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
|
|||
|
||||
#[inline(always)]
|
||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
||||
move |_, _| {
|
||||
panic!("TODO parse app header");
|
||||
}
|
||||
parser::map(
|
||||
and!(
|
||||
skip_first!(string("app"), and!(space1(1), loc!(module_name()))),
|
||||
and!(provides(), imports())
|
||||
),
|
||||
|(
|
||||
(after_interface, name),
|
||||
(
|
||||
((before_provides, after_provides), provides),
|
||||
((before_imports, after_imports), imports),
|
||||
),
|
||||
)| {
|
||||
AppHeader {
|
||||
name,
|
||||
provides,
|
||||
imports,
|
||||
after_interface,
|
||||
before_provides,
|
||||
after_provides,
|
||||
before_imports,
|
||||
after_imports,
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -137,6 +158,20 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
|
|||
zero_or_more!(space0_around(loc(def(0)), 0))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn provides<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a>>>,
|
||||
),
|
||||
> {
|
||||
and!(
|
||||
and!(skip_second!(space1(1), string("provides")), space1(1)),
|
||||
collection!(char('['), loc!(exposes_entry()), char(','), char(']'), 1)
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exposes<'a>() -> impl Parser<
|
||||
'a,
|
||||
|
|
|
@ -190,6 +190,7 @@ pub enum FailReason {
|
|||
Eof(Region),
|
||||
InvalidPattern,
|
||||
ReservedKeyword(Region),
|
||||
ArgumentsBeforeEquals(Region),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -12,7 +12,7 @@ roc_parse = { path = "../parse" }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use inlinable_string::InlinableString;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_parse::operator::BinOp;
|
||||
use roc_parse::pattern::PatternType;
|
||||
|
@ -21,6 +21,30 @@ pub enum Problem {
|
|||
original_region: Region,
|
||||
shadow: Located<Ident>,
|
||||
},
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
PhantomTypeArgument {
|
||||
alias: Symbol,
|
||||
variable_region: Region,
|
||||
variable_name: Lowercase,
|
||||
},
|
||||
DuplicateRecordFieldValue {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
DuplicateRecordFieldType {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
DuplicateTag {
|
||||
tag_name: TagName,
|
||||
tag_union_region: Region,
|
||||
tag_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ impl Region {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.end_line == self.start_line && self.start_col == self.end_col
|
||||
}
|
||||
|
||||
pub fn span_across(start: &Region, end: &Region) -> Self {
|
||||
Region {
|
||||
start_line: start.start_line,
|
||||
|
|
|
@ -27,7 +27,7 @@ roc_constrain = { path = "../constrain" }
|
|||
roc_builtins = { path = "../builtins" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
397
compiler/reporting/src/error/canonicalize.rs
Normal file
397
compiler/reporting/src/error/canonicalize.rs
Normal file
|
@ -0,0 +1,397 @@
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Problem,
|
||||
) -> Report<'b> {
|
||||
let doc = match problem {
|
||||
Problem::UnusedDef(symbol, region) => {
|
||||
let line =
|
||||
r#" then remove it so future readers of your code don't wonder why it is there."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is not used anywhere in your code.")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("If you didn't intend on using ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
|
||||
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
|
||||
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(" doesn't use "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow("."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("If you don't need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(", then you can just remove it. However, if you really do need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(" as an argument of "),
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(", prefix it with an underscore, like this: \"_"),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
|
||||
.stack(vec![
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using more than one "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" like this requires parentheses,",
|
||||
" to clarify how things should be grouped.",
|
||||
)),
|
||||
])
|
||||
} else {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(" and "),
|
||||
alloc.binop(right_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" together requires parentheses, ",
|
||||
"to clarify how they should be grouped."
|
||||
)),
|
||||
])
|
||||
},
|
||||
alloc.region(region),
|
||||
]),
|
||||
Problem::UnsupportedPattern(pattern_type, region) => {
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
|
||||
let this_thing = match pattern_type {
|
||||
TopLevelDef => "a top-level definition:",
|
||||
DefExpr => "a value definition:",
|
||||
FunctionArg => "function arguments:",
|
||||
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
||||
};
|
||||
|
||||
let suggestion = vec![
|
||||
alloc.reflow(
|
||||
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" ... "),
|
||||
alloc.keyword("is"),
|
||||
alloc.reflow(" instead."),
|
||||
];
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("This pattern is not allowed in ")
|
||||
.append(alloc.reflow(this_thing)),
|
||||
alloc.region(region),
|
||||
alloc.concat(suggestion),
|
||||
])
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
original_region,
|
||||
shadow,
|
||||
} => pretty_runtime_error(
|
||||
alloc,
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
},
|
||||
),
|
||||
Problem::CyclicAlias(symbol, region, others) => {
|
||||
let (doc, title) = crate::error::r#type::cyclic_alias(alloc, symbol, region, others);
|
||||
|
||||
return Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
};
|
||||
}
|
||||
Problem::PhantomTypeArgument {
|
||||
alias,
|
||||
variable_region,
|
||||
variable_name,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.type_variable(variable_name),
|
||||
alloc.reflow(" type variable is not used in the "),
|
||||
alloc.symbol_unqualified(alias),
|
||||
alloc.reflow(" alias definition:"),
|
||||
]),
|
||||
alloc.region(variable_region),
|
||||
alloc.reflow("Roc does not allow unused type parameters!"),
|
||||
// TODO add link to this guide section
|
||||
alloc.hint().append(alloc.reflow(
|
||||
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
||||
read the guide section on phantom data.",
|
||||
)),
|
||||
]),
|
||||
Problem::DuplicateRecordFieldValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record defines the "),
|
||||
alloc.record_field(field_name.clone()),
|
||||
alloc.reflow(" field twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
replaced_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" definitions from this record."),
|
||||
]),
|
||||
]),
|
||||
Problem::DuplicateRecordFieldType {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record type defines the "),
|
||||
alloc.record_field(field_name.clone()),
|
||||
alloc.reflow(" field twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
replaced_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" definitions from this record type."),
|
||||
]),
|
||||
]),
|
||||
Problem::DuplicateTag {
|
||||
tag_name,
|
||||
tag_union_region,
|
||||
tag_region,
|
||||
replaced_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This tag union type defines the "),
|
||||
alloc.tag_name(tag_name.clone()),
|
||||
alloc.reflow(" tag twice!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
tag_union_region,
|
||||
replaced_region,
|
||||
tag_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
tag_union_region,
|
||||
tag_region,
|
||||
tag_region,
|
||||
Annotation::TypoSuggestion,
|
||||
),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("For clarity, remove the previous "),
|
||||
alloc.tag_name(tag_name),
|
||||
alloc.reflow(" definitions from this tag union type."),
|
||||
]),
|
||||
]),
|
||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||
};
|
||||
|
||||
Report {
|
||||
title: "SYNTAX PROBLEM".to_string(),
|
||||
filename,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(mut idents, regions) => {
|
||||
let first = idents.remove(0);
|
||||
|
||||
if idents.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO hint?
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(
|
||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||
),
|
||||
alloc.region(regions[0].0),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.ident(first.value),
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|ident| alloc.ident(ident.value))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO hint?
|
||||
])
|
||||
}
|
||||
}
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// SymbolNotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// 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
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
region: roc_region::all::Region,
|
||||
name: &str,
|
||||
thing: &'b str,
|
||||
options: MutSet<Box<str>>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::error::r#type::suggest;
|
||||
|
||||
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
||||
suggestions.truncate(4);
|
||||
|
||||
let default_no = alloc.concat(vec![
|
||||
alloc.reflow("Is there an "),
|
||||
alloc.keyword("import"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" missing up-top"),
|
||||
]);
|
||||
|
||||
let default_yes = alloc.reflow("these names seem close though:");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
no_suggestion_details
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
yes_suggestion_details,
|
||||
alloc
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("I cannot find a `"),
|
||||
alloc.string(name.to_string()),
|
||||
alloc.reflow("` "),
|
||||
alloc.reflow(thing),
|
||||
]),
|
||||
alloc.region(region),
|
||||
to_details(default_no, default_yes),
|
||||
])
|
||||
}
|
4
compiler/reporting/src/error/mod.rs
Normal file
4
compiler/reporting/src/error/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod canonicalize;
|
||||
pub mod mono;
|
||||
pub mod parse;
|
||||
pub mod r#type;
|
147
compiler/reporting/src/error/mono.rs
Normal file
147
compiler/reporting/src/error/mono.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn mono_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: roc_mono::expr::MonoProblem,
|
||||
) -> Report<'b> {
|
||||
use roc_mono::expr::MonoProblem::*;
|
||||
use roc_mono::pattern::Context::*;
|
||||
use roc_mono::pattern::Error::*;
|
||||
|
||||
match problem {
|
||||
PatternProblem(Incomplete(region, context, missing)) => match context {
|
||||
BadArg => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
So rather than pattern matching in function arguments, put a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" in the function body to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadDestruct => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
You can use a binding to deconstruct a value if there is only ONE possibility. \
|
||||
Use a "
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadCase => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" does not cover all the possibilities:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
Add branches for them!",
|
||||
),
|
||||
// alloc.hint().append(alloc.reflow("or use a hole.")),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
},
|
||||
PatternProblem(Redundant {
|
||||
overall_region,
|
||||
branch_region,
|
||||
index,
|
||||
}) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.string(index.ordinal()),
|
||||
alloc.reflow(" pattern is redundant:"),
|
||||
]),
|
||||
alloc.region_with_subregion(overall_region, branch_region),
|
||||
alloc.reflow(
|
||||
"Any value of this shape will be handled by \
|
||||
a previous pattern, so this one should be removed.",
|
||||
),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "REDUNDANT PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
patterns: Vec<roc_mono::pattern::Pattern>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc
|
||||
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
|
||||
.indent(4)
|
||||
.annotate(Annotation::TypeBlock)
|
||||
}
|
||||
|
||||
fn pattern_to_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::pattern::Pattern,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use roc_mono::pattern::Literal::*;
|
||||
use roc_mono::pattern::Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Anything => alloc.text("_"),
|
||||
Literal(l) => match l {
|
||||
Int(i) => alloc.text(i.to_string()),
|
||||
Bit(true) => alloc.text("True"),
|
||||
Bit(false) => alloc.text("False"),
|
||||
Byte(b) => alloc.text(b.to_string()),
|
||||
Float(f) => alloc.text(f.to_string()),
|
||||
Str(s) => alloc.string(s.into()),
|
||||
},
|
||||
Ctor(_, tag_name, args) => {
|
||||
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
||||
|
||||
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
||||
|
||||
alloc.intersperse(docs, alloc.space())
|
||||
}
|
||||
}
|
||||
}
|
42
compiler/reporting/src/error/parse.rs
Normal file
42
compiler/reporting/src/error/parse.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use roc_parse::parser::{Fail, FailReason};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::report::{Report, RocDocAllocator};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
pub fn parse_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Fail,
|
||||
) -> Report<'b> {
|
||||
use FailReason::*;
|
||||
|
||||
match problem.reason {
|
||||
ArgumentsBeforeEquals(region) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("Unexpected tokens in front of the `=` symbol:"),
|
||||
alloc.region(region),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "PARSE PROBLEM".to_string(),
|
||||
}
|
||||
}
|
||||
other => {
|
||||
//
|
||||
// Unexpected(char, Region),
|
||||
// OutdentedTooFar,
|
||||
// ConditionFailed,
|
||||
// LineTooLong(u32 /* which line was too long */),
|
||||
// TooManyLines,
|
||||
// Eof(Region),
|
||||
// InvalidPattern,
|
||||
// ReservedKeyword(Region),
|
||||
// ArgumentsBeforeEquals,
|
||||
//}
|
||||
todo!("unhandled parse error: {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,9 +29,111 @@ pub fn type_problem<'b>(
|
|||
CircularType(region, symbol, overall_type) => {
|
||||
to_circular_report(alloc, filename, region, symbol, overall_type)
|
||||
}
|
||||
BadType(type_problem) => {
|
||||
use roc_types::types::Problem::*;
|
||||
match type_problem {
|
||||
BadTypeArguments {
|
||||
symbol,
|
||||
region,
|
||||
type_got,
|
||||
alias_needs,
|
||||
} => {
|
||||
let needed_arguments = if alias_needs == 1 {
|
||||
alloc.reflow("1 type argument")
|
||||
} else {
|
||||
alloc
|
||||
.text(alias_needs.to_string())
|
||||
.append(alloc.reflow(" type arguments"))
|
||||
};
|
||||
|
||||
let found_arguments = alloc.text(type_got.to_string());
|
||||
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.symbol_unqualified(symbol),
|
||||
alloc.reflow(" alias expects "),
|
||||
needed_arguments,
|
||||
alloc.reflow(", but it got "),
|
||||
found_arguments,
|
||||
alloc.reflow(" instead:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Are there missing parentheses?"),
|
||||
]);
|
||||
|
||||
let title = if type_got > alias_needs {
|
||||
"TOO MANY TYPE ARGUMENTS".to_string()
|
||||
} else {
|
||||
"TOO FEW TYPE ARGUMENTS".to_string()
|
||||
};
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
CyclicAlias(symbol, region, others) => {
|
||||
let (doc, title) = cyclic_alias(alloc, symbol, region, others);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
other => panic!("unhandled bad type: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cyclic_alias<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
symbol: Symbol,
|
||||
region: roc_region::all::Region,
|
||||
others: Vec<Symbol>,
|
||||
) -> (RocDocBuilder<'b>, String) {
|
||||
let doc = if others.is_empty() {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(" alias is self-recursive in an invalid way:")),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."),
|
||||
])
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(" alias is recursive in an invalid way:")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(
|
||||
" alias depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.symbol_unqualified(symbol),
|
||||
others
|
||||
.into_iter()
|
||||
.map(|other| alloc.symbol_unqualified(other))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."),
|
||||
])
|
||||
};
|
||||
|
||||
(doc, "CYCLIC ALIAS".to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn report_mismatch<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
|
@ -1849,7 +1951,7 @@ mod report_text {
|
|||
fs: Vec<(Lowercase, ErrorType)>,
|
||||
ext: TypeExt,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::{ext_to_doc, to_doc};
|
||||
use crate::error::r#type::{ext_to_doc, to_doc};
|
||||
|
||||
let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| {
|
||||
(
|
||||
|
@ -2027,9 +2129,9 @@ mod report_text {
|
|||
|
||||
fn type_problem_to_pretty<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
problem: crate::type_error::Problem,
|
||||
problem: crate::error::r#type::Problem,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::Problem::*;
|
||||
use crate::error::r#type::Problem::*;
|
||||
|
||||
match problem {
|
||||
FieldTypo(typo, possibilities) => {
|
|
@ -11,5 +11,5 @@
|
|||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod error;
|
||||
pub mod report;
|
||||
pub mod type_error;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||
|
||||
pub use crate::error::canonicalize::can_problem;
|
||||
pub use crate::error::mono::mono_problem;
|
||||
pub use crate::error::parse::parse_problem;
|
||||
pub use crate::error::r#type::type_problem;
|
||||
|
||||
// const IS_WINDOWS: bool = std::env::consts::OS == "windows";
|
||||
const IS_WINDOWS: bool = false;
|
||||
|
||||
|
@ -17,7 +19,9 @@ const CYCLE_LN: &str = ["| ", "│ "][!IS_WINDOWS as usize];
|
|||
const CYCLE_MID: &str = ["| |", "│ ↓"][!IS_WINDOWS as usize];
|
||||
const CYCLE_END: &str = ["+-<---+", "└─────┘"][!IS_WINDOWS as usize];
|
||||
|
||||
fn cycle<'b>(
|
||||
const GUTTER_BAR: &str = " ┆";
|
||||
|
||||
pub fn cycle<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
indent: usize,
|
||||
name: RocDocBuilder<'b>,
|
||||
|
@ -142,417 +146,6 @@ pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
|||
|
||||
pub const RESET_CODE: &str = "\u{001b}[0m";
|
||||
|
||||
pub fn mono_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: roc_mono::expr::MonoProblem,
|
||||
) -> Report<'b> {
|
||||
use roc_mono::expr::MonoProblem::*;
|
||||
use roc_mono::pattern::Context::*;
|
||||
use roc_mono::pattern::Error::*;
|
||||
|
||||
match problem {
|
||||
PatternProblem(Incomplete(region, context, missing)) => match context {
|
||||
BadArg => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
So rather than pattern matching in function arguments, put a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" in the function body to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadDestruct => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
You can use a binding to deconstruct a value if there is only ONE possibility. \
|
||||
Use a "
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" to account for all possibilities."),
|
||||
]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
BadCase => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" does not cover all the possibilities:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Other possibilities include:"),
|
||||
unhandled_patterns_to_doc_block(alloc, missing),
|
||||
alloc.reflow(
|
||||
"I would have to crash if I saw one of those! \
|
||||
Add branches for them!",
|
||||
),
|
||||
// alloc.hint().append(alloc.reflow("or use a hole.")),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "UNSAFE PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
},
|
||||
PatternProblem(Redundant {
|
||||
overall_region,
|
||||
branch_region,
|
||||
index,
|
||||
}) => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.string(index.ordinal()),
|
||||
alloc.reflow(" pattern is redundant:"),
|
||||
]),
|
||||
alloc.region_with_subregion(overall_region, branch_region),
|
||||
alloc.reflow(
|
||||
"Any value of this shape will be handled by \
|
||||
a previous pattern, so this one should be removed.",
|
||||
),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "REDUNDANT PATTERN".to_string(),
|
||||
doc,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
patterns: Vec<roc_mono::pattern::Pattern>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc
|
||||
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
|
||||
.indent(4)
|
||||
.annotate(Annotation::TypeBlock)
|
||||
}
|
||||
|
||||
fn pattern_to_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::pattern::Pattern,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use roc_mono::pattern::Literal::*;
|
||||
use roc_mono::pattern::Pattern::*;
|
||||
// Anything,
|
||||
// Literal(Literal),
|
||||
// Ctor(Union, TagName, std::vec::Vec<Pattern>),
|
||||
match pattern {
|
||||
Anything => alloc.text("_"),
|
||||
Literal(l) => match l {
|
||||
Int(i) => alloc.text(i.to_string()),
|
||||
Bit(true) => alloc.text("True"),
|
||||
Bit(false) => alloc.text("False"),
|
||||
Byte(b) => alloc.text(b.to_string()),
|
||||
Float(f) => alloc.text(f.to_string()),
|
||||
Str(s) => alloc.string(s.into()),
|
||||
},
|
||||
Ctor(_, tag_name, args) => {
|
||||
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
||||
|
||||
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
||||
|
||||
alloc.intersperse(docs, alloc.space())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Problem,
|
||||
) -> Report<'b> {
|
||||
let doc = match problem {
|
||||
Problem::UnusedDef(symbol, region) => {
|
||||
let line =
|
||||
r#" then remove it so future readers of your code don't wonder why it is there."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is not used anywhere in your code.")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("If you didn't intend on using ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
|
||||
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
|
||||
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(" doesn't use "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow("."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("If you don't need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(", then you can just remove it. However, if you really do need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(" as an argument of "),
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(", prefix it with an underscore, like this: \"_"),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
|
||||
.stack(vec![
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using more than one "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" like this requires parentheses,",
|
||||
" to clarify how things should be grouped.",
|
||||
)),
|
||||
])
|
||||
} else {
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Using "),
|
||||
alloc.binop(left_bin_op.value),
|
||||
alloc.reflow(" and "),
|
||||
alloc.binop(right_bin_op.value),
|
||||
alloc.reflow(concat!(
|
||||
" together requires parentheses, ",
|
||||
"to clarify how they should be grouped."
|
||||
)),
|
||||
])
|
||||
},
|
||||
alloc.region(region),
|
||||
]),
|
||||
Problem::UnsupportedPattern(pattern_type, region) => {
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
|
||||
let this_thing = match pattern_type {
|
||||
TopLevelDef => "a top-level definition:",
|
||||
DefExpr => "a value definition:",
|
||||
FunctionArg => "function arguments:",
|
||||
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
||||
};
|
||||
|
||||
let suggestion = vec![
|
||||
alloc.reflow(
|
||||
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
||||
),
|
||||
alloc.keyword("when"),
|
||||
alloc.reflow(" ... "),
|
||||
alloc.keyword("is"),
|
||||
alloc.reflow(" instead."),
|
||||
];
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("This pattern is not allowed in ")
|
||||
.append(alloc.reflow(this_thing)),
|
||||
alloc.region(region),
|
||||
alloc.concat(suggestion),
|
||||
])
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
original_region,
|
||||
shadow,
|
||||
} => pretty_runtime_error(
|
||||
alloc,
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
},
|
||||
),
|
||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||
};
|
||||
|
||||
Report {
|
||||
title: "SYNTAX PROBLEM".to_string(),
|
||||
filename,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
region: roc_region::all::Region,
|
||||
name: &str,
|
||||
thing: &str,
|
||||
options: MutSet<Box<str>>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::suggest;
|
||||
|
||||
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
||||
suggestions.truncate(4);
|
||||
|
||||
let default_no = alloc.concat(vec![
|
||||
alloc.reflow("Is there an "),
|
||||
alloc.keyword("import"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" missing up-top"),
|
||||
]);
|
||||
|
||||
let default_yes = alloc.reflow("these names seem close though:");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
no_suggestion_details
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
yes_suggestion_details,
|
||||
alloc
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc.string(format!("I cannot find a `{}` {}", name, thing)),
|
||||
alloc.region(region),
|
||||
to_details(default_no, default_yes),
|
||||
])
|
||||
}
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(mut idents, regions) => {
|
||||
let first = idents.remove(0);
|
||||
|
||||
if idents.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO hint?
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(
|
||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||
),
|
||||
alloc.region(regions[0].0),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(first.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.ident(first.value),
|
||||
idents
|
||||
.into_iter()
|
||||
.map(|ident| alloc.ident(ident.value))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO hint?
|
||||
])
|
||||
}
|
||||
}
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// SymbolNotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// 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
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers
|
||||
pub struct RocDocAllocator<'a> {
|
||||
upstream: BoxAllocator,
|
||||
|
@ -711,6 +304,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.text(content.to_string()).annotate(Annotation::BinOp)
|
||||
}
|
||||
|
||||
/// Turns of backticks/colors in a block
|
||||
pub fn type_block(
|
||||
&'a self,
|
||||
content: DocBuilder<'a, Self, Annotation>,
|
||||
|
@ -724,6 +318,107 @@ impl<'a> RocDocAllocator<'a> {
|
|||
.annotate(Annotation::Hint)
|
||||
}
|
||||
|
||||
pub fn region_all_the_things(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
sub_region1: roc_region::all::Region,
|
||||
sub_region2: roc_region::all::Region,
|
||||
error_annotation: Annotation,
|
||||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
debug_assert!(region.contains(&sub_region1));
|
||||
debug_assert!(region.contains(&sub_region2));
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// where the problem is.
|
||||
let error_highlight_line = region.start_line == region.end_line;
|
||||
|
||||
let max_line_number_length = (region.end_line + 1).to_string().len();
|
||||
let indent = 2;
|
||||
|
||||
let mut result = self.nil();
|
||||
for i in region.start_line..=region.end_line {
|
||||
let line_number_string = (i + 1).to_string();
|
||||
let line_number = line_number_string;
|
||||
let this_line_number_length = line_number.len();
|
||||
|
||||
let line = self.src_lines[i as usize];
|
||||
|
||||
let rest_of_line = if !line.trim().is_empty() {
|
||||
self.text(line).indent(indent)
|
||||
} else {
|
||||
self.nil()
|
||||
};
|
||||
|
||||
let highlight = !error_highlight_line
|
||||
&& ((i >= sub_region1.start_line && i <= sub_region1.end_line)
|
||||
|| (i >= sub_region2.start_line && i <= sub_region2.end_line));
|
||||
|
||||
let source_line = if highlight {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(">").annotate(error_annotation))
|
||||
.append(rest_of_line)
|
||||
} else if error_highlight_line {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(rest_of_line)
|
||||
} else {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(" "))
|
||||
.append(rest_of_line)
|
||||
};
|
||||
|
||||
result = result.append(source_line);
|
||||
|
||||
if i != region.end_line {
|
||||
result = result.append(self.line())
|
||||
}
|
||||
}
|
||||
|
||||
if error_highlight_line {
|
||||
let overlapping = sub_region2.start_col < sub_region1.end_col;
|
||||
|
||||
let highlight = if overlapping {
|
||||
self.text("^".repeat((sub_region2.end_col - sub_region1.start_col) as usize))
|
||||
} else {
|
||||
let highlight1 = "^".repeat((sub_region1.end_col - sub_region1.start_col) as usize);
|
||||
let highlight2 = if sub_region1 == sub_region2 {
|
||||
"".repeat(0)
|
||||
} else {
|
||||
"^".repeat((sub_region2.end_col - sub_region2.start_col) as usize)
|
||||
};
|
||||
let inbetween = " "
|
||||
.repeat((sub_region2.start_col.saturating_sub(sub_region1.end_col)) as usize);
|
||||
|
||||
self.text(highlight1)
|
||||
.append(self.text(inbetween))
|
||||
.append(self.text(highlight2))
|
||||
};
|
||||
|
||||
let highlight_line = self
|
||||
.line()
|
||||
.append(self.text(" ".repeat(max_line_number_length)))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(if sub_region1.is_empty() && sub_region2.is_empty() {
|
||||
self.nil()
|
||||
} else {
|
||||
self.text(" ".repeat(sub_region1.start_col as usize))
|
||||
.indent(indent)
|
||||
.append(highlight)
|
||||
.annotate(error_annotation)
|
||||
});
|
||||
|
||||
result = result.append(highlight_line);
|
||||
}
|
||||
|
||||
result.annotate(Annotation::CodeBlock)
|
||||
}
|
||||
|
||||
pub fn region_with_subregion(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
|
@ -731,6 +426,11 @@ impl<'a> RocDocAllocator<'a> {
|
|||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
debug_assert!(region.contains(&sub_region));
|
||||
|
||||
// If the outer region takes more than 1 full screen (~60 lines), only show the inner region
|
||||
if region.end_line - region.start_line > 60 {
|
||||
return self.region_with_subregion(sub_region, sub_region);
|
||||
}
|
||||
|
||||
// if true, the final line of the snippet will be some ^^^ that point to the region where
|
||||
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
|
||||
// where the problem is.
|
||||
|
@ -761,18 +461,18 @@ impl<'a> RocDocAllocator<'a> {
|
|||
{
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(">").annotate(Annotation::Error))
|
||||
.append(rest_of_line)
|
||||
} else if error_highlight_line {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(rest_of_line)
|
||||
} else {
|
||||
self.text(" ".repeat(max_line_number_length - this_line_number_length))
|
||||
.append(self.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(self.text(" "))
|
||||
.append(rest_of_line)
|
||||
};
|
||||
|
@ -789,7 +489,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
let highlight_line = self
|
||||
.line()
|
||||
.append(self.text(" ".repeat(max_line_number_length)))
|
||||
.append(self.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(self.text(GUTTER_BAR).annotate(Annotation::GutterBar))
|
||||
.append(if highlight_text.is_empty() {
|
||||
self.nil()
|
||||
} else {
|
||||
|
@ -808,6 +508,40 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.region_with_subregion(region, region)
|
||||
}
|
||||
|
||||
pub fn region_without_error(
|
||||
&'a self,
|
||||
region: roc_region::all::Region,
|
||||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
let mut result = self.nil();
|
||||
for i in region.start_line..=region.end_line {
|
||||
let line = if i == region.start_line {
|
||||
if i == region.end_line {
|
||||
&self.src_lines[i as usize][region.start_col as usize..region.end_col as usize]
|
||||
} else {
|
||||
&self.src_lines[i as usize][region.start_col as usize..]
|
||||
}
|
||||
} else if i == region.end_line {
|
||||
&self.src_lines[i as usize][0..region.end_col as usize]
|
||||
} else {
|
||||
self.src_lines[i as usize]
|
||||
};
|
||||
|
||||
let rest_of_line = if !line.trim().is_empty() {
|
||||
self.text(line).annotate(Annotation::CodeBlock)
|
||||
} else {
|
||||
self.nil()
|
||||
};
|
||||
|
||||
result = result.append(rest_of_line);
|
||||
|
||||
if i != region.end_line {
|
||||
result = result.append(self.line())
|
||||
}
|
||||
}
|
||||
|
||||
result.indent(4)
|
||||
}
|
||||
|
||||
pub fn ident(&'a self, ident: Ident) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text(format!("{}", ident.as_inline_str()))
|
||||
.annotate(Annotation::Symbol)
|
||||
|
@ -843,6 +577,7 @@ pub enum Annotation {
|
|||
pub struct CiWrite<W> {
|
||||
style_stack: Vec<Annotation>,
|
||||
in_type_block: bool,
|
||||
in_code_block: bool,
|
||||
upstream: W,
|
||||
}
|
||||
|
||||
|
@ -851,6 +586,7 @@ impl<W> CiWrite<W> {
|
|||
CiWrite {
|
||||
style_stack: vec![],
|
||||
in_type_block: false,
|
||||
in_code_block: false,
|
||||
upstream,
|
||||
}
|
||||
}
|
||||
|
@ -898,6 +634,9 @@ where
|
|||
TypeBlock => {
|
||||
self.in_type_block = true;
|
||||
}
|
||||
CodeBlock => {
|
||||
self.in_code_block = true;
|
||||
}
|
||||
Emphasized => {
|
||||
self.write_str("*")?;
|
||||
}
|
||||
|
@ -906,7 +645,7 @@ where
|
|||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
if !self.in_type_block && !self.in_code_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
}
|
||||
|
@ -926,6 +665,9 @@ where
|
|||
TypeBlock => {
|
||||
self.in_type_block = false;
|
||||
}
|
||||
CodeBlock => {
|
||||
self.in_code_block = false;
|
||||
}
|
||||
Emphasized => {
|
||||
self.write_str("*")?;
|
||||
}
|
||||
|
@ -934,7 +676,7 @@ where
|
|||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
if !self.in_type_block && !self.in_code_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
extern crate bumpalo;
|
||||
|
||||
use self::bumpalo::Bump;
|
||||
use roc_builtins::unique::uniq_stdlib;
|
||||
use roc_can::constraint::Constraint;
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
|
@ -11,13 +10,12 @@ use roc_can::scope::Scope;
|
|||
use roc_collections::all::{ImMap, MutMap, SendMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
|
||||
use roc_parse::ast::{self, Attempting};
|
||||
use roc_parse::blankspace::space0_before;
|
||||
use roc_parse::parser::{loc, Fail, Parser, State};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_region::all::Located;
|
||||
use roc_solve::solve;
|
||||
use roc_types::subs::{Content, Subs, VarStore, Variable};
|
||||
use roc_types::types::Type;
|
||||
|
@ -103,96 +101,10 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr(expr_str: &str) -> CanExprOut {
|
||||
pub fn can_expr(expr_str: &str) -> Result<CanExprOut, ParseErrOut> {
|
||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uniq_expr(
|
||||
expr_str: &str,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
|
||||
|
||||
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uniq_expr_with(
|
||||
arena: &Bump,
|
||||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let home = test_home();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems,
|
||||
var_store: old_var_store,
|
||||
var,
|
||||
interns,
|
||||
..
|
||||
} = can_expr_with(arena, home, expr_str);
|
||||
|
||||
// double check
|
||||
let var_store = VarStore::new(old_var_store.fresh());
|
||||
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(var));
|
||||
let constraint = roc_constrain::uniq::constrain_declaration(
|
||||
home,
|
||||
&var_store,
|
||||
Region::zero(),
|
||||
&loc_expr,
|
||||
declared_idents,
|
||||
expected2,
|
||||
);
|
||||
|
||||
let stdlib = uniq_stdlib();
|
||||
|
||||
let types = stdlib.types;
|
||||
let imports: Vec<_> = types
|
||||
.iter()
|
||||
.map(|(symbol, (solved_type, region))| Import {
|
||||
loc_symbol: Located::at(*region, *symbol),
|
||||
solved_type: solved_type,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// load builtin values
|
||||
|
||||
// TODO what to do with those rigids?
|
||||
let (_introduced_rigids, constraint) =
|
||||
constrain_imported_values(imports, constraint, &var_store);
|
||||
|
||||
// load builtin types
|
||||
let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
|
||||
|
||||
constraint.instantiate_aliases(&var_store);
|
||||
|
||||
let subs2 = Subs::new(var_store.into());
|
||||
|
||||
(
|
||||
loc_expr, output, problems, subs2, var, constraint, home, interns,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct CanExprOut {
|
||||
pub loc_expr: Located<Expr>,
|
||||
pub output: Output,
|
||||
|
@ -204,14 +116,34 @@ pub struct CanExprOut {
|
|||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseErrOut {
|
||||
pub fail: Fail,
|
||||
pub home: ModuleId,
|
||||
pub interns: Interns,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
|
||||
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
|
||||
expr_str, e
|
||||
)
|
||||
});
|
||||
pub fn can_expr_with(
|
||||
arena: &Bump,
|
||||
home: ModuleId,
|
||||
expr_str: &str,
|
||||
) -> Result<CanExprOut, ParseErrOut> {
|
||||
let loc_expr = match parse_loc_with(&arena, expr_str) {
|
||||
Ok(e) => e,
|
||||
Err(fail) => {
|
||||
let interns = Interns {
|
||||
module_ids: ModuleIds::default(),
|
||||
all_ident_ids: MutMap::default(),
|
||||
};
|
||||
|
||||
return Err(ParseErrOut {
|
||||
fail,
|
||||
interns,
|
||||
home,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
|
@ -283,7 +215,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
all_ident_ids,
|
||||
};
|
||||
|
||||
CanExprOut {
|
||||
Ok(CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems: env.problems,
|
||||
|
@ -292,7 +224,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
interns,
|
||||
var,
|
||||
constraint,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -14,15 +14,15 @@ mod test_reporting {
|
|||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::expr::{Expr, Procs};
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE,
|
||||
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
|
||||
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
||||
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
||||
WHITE_CODE, YELLOW_CODE,
|
||||
};
|
||||
use roc_reporting::type_error::type_problem;
|
||||
use roc_types::pretty_print::name_all_type_vars;
|
||||
use roc_types::subs::Subs;
|
||||
use std::path::PathBuf;
|
||||
// use roc_region::all;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::solve;
|
||||
|
||||
|
@ -43,13 +43,16 @@ mod test_reporting {
|
|||
|
||||
fn infer_expr_help(
|
||||
expr_src: &str,
|
||||
) -> (
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Vec<roc_mono::expr::MonoProblem>,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
) -> Result<
|
||||
(
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Vec<roc_mono::expr::MonoProblem>,
|
||||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
ParseErrOut,
|
||||
> {
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
|
@ -60,7 +63,7 @@ mod test_reporting {
|
|||
mut interns,
|
||||
problems: can_problems,
|
||||
..
|
||||
} = can_expr(expr_src);
|
||||
} = can_expr(expr_src)?;
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
for (var, name) in output.introduced_variables.name_by_var {
|
||||
|
@ -99,7 +102,7 @@ mod test_reporting {
|
|||
);
|
||||
}
|
||||
|
||||
(unify_problems, can_problems, mono_problems, home, interns)
|
||||
Ok((unify_problems, can_problems, mono_problems, home, interns))
|
||||
}
|
||||
|
||||
fn list_reports<F>(src: &str, buf: &mut String, callback: F)
|
||||
|
@ -108,40 +111,57 @@ mod test_reporting {
|
|||
{
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let (type_problems, can_problems, mono_problems, home, interns) = infer_expr_help(src);
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||||
let mut reports = Vec::new();
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
match infer_expr_help(src) {
|
||||
Err(parse_err) => {
|
||||
let ParseErrOut {
|
||||
fail,
|
||||
home,
|
||||
interns,
|
||||
} = parse_err;
|
||||
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let doc = parse_problem(&alloc, filename, fail);
|
||||
|
||||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||||
}
|
||||
Ok((type_problems, can_problems, mono_problems, home, interns)) => {
|
||||
let mut reports = Vec::new();
|
||||
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
callback(doc, buf)
|
||||
}
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
callback(doc, buf)
|
||||
}
|
||||
|
||||
fn report_problem_as(src: &str, expected_rendering: &str) {
|
||||
|
@ -491,7 +511,8 @@ mod test_reporting {
|
|||
"#
|
||||
);
|
||||
|
||||
let (_type_problems, _can_problems, _mono_problems, home, interns) = infer_expr_help(src);
|
||||
let (_type_problems, _can_problems, _mono_problems, home, interns) =
|
||||
infer_expr_help(src).expect("parse error");
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -521,7 +542,7 @@ mod test_reporting {
|
|||
);
|
||||
|
||||
let (_type_problems, _can_problems, _mono_problems, home, mut interns) =
|
||||
infer_expr_help(src);
|
||||
infer_expr_help(src).expect("parse error");
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -2257,4 +2278,439 @@ mod test_reporting {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn circular_alias() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x: Bar }
|
||||
Bar : { y : Foo }
|
||||
|
||||
f : Foo
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
// should not report Bar as unused!
|
||||
indoc!(
|
||||
r#"
|
||||
-- CYCLIC ALIAS ----------------------------------------------------------------
|
||||
|
||||
The `Bar` alias is recursive in an invalid way:
|
||||
|
||||
2 ┆ Bar : { y : Foo }
|
||||
┆ ^^^^^^^^^^^
|
||||
|
||||
The `Bar` alias depends on itself through the following chain of
|
||||
definitions:
|
||||
|
||||
┌─────┐
|
||||
│ Bar
|
||||
│ ↓
|
||||
│ Foo
|
||||
└─────┘
|
||||
|
||||
Recursion in aliases is only allowed if recursion happens behind a
|
||||
tag.
|
||||
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
`Bar` is not used anywhere in your code.
|
||||
|
||||
2 ┆ Bar : { y : Foo }
|
||||
┆ ^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you didn't intend on using `Bar` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_recursive_alias() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Foo }
|
||||
|
||||
f : Foo
|
||||
f = 3
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
// should not report Bar as unused!
|
||||
indoc!(
|
||||
r#"
|
||||
-- CYCLIC ALIAS ----------------------------------------------------------------
|
||||
|
||||
The `Foo` alias is self-recursive in an invalid way:
|
||||
|
||||
1 ┆ Foo : { x : Foo }
|
||||
┆ ^^^
|
||||
|
||||
Recursion in aliases is only allowed if recursion happens behind a
|
||||
tag.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_same_type() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 4, y: 3, x: 4 }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: 4 }
|
||||
┆ ^^^^ ^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: 4 }
|
||||
┆ ^^^^
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_different_types() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 4, y: 3, x: "foo" }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: "foo" }
|
||||
┆ ^^^^ ^^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ { x: 4, y: 3, x: "foo" }
|
||||
┆ ^^^^^^^^
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_duplicate_field_multiline() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
x: 4,
|
||||
y: 3,
|
||||
x: "foo"
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
1 ┆ {
|
||||
2 ┆> x: 4,
|
||||
3 ┆ y: 3,
|
||||
4 ┆> x: "foo"
|
||||
5 ┆ }
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ {
|
||||
2 ┆ x: 4,
|
||||
3 ┆ y: 3,
|
||||
4 ┆> x: "foo"
|
||||
5 ┆ }
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_update_duplicate_field_multiline() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
{ r &
|
||||
x: 4,
|
||||
y: 3,
|
||||
x: "foo"
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record defines the `.x` field twice!
|
||||
|
||||
2 ┆ { r &
|
||||
3 ┆> x: 4,
|
||||
4 ┆ y: 3,
|
||||
5 ┆> x: "foo"
|
||||
6 ┆ }
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
2 ┆ { r &
|
||||
3 ┆ x: 4,
|
||||
4 ┆ y: 3,
|
||||
5 ┆> x: "foo"
|
||||
6 ┆ }
|
||||
|
||||
For clarity, remove the previous `.x` definitions from this record.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_type_duplicate_field() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : { foo : Int, bar : Float, foo : Str }
|
||||
a = { bar: 3.0, foo: "foo" }
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This record type defines the `.foo` field twice!
|
||||
|
||||
1 ┆ a : { foo : Int, bar : Float, foo : Str }
|
||||
┆ ^^^^^^^^^ ^^^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ a : { foo : Int, bar : Float, foo : Str }
|
||||
┆ ^^^^^^^^^
|
||||
|
||||
For clarity, remove the previous `.foo` definitions from this record
|
||||
type.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_union_duplicate_tag() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : [ Foo Int, Bar Float, Foo Str ]
|
||||
a = Foo "foo"
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
This tag union type defines the `Foo` tag twice!
|
||||
|
||||
1 ┆ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
┆ ^^^^^^^ ^^^^^^^
|
||||
|
||||
In the rest of the program, I will only use the latter definition:
|
||||
|
||||
1 ┆ a : [ Foo Int, Bar Float, Foo Str ]
|
||||
┆ ^^^^^^^
|
||||
|
||||
For clarity, remove the previous `Foo` definitions from this tag union
|
||||
type.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_num() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
a : Num Int Float
|
||||
a = 3
|
||||
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1 ┆ a : Num Int Float
|
||||
┆ ^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_num_fn() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
f : Bool -> Num Int Float
|
||||
f = \_ -> 3
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||||
|
||||
1 ┆ f : Bool -> Num Int Float
|
||||
┆ ^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_few_type_arguments() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Pair a b : [ Pair a b ]
|
||||
|
||||
x : Pair Int
|
||||
x = 3
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO FEW TYPE ARGUMENTS ------------------------------------------------------
|
||||
|
||||
The `Pair` alias expects 2 type arguments, but it got 1 instead:
|
||||
|
||||
3 ┆ x : Pair Int
|
||||
┆ ^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_type_arguments() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Pair a b : [ Pair a b ]
|
||||
|
||||
x : Pair Int Int Int
|
||||
x = 3
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
|
||||
|
||||
The `Pair` alias expects 2 type arguments, but it got 3 instead:
|
||||
|
||||
3 ┆ x : Pair Int Int Int
|
||||
┆ ^^^^^^^^^^^^^^^^
|
||||
|
||||
Are there missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn phantom_type_variable() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo a : [ Foo ]
|
||||
|
||||
f : Foo Int
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
The `a` type variable is not used in the `Foo` alias definition:
|
||||
|
||||
1 ┆ Foo a : [ Foo ]
|
||||
┆ ^
|
||||
|
||||
Roc does not allow unused type parameters!
|
||||
|
||||
Hint: If you want an unused type parameter (a so-called "phantom
|
||||
type"), read the guide section on phantom data.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elm_function_syntax() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
f x y = x
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- PARSE PROBLEM ---------------------------------------------------------------
|
||||
|
||||
Unexpected tokens in front of the `=` symbol:
|
||||
|
||||
1 ┆ f x y = x
|
||||
┆ ^^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -21,6 +21,7 @@ pub enum TypeError {
|
|||
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
|
||||
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
|
||||
CircularType(Region, Symbol, ErrorType),
|
||||
BadType(roc_types::types::Problem),
|
||||
}
|
||||
|
||||
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
|
||||
|
@ -166,6 +167,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -228,6 +236,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +293,13 @@ fn solve(
|
|||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -829,7 +851,7 @@ fn circular_error(
|
|||
loc_var: &Located<Variable>,
|
||||
) {
|
||||
let var = loc_var.value;
|
||||
let error_type = subs.var_to_error_type(var);
|
||||
let (error_type, _) = subs.var_to_error_type(var);
|
||||
let problem = TypeError::CircularType(loc_var.region, symbol, error_type);
|
||||
|
||||
subs.set_content(var, Content::Error);
|
||||
|
|
|
@ -14,7 +14,7 @@ ven_ena = { path = "../../vendor/ena" }
|
|||
inlinable_string = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -37,9 +37,10 @@ impl fmt::Debug for Mark {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct NameState {
|
||||
struct ErrorTypeState {
|
||||
taken: MutSet<Lowercase>,
|
||||
normals: u32,
|
||||
problems: Vec<crate::types::Problem>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
@ -363,7 +364,7 @@ impl Subs {
|
|||
explicit_substitute(self, x, y, z, &mut seen)
|
||||
}
|
||||
|
||||
pub fn var_to_error_type(&mut self, var: Variable) -> ErrorType {
|
||||
pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec<crate::types::Problem>) {
|
||||
let names = get_var_names(self, var, ImMap::default());
|
||||
let mut taken = MutSet::default();
|
||||
|
||||
|
@ -371,9 +372,13 @@ impl Subs {
|
|||
taken.insert(name);
|
||||
}
|
||||
|
||||
let mut state = NameState { taken, normals: 0 };
|
||||
let mut state = ErrorTypeState {
|
||||
taken,
|
||||
normals: 0,
|
||||
problems: Vec::new(),
|
||||
};
|
||||
|
||||
var_to_err_type(self, &mut state, var)
|
||||
(var_to_err_type(self, &mut state, var), state.problems)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, var: Variable) {
|
||||
|
@ -1114,7 +1119,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> ErrorType {
|
||||
fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType {
|
||||
let desc = subs.get(var);
|
||||
|
||||
if desc.mark == Mark::OCCURS {
|
||||
|
@ -1132,7 +1137,7 @@ fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> Err
|
|||
|
||||
fn content_to_err_type(
|
||||
subs: &mut Subs,
|
||||
state: &mut NameState,
|
||||
state: &mut ErrorTypeState,
|
||||
var: Variable,
|
||||
content: Content,
|
||||
) -> ErrorType {
|
||||
|
@ -1174,7 +1179,11 @@ fn content_to_err_type(
|
|||
}
|
||||
}
|
||||
|
||||
fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: FlatType) -> ErrorType {
|
||||
fn flat_type_to_err_type(
|
||||
subs: &mut Subs,
|
||||
state: &mut ErrorTypeState,
|
||||
flat_type: FlatType,
|
||||
) -> ErrorType {
|
||||
use self::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
|
@ -1296,11 +1305,15 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
|
|||
|
||||
Boolean(b) => ErrorType::Boolean(b),
|
||||
|
||||
Erroneous(_) => ErrorType::Error,
|
||||
Erroneous(problem) => {
|
||||
state.problems.push(problem);
|
||||
|
||||
ErrorType::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fresh_var_name(state: &mut NameState) -> Lowercase {
|
||||
fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase {
|
||||
let (name, new_index) = name_type_var(state.normals, &mut state.taken);
|
||||
|
||||
state.normals = new_index;
|
||||
|
|
|
@ -395,6 +395,7 @@ impl Type {
|
|||
|
||||
pub fn instantiate_aliases(
|
||||
&mut self,
|
||||
region: Region,
|
||||
aliases: &ImMap<Symbol, Alias>,
|
||||
var_store: &VarStore,
|
||||
introduced: &mut ImSet<Variable>,
|
||||
|
@ -404,34 +405,44 @@ impl Type {
|
|||
match self {
|
||||
Function(args, ret) => {
|
||||
for arg in args {
|
||||
arg.instantiate_aliases(aliases, var_store, introduced);
|
||||
arg.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
ret.instantiate_aliases(aliases, var_store, introduced);
|
||||
ret.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
}
|
||||
ext.instantiate_aliases(aliases, var_store, introduced);
|
||||
ext.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for x in fields.iter_mut() {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
ext.instantiate_aliases(aliases, var_store, introduced);
|
||||
ext.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Alias(_, type_args, actual_type) => {
|
||||
for arg in type_args {
|
||||
arg.1.instantiate_aliases(aliases, var_store, introduced);
|
||||
arg.1
|
||||
.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
|
||||
actual_type.instantiate_aliases(aliases, var_store, introduced);
|
||||
actual_type.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
if let Some(alias) = aliases.get(symbol) {
|
||||
debug_assert!(args.len() == alias.vars.len());
|
||||
if args.len() != alias.vars.len() {
|
||||
*self = Type::Erroneous(Problem::BadTypeArguments {
|
||||
symbol: *symbol,
|
||||
region,
|
||||
type_got: args.len() as u8,
|
||||
alias_needs: alias.vars.len() as u8,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let mut actual = alias.typ.clone();
|
||||
|
||||
let mut named_args = Vec::with_capacity(args.len());
|
||||
|
@ -447,7 +458,7 @@ impl Type {
|
|||
) in alias.vars.iter().zip(args.iter())
|
||||
{
|
||||
let mut filler = filler.clone();
|
||||
filler.instantiate_aliases(aliases, var_store, introduced);
|
||||
filler.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
named_args.push((lowercase.clone(), filler.clone()));
|
||||
substitution.insert(*placeholder, filler);
|
||||
}
|
||||
|
@ -463,7 +474,7 @@ impl Type {
|
|||
}
|
||||
|
||||
actual.substitute(&substitution);
|
||||
actual.instantiate_aliases(aliases, var_store, introduced);
|
||||
actual.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
|
||||
// instantiate recursion variable!
|
||||
if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual {
|
||||
|
@ -487,7 +498,7 @@ impl Type {
|
|||
} else {
|
||||
// one of the special-cased Apply types.
|
||||
for x in args {
|
||||
x.instantiate_aliases(aliases, var_store, introduced);
|
||||
x.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -691,11 +702,18 @@ pub struct Alias {
|
|||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Problem {
|
||||
CanonicalizationProblem,
|
||||
Mismatch(Mismatch, ErrorType, ErrorType),
|
||||
CircularType(Symbol, ErrorType, Region),
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
UnrecognizedIdent(InlinableString),
|
||||
Shadowed(Region, Located<Ident>),
|
||||
BadTypeArguments {
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
type_got: u8,
|
||||
alias_needs: u8,
|
||||
},
|
||||
InvalidModule,
|
||||
SolvedTypeError,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
|
|
|
@ -10,7 +10,7 @@ roc_module = { path = "../module" }
|
|||
roc_types = { path = "../types" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
|
@ -73,6 +73,7 @@ struct Context {
|
|||
pub enum Unified {
|
||||
Success(Pool),
|
||||
Failure(Pool, ErrorType, ErrorType),
|
||||
BadType(Pool, roc_types::types::Problem),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -91,11 +92,18 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
|
|||
if mismatches.is_empty() {
|
||||
Unified::Success(vars)
|
||||
} else {
|
||||
let type1 = subs.var_to_error_type(var1);
|
||||
let type2 = subs.var_to_error_type(var2);
|
||||
let (type1, mut problems) = subs.var_to_error_type(var1);
|
||||
let (type2, problems2) = subs.var_to_error_type(var2);
|
||||
|
||||
problems.extend(problems2);
|
||||
|
||||
subs.union(var1, var2, Content::Error.into());
|
||||
Unified::Failure(vars, type1, type2)
|
||||
|
||||
if !problems.is_empty() {
|
||||
Unified::BadType(vars, problems.remove(0))
|
||||
} else {
|
||||
Unified::Failure(vars, type1, type2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
pretty_assertions = "0.5.1 "
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue