mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-14 07:29:02 +03:00
Merge branch 'trunk' into task_file_adjustments
This commit is contained in:
commit
16d8abebbd
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3058,6 +3058,7 @@ dependencies = [
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_types",
|
||||
"ropey",
|
||||
"serde",
|
||||
|
@ -466,6 +466,8 @@ fn link_macos(
|
||||
"-lc++",
|
||||
// "-lc++abi",
|
||||
// "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
|
||||
"-framework",
|
||||
"Security", // This "-framework Security" arg is needed for the `rand` crate in examples/cli
|
||||
// Output
|
||||
"-o",
|
||||
output_path.to_str().unwrap(), // app
|
||||
|
@ -14,7 +14,7 @@ use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, Type};
|
||||
@ -90,7 +90,7 @@ pub enum Declaration {
|
||||
Declare(Def),
|
||||
DeclareRec(Vec<Def>),
|
||||
Builtin(Def),
|
||||
InvalidCycle(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
||||
InvalidCycle(Vec<CycleEntry>),
|
||||
}
|
||||
|
||||
impl Declaration {
|
||||
@ -99,7 +99,7 @@ impl Declaration {
|
||||
match self {
|
||||
Declare(_) => 1,
|
||||
DeclareRec(defs) => defs.len(),
|
||||
InvalidCycle(_, _) => 0,
|
||||
InvalidCycle { .. } => 0,
|
||||
Builtin(_) => 0,
|
||||
}
|
||||
}
|
||||
@ -530,38 +530,41 @@ pub fn sort_can_defs(
|
||||
|
||||
if is_invalid_cycle {
|
||||
// We want to show the entire cycle in the error message, so expand it out.
|
||||
let mut loc_symbols = Vec::new();
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for symbol in cycle {
|
||||
match refs_by_symbol.get(&symbol) {
|
||||
for symbol in &cycle {
|
||||
match refs_by_symbol.get(symbol) {
|
||||
None => unreachable!(
|
||||
r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#,
|
||||
symbol, refs_by_symbol
|
||||
),
|
||||
Some((region, _)) => {
|
||||
loc_symbols.push(Located::at(*region, symbol));
|
||||
let expr_region =
|
||||
can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region;
|
||||
|
||||
let entry = CycleEntry {
|
||||
symbol: *symbol,
|
||||
symbol_region: *region,
|
||||
expr_region,
|
||||
};
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||
for def in can_defs_by_symbol.values() {
|
||||
regions.push((def.loc_pattern.region, def.loc_expr.region));
|
||||
}
|
||||
|
||||
// Sort them by line number to make the report more helpful.
|
||||
loc_symbols.sort();
|
||||
regions.sort();
|
||||
|
||||
let symbols_in_cycle: Vec<Symbol> =
|
||||
loc_symbols.into_iter().map(|s| s.value).collect();
|
||||
entries.sort_by_key(|entry| entry.symbol_region);
|
||||
|
||||
problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
symbols_in_cycle.clone(),
|
||||
regions.clone(),
|
||||
entries.clone(),
|
||||
)));
|
||||
|
||||
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
||||
declarations.push(Declaration::InvalidCycle(entries));
|
||||
|
||||
// other groups may depend on the symbols defined here, so
|
||||
// also push this cycle onto the groups
|
||||
groups.push(cycle);
|
||||
} else {
|
||||
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
||||
groups.push(cycle);
|
||||
@ -590,7 +593,12 @@ pub fn sort_can_defs(
|
||||
// find its successors
|
||||
for succ in all_successors_without_self(symbol) {
|
||||
// and add its group to the result
|
||||
result.insert(symbol_to_group_index[&succ]);
|
||||
match symbol_to_group_index.get(&succ) {
|
||||
Some(index) => {
|
||||
result.insert(*index);
|
||||
}
|
||||
None => unreachable!("no index for symbol {:?}", succ),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1297,8 +1305,8 @@ fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Located<Exp
|
||||
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh())
|
||||
}
|
||||
Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret), var_store.fresh()),
|
||||
Declaration::InvalidCycle(symbols, regions) => {
|
||||
Expr::RuntimeError(RuntimeError::CircularDef(symbols, regions))
|
||||
Declaration::InvalidCycle(entries) => {
|
||||
Expr::RuntimeError(RuntimeError::CircularDef(entries))
|
||||
}
|
||||
Declaration::Builtin(_) => {
|
||||
// Builtins should only be added to top-level decls, not to let-exprs!
|
||||
|
@ -217,8 +217,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
InvalidCycle(identifiers, _) => {
|
||||
panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers);
|
||||
InvalidCycle(entries) => {
|
||||
env.problems.push(Problem::BadRecursion(entries.to_vec()));
|
||||
}
|
||||
Builtin(def) => {
|
||||
// Builtins cannot be exposed in module declarations.
|
||||
@ -289,7 +289,7 @@ where
|
||||
DeclareRec(defs) => {
|
||||
fix_values_captured_in_closure_defs(defs, &mut MutSet::default())
|
||||
}
|
||||
InvalidCycle(_, _) | Builtin(_) => {}
|
||||
InvalidCycle(_) | Builtin(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ mod test_can {
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::Recursive;
|
||||
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
use roc_region::all::Region;
|
||||
use std::{f64, i64};
|
||||
|
||||
@ -907,8 +907,7 @@ mod test_can {
|
||||
|
||||
assert_eq!(problems, Vec::new());
|
||||
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
||||
{
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_)) = loc_expr.value {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -936,17 +935,17 @@ mod test_can {
|
||||
..
|
||||
} = can_expr_with(&arena, home, src);
|
||||
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
||||
{
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_)) = loc_expr.value {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
vec![interns.symbol(home, "x".into())],
|
||||
vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))],
|
||||
));
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry {
|
||||
symbol: interns.symbol(home, "x".into()),
|
||||
symbol_region: Region::new(0, 0, 0, 1),
|
||||
expr_region: Region::new(0, 0, 4, 5),
|
||||
}]));
|
||||
|
||||
assert_eq!(is_circular_def, true);
|
||||
assert_eq!(problems, vec![problem]);
|
||||
@ -972,23 +971,28 @@ mod test_can {
|
||||
..
|
||||
} = can_expr_with(&arena, home, src);
|
||||
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
vec![
|
||||
interns.symbol(home, "x".into()),
|
||||
interns.symbol(home, "y".into()),
|
||||
interns.symbol(home, "z".into()),
|
||||
],
|
||||
vec![
|
||||
(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)),
|
||||
(Region::new(1, 1, 0, 1), Region::new(1, 1, 4, 5)),
|
||||
(Region::new(2, 2, 0, 1), Region::new(2, 2, 4, 5)),
|
||||
],
|
||||
));
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![
|
||||
CycleEntry {
|
||||
symbol: interns.symbol(home, "x".into()),
|
||||
symbol_region: Region::new(0, 0, 0, 1),
|
||||
expr_region: Region::new(0, 0, 4, 5),
|
||||
},
|
||||
CycleEntry {
|
||||
symbol: interns.symbol(home, "y".into()),
|
||||
symbol_region: Region::new(1, 1, 0, 1),
|
||||
expr_region: Region::new(1, 1, 4, 5),
|
||||
},
|
||||
CycleEntry {
|
||||
symbol: interns.symbol(home, "z".into()),
|
||||
symbol_region: Region::new(2, 2, 0, 1),
|
||||
expr_region: Region::new(2, 2, 4, 5),
|
||||
},
|
||||
]));
|
||||
|
||||
assert_eq!(problems, vec![problem]);
|
||||
|
||||
match loc_expr.value {
|
||||
RuntimeError(RuntimeError::CircularDef(_, _)) => (),
|
||||
RuntimeError(RuntimeError::CircularDef(_)) => (),
|
||||
actual => {
|
||||
panic!("Expected a CircularDef runtime error, but got {:?}", actual);
|
||||
}
|
||||
|
@ -1020,7 +1020,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
|
||||
Declaration::DeclareRec(defs) => {
|
||||
constraint = constrain_recursive_defs(&env, defs, constraint);
|
||||
}
|
||||
Declaration::InvalidCycle(_, _) => {
|
||||
Declaration::InvalidCycle(_) => {
|
||||
// invalid cycles give a canonicalization error. we skip them here.
|
||||
continue;
|
||||
}
|
||||
@ -1586,7 +1586,35 @@ pub fn rec_defs_help(
|
||||
})));
|
||||
rigid_info.def_types.extend(def_pattern_state.headers);
|
||||
}
|
||||
_ => todo!(),
|
||||
_ => {
|
||||
let expected = annotation_expected;
|
||||
|
||||
let ret_constraint =
|
||||
constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected);
|
||||
|
||||
let def_con = And(vec![
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: vec![],
|
||||
def_types: SendMap::default(),
|
||||
defs_constraint: True,
|
||||
ret_constraint,
|
||||
})),
|
||||
// Store type into AST vars. We use Store so errors aren't reported twice
|
||||
Store(signature, expr_var, std::file!(), std::line!()),
|
||||
]);
|
||||
|
||||
rigid_info.vars.extend(&new_rigids);
|
||||
|
||||
rigid_info.constraints.push(Let(Box::new(LetConstraint {
|
||||
rigid_vars: new_rigids,
|
||||
flex_vars: def_pattern_state.vars,
|
||||
def_types: SendMap::default(), // no headers introduced (at this level)
|
||||
defs_constraint: def_con,
|
||||
ret_constraint: True,
|
||||
})));
|
||||
rigid_info.def_types.extend(def_pattern_state.headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3827,8 +3827,9 @@ fn build_pending_specializations<'a>(
|
||||
)
|
||||
}
|
||||
}
|
||||
InvalidCycle(_loc_idents, _regions) => {
|
||||
todo!("TODO handle InvalidCycle");
|
||||
InvalidCycle(_entries) => {
|
||||
// do nothing?
|
||||
// this may mean the loc_symbols are not defined during codegen; is that a problem?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ mod test_load {
|
||||
}
|
||||
}
|
||||
Builtin(_) => {}
|
||||
cycle @ InvalidCycle(_, _) => {
|
||||
cycle @ InvalidCycle(_) => {
|
||||
panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,13 @@ use roc_parse::ast::Base;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_region::all::{Located, Region};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct CycleEntry {
|
||||
pub symbol: Symbol,
|
||||
pub symbol_region: Region,
|
||||
pub expr_region: Region,
|
||||
}
|
||||
|
||||
/// Problems that can occur in the course of canonicalization.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Problem {
|
||||
@ -24,6 +31,7 @@ pub enum Problem {
|
||||
shadow: Located<Ident>,
|
||||
},
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
BadRecursion(Vec<CycleEntry>),
|
||||
PhantomTypeArgument {
|
||||
alias: Symbol,
|
||||
variable_region: Region,
|
||||
@ -141,7 +149,7 @@ pub enum RuntimeError {
|
||||
},
|
||||
InvalidFloat(FloatErrorKind, Region, Box<str>),
|
||||
InvalidInt(IntErrorKind, Base, Region, Box<str>),
|
||||
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
||||
CircularDef(Vec<CycleEntry>),
|
||||
|
||||
NonExhaustivePattern,
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_parse::parser::{Col, Row};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
@ -29,35 +30,30 @@ pub fn can_problem<'b>(
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => {
|
||||
alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
])
|
||||
])
|
||||
|
||||
}
|
||||
Problem::ExposedButNotDefined(symbol) => {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is listed as exposed, but it isn't defined in this module.")),
|
||||
alloc
|
||||
.reflow("You can fix this by adding a definition for ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(", or by removing it from "))
|
||||
.append(alloc.keyword("exposes"))
|
||||
.append(alloc.reflow("."))
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
]),
|
||||
Problem::ExposedButNotDefined(symbol) => alloc.stack(vec![
|
||||
alloc.symbol_unqualified(symbol).append(
|
||||
alloc.reflow(" is listed as exposed, but it isn't defined in this module."),
|
||||
),
|
||||
alloc
|
||||
.reflow("You can fix this by adding a definition for ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(", or by removing it from "))
|
||||
.append(alloc.keyword("exposes"))
|
||||
.append(alloc.reflow(".")),
|
||||
]),
|
||||
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.";
|
||||
|
||||
@ -79,7 +75,7 @@ pub fn can_problem<'b>(
|
||||
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
|
||||
@ -174,6 +170,7 @@ pub fn can_problem<'b>(
|
||||
read the guide section on phantom data.",
|
||||
)),
|
||||
]),
|
||||
Problem::BadRecursion(entries) => to_circular_def_doc(alloc, &entries),
|
||||
Problem::DuplicateRecordFieldValue {
|
||||
field_name,
|
||||
field_region,
|
||||
@ -191,7 +188,7 @@ pub fn can_problem<'b>(
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.reflow(r"In the rest of the program, I will only use the latter definition:"),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
@ -208,21 +205,15 @@ pub fn can_problem<'b>(
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record uses an optional value for the "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" field in an incorrect context!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
} => {
|
||||
return to_invalid_optional_value_report(
|
||||
alloc,
|
||||
filename,
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("You can only use optional values in record destructuring, for example in affectation:"),
|
||||
alloc.reflow("{ answer ? 42, otherField } = myRecord").indent(4),
|
||||
]),
|
||||
);
|
||||
}
|
||||
Problem::DuplicateRecordFieldType {
|
||||
field_name,
|
||||
field_region,
|
||||
@ -345,6 +336,42 @@ pub fn can_problem<'b>(
|
||||
}
|
||||
}
|
||||
|
||||
fn to_invalid_optional_value_report<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
field_name: Lowercase,
|
||||
field_region: Region,
|
||||
record_region: Region,
|
||||
) -> Report {
|
||||
let doc = to_invalid_optional_value_report_help(alloc, field_name, field_region, record_region);
|
||||
|
||||
Report {
|
||||
title: "BAD OPTIONAL VALUE".to_string(),
|
||||
filename,
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_invalid_optional_value_report_help<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
field_name: Lowercase,
|
||||
field_region: Region,
|
||||
record_region: Region,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record uses an optional value for the "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" field in an incorrect context!"),
|
||||
]),
|
||||
alloc.region_all_the_things(record_region, field_region, field_region, Annotation::Error),
|
||||
alloc.reflow(r"You can only use optional values in record destructuring, like:"),
|
||||
alloc
|
||||
.reflow(r"{ answer ? 42, otherField } = myRecord")
|
||||
.indent(4),
|
||||
])
|
||||
}
|
||||
|
||||
fn to_bad_ident_expr_report<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
bad_ident: roc_parse::ident::BadIdent,
|
||||
@ -672,46 +699,7 @@ fn pretty_runtime_error<'b>(
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(mut symbols, regions) => {
|
||||
let first = symbols.remove(0);
|
||||
|
||||
if symbols.is_empty() {
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO tip?
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first))
|
||||
.append(
|
||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||
),
|
||||
alloc.region(regions[0].0),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.symbol_unqualified(first),
|
||||
symbols
|
||||
.into_iter()
|
||||
.map(|s| alloc.symbol_unqualified(s))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO tip?
|
||||
])
|
||||
}
|
||||
}
|
||||
RuntimeError::CircularDef(entries) => to_circular_def_doc(alloc, &entries),
|
||||
RuntimeError::MalformedPattern(problem, region) => {
|
||||
use roc_parse::ast::Base;
|
||||
use roc_problem::can::MalformedPatternProblem::*;
|
||||
@ -723,7 +711,9 @@ fn pretty_runtime_error<'b>(
|
||||
MalformedBase(Base::Binary) => " binary integer ",
|
||||
MalformedBase(Base::Octal) => " octal integer ",
|
||||
MalformedBase(Base::Decimal) => " integer ",
|
||||
BadIdent(bad_ident) => return to_bad_ident_pattern_report(alloc, bad_ident, region),
|
||||
BadIdent(bad_ident) => {
|
||||
return to_bad_ident_pattern_report(alloc, bad_ident, region)
|
||||
}
|
||||
Unknown => " ",
|
||||
QualifiedIdentifier => " qualified ",
|
||||
};
|
||||
@ -751,19 +741,20 @@ fn pretty_runtime_error<'b>(
|
||||
RuntimeError::UnsupportedPattern(_) => {
|
||||
todo!("unsupported patterns are currently not parsed!")
|
||||
}
|
||||
RuntimeError::ValueNotExposed { module_name, ident, region } => {
|
||||
alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.module_name(module_name),
|
||||
alloc.reflow(" module does not expose a "),
|
||||
alloc.string(ident.to_string()),
|
||||
alloc.reflow(" value:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The "),
|
||||
alloc.module_name(module_name),
|
||||
alloc.reflow(" module does not expose a "),
|
||||
alloc.string(ident.to_string()),
|
||||
alloc.reflow(" value:"),
|
||||
]),
|
||||
alloc.region(region),
|
||||
]),
|
||||
|
||||
RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
@ -777,20 +768,18 @@ fn pretty_runtime_error<'b>(
|
||||
RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => {
|
||||
to_bad_ident_expr_report(alloc, bad_ident, surroundings)
|
||||
}
|
||||
RuntimeError::MalformedTypeName(_box_str, surroundings) => {
|
||||
alloc.stack(vec![
|
||||
alloc.reflow(r"I am confused by this type name:"),
|
||||
alloc.region(surroundings),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Type names start with an uppercase letter, "),
|
||||
alloc.reflow("and can optionally be qualified by a module name, like "),
|
||||
alloc.parser_suggestion("Bool"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.parser_suggestion("Http.Request.Request"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
])
|
||||
}
|
||||
RuntimeError::MalformedTypeName(_box_str, surroundings) => alloc.stack(vec![
|
||||
alloc.reflow(r"I am confused by this type name:"),
|
||||
alloc.region(surroundings),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Type names start with an uppercase letter, "),
|
||||
alloc.reflow("and can optionally be qualified by a module name, like "),
|
||||
alloc.parser_suggestion("Bool"),
|
||||
alloc.reflow(" or "),
|
||||
alloc.parser_suggestion("Http.Request.Request"),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
]),
|
||||
RuntimeError::MalformedClosure(_) => todo!(""),
|
||||
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
|
||||
@ -812,7 +801,8 @@ fn pretty_runtime_error<'b>(
|
||||
]),
|
||||
alloc.region(region),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between "),
|
||||
alloc
|
||||
.reflow("Roc uses signed 64-bit floating points, allowing values between "),
|
||||
alloc.text(format!("{:e}", f64::MIN)),
|
||||
alloc.reflow(" and "),
|
||||
alloc.text(format!("{:e}", f64::MAX)),
|
||||
@ -922,21 +912,7 @@ fn pretty_runtime_error<'b>(
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record uses an optional value for the "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" field in an incorrect context!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("You can only use optional values in record destructuring, for exemple in affectation:"),
|
||||
alloc.reflow("{ answer ? 42, otherField } = myRecord"),
|
||||
]),
|
||||
} => to_invalid_optional_value_report_help(alloc, field_name, field_region, record_region),
|
||||
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This expression cannot be updated"),
|
||||
@ -963,17 +939,59 @@ fn pretty_runtime_error<'b>(
|
||||
region
|
||||
);
|
||||
}
|
||||
RuntimeError::NoImplementation | RuntimeError::NoImplementationNamed { .. } => todo!("no implementation, unreachable"),
|
||||
RuntimeError::NoImplementation | RuntimeError::NoImplementationNamed { .. } => {
|
||||
todo!("no implementation, unreachable")
|
||||
}
|
||||
RuntimeError::NonExhaustivePattern => {
|
||||
unreachable!("not currently reported (but can blow up at runtime)")
|
||||
}
|
||||
RuntimeError::ExposedButNotDefined(symbol) => alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" was listed as exposed in "))
|
||||
.append(alloc.module(symbol.module_id()))
|
||||
.append(alloc.reflow(", but it was not defined anywhere in that module.")),
|
||||
]),
|
||||
RuntimeError::ExposedButNotDefined(symbol) => alloc.stack(vec![alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" was listed as exposed in "))
|
||||
.append(alloc.module(symbol.module_id()))
|
||||
.append(alloc.reflow(", but it was not defined anywhere in that module."))]),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_circular_def_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
entries: &[roc_problem::can::CycleEntry],
|
||||
) -> RocDocBuilder<'b> {
|
||||
// TODO "are you trying to mutate a variable?
|
||||
// TODO tip?
|
||||
match entries {
|
||||
[] => unreachable!(),
|
||||
[first] => alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first.symbol))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
)),
|
||||
[first, others @ ..] => {
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first.symbol))
|
||||
.append(alloc.reflow(" definition is causing a very tricky infinite loop:")),
|
||||
alloc.region(first.symbol_region),
|
||||
alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.symbol_unqualified(first.symbol))
|
||||
.append(alloc.reflow(
|
||||
" value depends on itself through the following chain of definitions:",
|
||||
)),
|
||||
crate::report::cycle(
|
||||
alloc,
|
||||
4,
|
||||
alloc.symbol_unqualified(first.symbol),
|
||||
others
|
||||
.iter()
|
||||
.map(|s| alloc.symbol_unqualified(s.symbol))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
// TODO tip?
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3938,7 +3938,7 @@ mod test_reporting {
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||||
── BAD OPTIONAL VALUE ──────────────────────────────────────────────────────────
|
||||
|
||||
This record uses an optional value for the `.y` field in an incorrect
|
||||
context!
|
||||
@ -3946,8 +3946,7 @@ mod test_reporting {
|
||||
1│ { x: 5, y ? 42 }
|
||||
^^^^^^
|
||||
|
||||
You can only use optional values in record destructuring, for example
|
||||
in affectation:
|
||||
You can only use optional values in record destructuring, like:
|
||||
|
||||
{ answer ? 42, otherField } = myRecord
|
||||
"#
|
||||
|
@ -17,9 +17,10 @@ roc_problem = { path = "../compiler/problem" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_reporting = { path = "../compiler/reporting" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
im = "15" # im and im-rc should always have the same version!
|
||||
im = "15" # im and im-rc should always have the same version!
|
||||
im-rc = "15" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1"
|
||||
@ -69,4 +70,3 @@ harness = false
|
||||
[[bench]]
|
||||
name = "edit_benchmark"
|
||||
harness = false
|
||||
|
||||
|
32
editor/src/lang/constrain.rs
Normal file
32
editor/src/lang/constrain.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use crate::lang::pool::{Pool, PoolVec};
|
||||
use crate::lang::{ast::Expr2, expr::Env, types::Type2};
|
||||
|
||||
use roc_can::expected::Expected;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::types::Category;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Constraint {
|
||||
Eq(Type2, Expected<Type2>, Category, Region),
|
||||
// Store(Type, Variable, &'static str, u32),
|
||||
// Lookup(Symbol, Expected<Type>, Region),
|
||||
// Pattern(Region, PatternCategory, Type, PExpected<Type>),
|
||||
True, // Used for things that always unify, e.g. blanks and runtime errors
|
||||
// SaveTheEnvironment,
|
||||
// Let(Box<LetConstraint>),
|
||||
// And(Vec<Constraint>),
|
||||
}
|
||||
|
||||
pub fn constrain_expr(env: &mut Env, expr: &Expr2, expected: Expected<Type2>) -> Constraint {
|
||||
use Constraint::*;
|
||||
|
||||
match expr {
|
||||
Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, Region::zero()),
|
||||
_ => todo!("implement constaints for {:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
fn str_type(pool: &mut Pool) -> Type2 {
|
||||
Type2::Apply(Symbol::STR_STR, PoolVec::empty(pool))
|
||||
}
|
@ -1159,7 +1159,7 @@ pub fn sort_can_defs(
|
||||
}
|
||||
Err((mut groups, nodes_in_cycle)) => {
|
||||
let mut declarations = Vec::new();
|
||||
let mut problems = Vec::new();
|
||||
let problems = Vec::new();
|
||||
|
||||
// nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem,
|
||||
// and in general it's impossible to decide whether it is. So we use a crude heuristic:
|
||||
@ -1211,7 +1211,7 @@ pub fn sort_can_defs(
|
||||
}
|
||||
|
||||
// TODO we don't store those regions any more!
|
||||
let regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||
// let regions = Vec::with_capacity(can_defs_by_symbol.len());
|
||||
// for def in can_defs_by_symbol.values() {
|
||||
// regions.push((def.loc_pattern.region, def.loc_expr.region));
|
||||
// }
|
||||
@ -1220,15 +1220,16 @@ pub fn sort_can_defs(
|
||||
// loc_symbols.sort();
|
||||
// regions.sort();
|
||||
|
||||
let symbols_in_cycle: Vec<Symbol> =
|
||||
loc_symbols.into_iter().map(|s| s.value).collect();
|
||||
|
||||
problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
symbols_in_cycle.clone(),
|
||||
regions.clone(),
|
||||
)));
|
||||
|
||||
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
||||
// let symbols_in_cycle: Vec<Symbol> =
|
||||
// loc_symbols.into_iter().map(|s| s.value).collect();
|
||||
//
|
||||
// problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
// symbols_in_cycle.clone(),
|
||||
// regions.clone(),
|
||||
// )));
|
||||
//
|
||||
// declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
||||
panic!("Invalid Cycle");
|
||||
} else {
|
||||
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
||||
groups.push(cycle);
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod ast;
|
||||
pub mod constrain;
|
||||
mod def;
|
||||
pub mod expr;
|
||||
mod module;
|
||||
@ -6,4 +7,4 @@ mod pattern;
|
||||
pub mod pool;
|
||||
pub mod roc_file;
|
||||
pub mod scope;
|
||||
mod types;
|
||||
pub mod types;
|
||||
|
@ -254,9 +254,9 @@ impl PoolStr {
|
||||
|
||||
pub fn as_str(&self, pool: &Pool) -> &str {
|
||||
unsafe {
|
||||
let node_ptr = pool.nodes.offset(self.first_node_id.index as isize);
|
||||
let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8;
|
||||
|
||||
let node_slice: &[u8] = &*node_ptr;
|
||||
let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize);
|
||||
|
||||
std::str::from_utf8_unchecked(&node_slice[0..self.len as usize])
|
||||
}
|
||||
|
117
editor/tests/solve_expr2.rs
Normal file
117
editor/tests/solve_expr2.rs
Normal file
@ -0,0 +1,117 @@
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_editor::lang::{
|
||||
constrain::constrain_expr,
|
||||
expr::{str_to_expr2, Env},
|
||||
pool::Pool,
|
||||
scope::Scope,
|
||||
types::Type2,
|
||||
};
|
||||
use roc_module::symbol::{IdentIds, ModuleIds};
|
||||
use roc_region::all::Region;
|
||||
use roc_solve::module::run_solve;
|
||||
use roc_types::{pretty_print::content_to_string, subs::VarStore, types::Type};
|
||||
|
||||
fn ed_constraint_to_can_constraint(
|
||||
constraint: roc_editor::lang::constrain::Constraint,
|
||||
) -> roc_can::constraint::Constraint {
|
||||
match constraint {
|
||||
roc_editor::lang::constrain::Constraint::Eq(typ, expected, category, region) => {
|
||||
let new_typ = type2_to_type(&typ);
|
||||
let expected_typ = expected.get_type_ref();
|
||||
|
||||
let expected_typ = type2_to_type(expected_typ);
|
||||
|
||||
roc_can::constraint::Constraint::Eq(
|
||||
new_typ,
|
||||
expected.replace(expected_typ),
|
||||
category,
|
||||
region,
|
||||
)
|
||||
}
|
||||
_ => todo!("{:?}", constraint),
|
||||
}
|
||||
}
|
||||
|
||||
fn type2_to_type(typ: &Type2) -> Type {
|
||||
match typ {
|
||||
Type2::Apply(symbol, _) => Type::Apply(*symbol, Vec::new()),
|
||||
Type2::Variable(var) => Type::Variable(*var),
|
||||
_ => todo!("{:?}", typ),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_eq(actual: &str, expected_str: &str) {
|
||||
let mut env_pool = Pool::with_capacity(1024);
|
||||
let env_arena = Bump::new();
|
||||
let code_arena = Bump::new();
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
let dep_idents = IdentIds::exposed_builtins(8);
|
||||
|
||||
let exposed_ident_ids = IdentIds::default();
|
||||
let mut module_ids = ModuleIds::default();
|
||||
let mod_id = module_ids.get_or_insert(&"ModId123".into());
|
||||
|
||||
let mut env = Env::new(
|
||||
mod_id,
|
||||
&env_arena,
|
||||
&mut env_pool,
|
||||
&mut var_store,
|
||||
dep_idents,
|
||||
&module_ids,
|
||||
exposed_ident_ids,
|
||||
);
|
||||
|
||||
let mut scope = Scope::new(env.home, env.pool, env.var_store);
|
||||
|
||||
let region = Region::zero();
|
||||
|
||||
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
|
||||
|
||||
match expr2_result {
|
||||
Ok((expr, _)) => {
|
||||
let constraint = constrain_expr(
|
||||
&mut env,
|
||||
&expr,
|
||||
Expected::NoExpectation(Type2::Variable(var)),
|
||||
);
|
||||
|
||||
let constraint = ed_constraint_to_can_constraint(constraint);
|
||||
|
||||
let (mut solved, _, _) = run_solve(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
constraint,
|
||||
var_store,
|
||||
);
|
||||
|
||||
let mut subs = solved.inner_mut();
|
||||
|
||||
let content = subs.get(var).content;
|
||||
|
||||
let actual_str = content_to_string(content, &mut subs, mod_id, &Default::default());
|
||||
|
||||
assert_eq!(actual_str, expected_str);
|
||||
}
|
||||
Err(e) => panic!("syntax error {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrain_str() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
"type inference!"
|
||||
"#
|
||||
),
|
||||
"Str",
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user