Merge branch 'trunk' of github.com:rtfeldman/roc into complex-type-signatures

This commit is contained in:
Chadtech 2021-07-11 14:58:55 -04:00
commit a68eb32b6f
16 changed files with 450 additions and 228 deletions

View file

@ -1,4 +1,7 @@
on: [pull_request]
on:
pull_request:
paths-ignore:
- '**.md'
name: Benchmarks

View file

@ -1,4 +1,7 @@
on: [pull_request]
on:
pull_request:
paths-ignore:
- '**.md'
name: CI

24
.github/workflows/spellcheck.yml vendored Normal file
View file

@ -0,0 +1,24 @@
on: [pull_request]
name: SpellCheck
env:
RUST_BACKTRACE: 1
jobs:
spell-check:
name: spell check
runs-on: [self-hosted]
timeout-minutes: 10
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: Earthly version
run: earthly --version
- name: install spell checker, do spell check
run: ./ci/safe-earthly.sh +check-typos

View file

@ -101,7 +101,7 @@ check-rustfmt:
RUN cargo fmt --all -- --check
check-typos:
RUN cargo install typos-cli --version 1.0.4 # use latest version on resolution of issue crate-ci/typos#277
RUN cargo install typos-cli
COPY --dir .github ci cli compiler docs editor examples nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos

View file

@ -196,7 +196,11 @@ impl<'a> ParamMap<'a> {
let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
debug_assert!(
already_in_there.is_none(),
"join point {:?} is already defined!",
j
);
stack.push(v);
stack.push(b);

View file

@ -22,7 +22,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree
.into_iter()
.map(|(guard, pattern, index)| Branch {
goal: index,
patterns: vec![(Vec::new(), guard, pattern)],
guard,
patterns: vec![(Vec::new(), pattern)],
})
.collect();
@ -33,9 +34,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree
pub enum Guard<'a> {
NoGuard,
Guard {
/// Symbol that stores a boolean
/// when true this branch is picked, otherwise skipped
symbol: Symbol,
/// pattern
pattern: Pattern<'a>,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
@ -60,22 +60,19 @@ enum DecisionTree<'a> {
#[derive(Clone, Debug, PartialEq)]
enum GuardedTest<'a> {
TestGuarded {
test: Test<'a>,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
},
// e.g. `_ if True -> ...`
GuardedNoTest {
/// pattern
pattern: Pattern<'a>,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
/// body
stmt: Stmt<'a>,
},
TestNotGuarded {
test: Test<'a>,
},
Placeholder,
}
#[derive(Clone, Debug, PartialEq)]
@ -136,16 +133,16 @@ impl<'a> Hash for Test<'a> {
impl<'a> Hash for GuardedTest<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
GuardedTest::TestGuarded { test, .. } => {
GuardedTest::GuardedNoTest { id, .. } => {
state.write_u8(1);
id.hash(state);
}
GuardedTest::TestNotGuarded { test } => {
state.write_u8(0);
test.hash(state);
}
GuardedTest::GuardedNoTest { .. } => {
state.write_u8(1);
}
GuardedTest::TestNotGuarded { test } => {
GuardedTest::Placeholder => {
state.write_u8(2);
test.hash(state);
}
}
}
@ -156,22 +153,70 @@ impl<'a> Hash for GuardedTest<'a> {
#[derive(Clone, Debug, PartialEq)]
struct Branch<'a> {
goal: Label,
patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
guard: Guard<'a>,
patterns: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
}
fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect();
debug_assert!(!branches.is_empty());
match check_for_match(&branches) {
Some(goal) => DecisionTree::Match(goal),
None => {
Match::Exact(goal) => DecisionTree::Match(goal),
Match::GuardOnly => {
// the first branch has no more tests to do, but it has an if-guard
let mut branches = branches;
let first = branches.remove(0);
match first.guard {
Guard::NoGuard => unreachable!(),
Guard::Guard { id, stmt, pattern } => {
let guarded_test = GuardedTest::GuardedNoTest { id, stmt, pattern };
// the guard test does not have a path
let path = vec![];
// we expect none of the patterns need tests, those decisions should have been made already
debug_assert!(first
.patterns
.iter()
.all(|(_, pattern)| !needs_tests(pattern)));
let default = if branches.is_empty() {
None
} else {
Some(Box::new(to_decision_tree(branches)))
};
DecisionTree::Decision {
path,
edges: vec![(guarded_test, DecisionTree::Match(first.goal))],
default,
}
}
}
}
Match::None => {
// must clone here to release the borrow on `branches`
let path = pick_path(&branches).clone();
let bs = branches.clone();
let (edges, fallback) = gather_edges(branches, &path);
let mut decision_edges: Vec<_> = edges
.into_iter()
.map(|(a, b)| (a, to_decision_tree(b)))
.map(|(test, branches)| {
if bs == branches {
panic!();
} else {
(test, to_decision_tree(branches))
}
})
.collect();
match (decision_edges.as_slice(), fallback.as_slice()) {
@ -181,21 +226,55 @@ fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
// get the `_decision_tree` without cloning
decision_edges.pop().unwrap().1
}
(_, []) => DecisionTree::Decision {
path,
edges: decision_edges,
default: None,
},
(_, []) => break_out_guard(path, decision_edges, None),
([], _) => {
// should be guaranteed by the patterns
debug_assert!(!fallback.is_empty());
to_decision_tree(fallback)
}
(_, _) => DecisionTree::Decision {
(_, _) => break_out_guard(
path,
edges: decision_edges,
default: Some(Box::new(to_decision_tree(fallback))),
decision_edges,
Some(Box::new(to_decision_tree(fallback))),
),
}
}
}
}
/// Give a guard it's own Decision
fn break_out_guard<'a>(
path: Vec<PathInstruction>,
mut edges: Vec<(GuardedTest<'a>, DecisionTree<'a>)>,
default: Option<Box<DecisionTree<'a>>>,
) -> DecisionTree<'a> {
match edges
.iter()
.position(|(t, _)| matches!(t, GuardedTest::Placeholder))
{
None => DecisionTree::Decision {
path,
edges,
default,
},
Some(index) => {
let (a, b) = edges.split_at_mut(index + 1);
let new_default = break_out_guard(path.clone(), b.to_vec(), default);
let mut left = a.to_vec();
let guard = left.pop().unwrap();
let help = DecisionTree::Decision {
path: path.clone(),
edges: vec![guard],
default: Some(Box::new(new_default)),
};
DecisionTree::Decision {
path,
edges: left,
default: Some(Box::new(help)),
}
}
}
@ -205,10 +284,14 @@ fn guarded_tests_are_complete(tests: &[GuardedTest]) -> bool {
let length = tests.len();
debug_assert!(length > 0);
let no_guard = tests
.iter()
.all(|t| matches!(t, GuardedTest::TestNotGuarded { .. }));
match tests.last().unwrap() {
GuardedTest::TestGuarded { .. } => false,
GuardedTest::Placeholder => false,
GuardedTest::GuardedNoTest { .. } => false,
GuardedTest::TestNotGuarded { test } => tests_are_complete_help(test, length),
GuardedTest::TestNotGuarded { test } => no_guard && tests_are_complete_help(test, length),
}
}
@ -231,16 +314,16 @@ fn flatten_patterns(branch: Branch) -> Branch {
}
Branch {
goal: branch.goal,
patterns: result,
..branch
}
}
fn flatten<'a>(
path_pattern: (Vec<PathInstruction>, Guard<'a>, Pattern<'a>),
path_patterns: &mut Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
path_pattern: (Vec<PathInstruction>, Pattern<'a>),
path_patterns: &mut Vec<(Vec<PathInstruction>, Pattern<'a>)>,
) {
match path_pattern.2 {
match path_pattern.1 {
Pattern::AppliedTag {
union,
arguments,
@ -257,7 +340,6 @@ fn flatten<'a>(
// NOTE here elm will unbox, but we don't use that
path_patterns.push((
path,
path_pattern.1.clone(),
Pattern::AppliedTag {
union,
arguments,
@ -274,15 +356,7 @@ fn flatten<'a>(
tag_id,
});
flatten(
(
new_path,
// same guard here?
path_pattern.1.clone(),
arg_pattern.clone(),
),
path_patterns,
);
flatten((new_path, arg_pattern.clone()), path_patterns);
}
}
}
@ -299,21 +373,33 @@ fn flatten<'a>(
/// path. If that is the case we give the resulting label and a mapping from free
/// variables to "how to get their value". So a pattern like (Just (x,_)) will give
/// us something like ("x" => value.0.0)
fn check_for_match(branches: &[Branch]) -> Option<Label> {
match branches.get(0) {
Some(Branch { goal, patterns })
if patterns
.iter()
.all(|(_, guard, pattern)| guard.is_none() && !needs_tests(pattern)) =>
{
Some(*goal)
enum Match {
Exact(Label),
GuardOnly,
None,
}
_ => None,
fn check_for_match(branches: &[Branch]) -> Match {
match branches.get(0) {
Some(Branch {
goal,
patterns,
guard,
}) if patterns.iter().all(|(_, pattern)| !needs_tests(pattern)) => {
if guard.is_none() {
Match::Exact(*goal)
} else {
Match::GuardOnly
}
}
_ => Match::None,
}
}
/// GATHER OUTGOING EDGES
// my understanding: branches that we could jump to based on the pattern at the current path
fn gather_edges<'a>(
branches: Vec<Branch<'a>>,
path: &[PathInstruction],
@ -351,7 +437,7 @@ fn tests_at_path<'a>(
let mut all_tests = Vec::new();
for branch in branches {
test_at_path(selected_path, branch, &mut all_tests);
all_tests.extend(test_at_path(selected_path, branch));
}
// The rust HashMap also uses equality, here we really want to use the custom hash function
@ -382,28 +468,26 @@ fn tests_at_path<'a>(
fn test_at_path<'a>(
selected_path: &[PathInstruction],
branch: &Branch<'a>,
guarded_tests: &mut Vec<GuardedTest<'a>>,
) {
) -> Option<GuardedTest<'a>> {
use Pattern::*;
use Test::*;
match branch
.patterns
.iter()
.find(|(path, _, _)| path == selected_path)
.find(|(path, _)| path == selected_path)
{
None => {}
Some((_, guard, pattern)) => {
None => None,
Some((_, pattern)) => {
let test = match pattern {
Identifier(_) | Underscore => {
if let Guard::Guard { id, stmt, .. } = guard {
guarded_tests.push(GuardedTest::GuardedNoTest {
stmt: stmt.clone(),
id: *id,
});
if let Guard::Guard { .. } = &branch.guard {
// no tests for this pattern remain, but we cannot discard it yet
// because it has a guard!
return Some(GuardedTest::Placeholder);
} else {
return None;
}
return;
}
RecordDestructure(destructs, _) => {
@ -475,23 +559,16 @@ fn test_at_path<'a>(
StrLiteral(v) => IsStr(v.clone()),
};
let guarded_test = if let Guard::Guard { id, stmt, .. } = guard {
GuardedTest::TestGuarded {
test,
stmt: stmt.clone(),
id: *id,
}
} else {
GuardedTest::TestNotGuarded { test }
};
let guarded_test = GuardedTest::TestNotGuarded { test };
guarded_tests.push(guarded_test);
Some(guarded_test)
}
}
}
/// BUILD EDGES
// understanding: if the test is successful, where could we go?
fn edges_for<'a>(
path: &[PathInstruction],
branches: Vec<Branch<'a>>,
@ -499,8 +576,22 @@ fn edges_for<'a>(
) -> (GuardedTest<'a>, Vec<Branch<'a>>) {
let mut new_branches = Vec::new();
for branch in branches.iter() {
to_relevant_branch(&test, path, branch, &mut new_branches);
// if we test for a guard, skip all branches until one that has a guard
let it = match test {
GuardedTest::GuardedNoTest { .. } | GuardedTest::Placeholder => {
let index = branches
.iter()
.position(|b| !b.guard.is_none())
.expect("if testing for a guard, one branch must have a guard");
branches[index..].iter()
}
GuardedTest::TestNotGuarded { .. } => branches.iter(),
};
for branch in it {
new_branches.extend(to_relevant_branch(&test, path, branch));
}
(test, new_branches)
@ -510,59 +601,38 @@ fn to_relevant_branch<'a>(
guarded_test: &GuardedTest<'a>,
path: &[PathInstruction],
branch: &Branch<'a>,
new_branches: &mut Vec<Branch<'a>>,
) {
) -> Option<Branch<'a>> {
// TODO remove clone
match extract(path, branch.patterns.clone()) {
Extract::NotFound => {
new_branches.push(branch.clone());
}
Extract::NotFound => Some(branch.clone()),
Extract::Found {
start,
found_pattern: (guard, pattern),
found_pattern: pattern,
end,
} => {
let actual_test = match guarded_test {
GuardedTest::TestGuarded { test, .. } => test,
GuardedTest::GuardedNoTest { .. } => {
let mut new_branch = branch.clone();
} => match guarded_test {
GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
// if there is no test, the pattern should not require any
debug_assert!(
matches!(pattern, Pattern::Identifier(_) | Pattern::Underscore,),
"{:?}",
pattern,
);
// guards can/should only occur at the top level. When we recurse on these
// branches, the guard is not relevant any more. Not setthing the guard to None
// leads to infinite recursion.
new_branch.patterns.iter_mut().for_each(|(_, guard, _)| {
*guard = Guard::NoGuard;
});
new_branches.push(new_branch);
return;
}
GuardedTest::TestNotGuarded { test } => test,
};
if let Some(mut new_branch) =
to_relevant_branch_help(actual_test, path, start, end, branch, guard, pattern)
{
// guards can/should only occur at the top level. When we recurse on these
// branches, the guard is not relevant any more. Not setthing the guard to None
// leads to infinite recursion.
new_branch.patterns.iter_mut().for_each(|(_, guard, _)| {
*guard = Guard::NoGuard;
});
new_branches.push(new_branch);
Some(branch.clone())
}
GuardedTest::TestNotGuarded { test } => {
to_relevant_branch_help(test, path, start, end, branch, pattern)
}
},
}
}
fn to_relevant_branch_help<'a>(
test: &Test<'a>,
path: &[PathInstruction],
mut start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
mut start: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
end: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
branch: &Branch<'a>,
guard: Guard<'a>,
pattern: Pattern<'a>,
) -> Option<Branch<'a>> {
use Pattern::*;
@ -590,13 +660,14 @@ fn to_relevant_branch_help<'a>(
tag_id: *tag_id,
});
(new_path, Guard::NoGuard, pattern)
(new_path, pattern)
});
start.extend(sub_positions);
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -626,13 +697,14 @@ fn to_relevant_branch_help<'a>(
index: index as u64,
tag_id,
});
(new_path, Guard::NoGuard, pattern)
(new_path, pattern)
});
start.extend(sub_positions);
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -665,7 +737,7 @@ fn to_relevant_branch_help<'a>(
{
// NOTE here elm unboxes, but we ignore that
// Path::Unbox(Box::new(path.clone()))
start.push((path.to_vec(), guard, arg.0));
start.push((path.to_vec(), arg.0));
start.extend(end);
}
}
@ -680,7 +752,7 @@ fn to_relevant_branch_help<'a>(
index: index as u64,
tag_id,
});
(new_path, Guard::NoGuard, pattern)
(new_path, pattern)
});
start.extend(sub_positions);
start.extend(end);
@ -699,7 +771,7 @@ fn to_relevant_branch_help<'a>(
index: index as u64,
tag_id,
});
(new_path, Guard::NoGuard, pattern)
(new_path, pattern)
});
start.extend(sub_positions);
start.extend(end);
@ -708,6 +780,7 @@ fn to_relevant_branch_help<'a>(
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -719,6 +792,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -730,6 +804,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -741,6 +816,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -752,6 +828,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -765,6 +842,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
@ -777,15 +855,15 @@ fn to_relevant_branch_help<'a>(
enum Extract<'a> {
NotFound,
Found {
start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
found_pattern: (Guard<'a>, Pattern<'a>),
end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
start: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
found_pattern: Pattern<'a>,
end: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
},
}
fn extract<'a>(
selected_path: &[PathInstruction],
path_patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>,
path_patterns: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
) -> Extract<'a> {
let mut start = Vec::new();
@ -795,7 +873,7 @@ fn extract<'a>(
if current.0 == selected_path {
return Extract::Found {
start,
found_pattern: (current.1, current.2),
found_pattern: current.1,
end: it.collect::<Vec<_>>(),
};
} else {
@ -812,10 +890,10 @@ fn is_irrelevant_to<'a>(selected_path: &[PathInstruction], branch: &Branch<'a>)
match branch
.patterns
.iter()
.find(|(path, _, _)| path == selected_path)
.find(|(path, _)| path == selected_path)
{
None => true,
Some((_, guard, pattern)) => guard.is_none() && !needs_tests(pattern),
Some((_, pattern)) => branch.guard.is_none() && !needs_tests(pattern),
}
}
@ -843,8 +921,10 @@ fn pick_path<'a>(branches: &'a [Branch]) -> &'a Vec<PathInstruction> {
// is choice path
for branch in branches {
for (path, guard, pattern) in &branch.patterns {
if !guard.is_none() || needs_tests(&pattern) {
for (path, pattern) in &branch.patterns {
// NOTE we no longer check for the guard here
// if !branch.guard.is_none() || needs_tests(&pattern) {
if needs_tests(&pattern) {
all_paths.push(path);
} else {
// do nothing
@ -960,6 +1040,7 @@ enum Decider<'a, T> {
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
pattern: Pattern<'a>,
success: Box<Decider<'a, T>>,
failure: Box<Decider<'a, T>>,
@ -997,11 +1078,15 @@ pub fn optimize_when<'a>(
.into_iter()
.enumerate()
.map(|(index, (pattern, guard, branch))| {
((guard, pattern, index as u64), (index as u64, branch))
let has_guard = !guard.is_none();
(
(guard, pattern.clone(), index as u64),
(index as u64, branch, pattern, has_guard),
)
})
.unzip();
let indexed_branches: Vec<(u64, Stmt<'a>)> = _indexed_branches;
let indexed_branches: Vec<_> = _indexed_branches;
let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree);
@ -1013,7 +1098,14 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default();
let mut jumps = Vec::new();
for (index, branch) in indexed_branches.into_iter() {
for (index, mut branch, pattern, has_guard) in indexed_branches.into_iter() {
// bind the fields referenced in the pattern. For guards this happens separately, so
// the pattern variables are defined when evaluating the guard.
if !has_guard {
branch =
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
}
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
if let Some((index, body)) = opt_jump {
@ -1495,6 +1587,7 @@ fn decide_to_branching<'a>(
Guarded {
id,
stmt,
pattern,
success,
failure,
} => {
@ -1540,12 +1633,14 @@ fn decide_to_branching<'a>(
borrow: false,
};
Stmt::Join {
let join = Stmt::Join {
id,
parameters: arena.alloc([param]),
remainder: arena.alloc(stmt),
body: arena.alloc(decide),
}
};
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
}
Chain {
test_chain,
@ -1835,7 +1930,7 @@ fn fanout_decider<'a>(
let fallback_decider = tree_to_decider(fallback);
let necessary_tests = edges
.into_iter()
.map(|(test, tree)| fanout_decider_help(tree, test, &fallback_decider))
.map(|(test, tree)| fanout_decider_help(tree, test))
.collect();
Decider::FanOut {
@ -1848,25 +1943,15 @@ fn fanout_decider<'a>(
fn fanout_decider_help<'a>(
dectree: DecisionTree<'a>,
guarded_test: GuardedTest<'a>,
fallback_decider: &Decider<'a, u64>,
) -> (Test<'a>, Decider<'a, u64>) {
let decider = tree_to_decider(dectree);
match guarded_test {
GuardedTest::TestGuarded { test, id, stmt } => {
let guarded = Decider::Guarded {
id,
stmt,
success: Box::new(decider),
failure: Box::new(fallback_decider.clone()),
};
(test, guarded)
}
GuardedTest::GuardedNoTest { .. } => {
GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
unreachable!("this would not end up in a switch")
}
GuardedTest::TestNotGuarded { test } => (test, decider),
GuardedTest::TestNotGuarded { test } => {
let decider = tree_to_decider(dectree);
(test, decider)
}
}
}
@ -1877,30 +1962,14 @@ fn chain_decider<'a>(
success_tree: DecisionTree<'a>,
) -> Decider<'a, u64> {
match guarded_test {
GuardedTest::TestGuarded { test, id, stmt } => {
let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree));
let guarded = Decider::Guarded {
id,
stmt,
success,
failure: failure.clone(),
};
Decider::Chain {
test_chain: vec![(path, test)],
success: Box::new(guarded),
failure,
}
}
GuardedTest::GuardedNoTest { id, stmt } => {
GuardedTest::GuardedNoTest { id, stmt, pattern } => {
let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree));
Decider::Guarded {
id,
stmt,
pattern,
success,
failure: failure.clone(),
}
@ -1912,6 +1981,11 @@ fn chain_decider<'a>(
to_chain(path, test, success_tree, failure_tree)
}
}
GuardedTest::Placeholder => {
// ?
tree_to_decider(success_tree)
}
}
}
@ -2022,11 +2096,13 @@ fn insert_choices<'a>(
Guarded {
id,
stmt,
pattern,
success,
failure,
} => Guarded {
id,
stmt,
pattern,
success: Box::new(insert_choices(choice_dict, *success)),
failure: Box::new(insert_choices(choice_dict, *failure)),
},

View file

@ -5096,21 +5096,17 @@ fn from_can_when<'a>(
jump,
);
let new_guard_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt);
(
pattern,
pattern.clone(),
Guard::Guard {
id,
symbol,
stmt: new_guard_stmt,
pattern,
stmt: guard_stmt,
},
branch_stmt,
)
} else {
let new_branch_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt);
(pattern, Guard::NoGuard, new_branch_stmt)
(pattern, Guard::NoGuard, branch_stmt)
}
});
let mono_branches = Vec::from_iter_in(it, arena);
@ -5510,7 +5506,7 @@ fn substitute_in_expr<'a>(
}
#[allow(clippy::too_many_arguments)]
fn store_pattern<'a>(
pub fn store_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,

View file

@ -469,6 +469,118 @@ fn nested_pattern_match() {
);
}
#[test]
fn if_guard_vanilla() {
assert_evals_to!(
indoc!(
r#"
when "fooz" is
s if s == "foo" -> 0
s -> List.len (Str.toBytes s)
"#
),
4,
i64
);
}
#[test]
#[ignore]
fn when_on_single_value_tag() {
// this fails because the switched-on symbol is not defined
assert_evals_to!(
indoc!(
r#"
when Identity 0 is
Identity 0 -> 0
Identity s -> s
"#
),
6,
i64
);
}
#[test]
#[ignore]
fn if_guard_multiple() {
assert_evals_to!(
indoc!(
r#"
f = \n ->
when Identity n 0 is
Identity x _ if x == 0 -> x + 0
Identity x _ if x == 1 -> x + 0
Identity x _ if x == 2 -> x + 0
Identity x _ -> x - x
{ a: f 0, b: f 1, c: f 2, d: f 4 }
"#
),
(0, 1, 2, 0),
(i64, i64, i64, i64)
);
}
#[test]
fn if_guard_constructor_switch() {
assert_evals_to!(
indoc!(
r#"
when Identity 32 0 is
Identity 41 _ -> 0
Identity s 0 if s == 32 -> 3
# Identity s 0 -> s
Identity z _ -> z
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
when Identity 42 "" is
Identity 41 _ -> 0
Identity 42 _ if 3 == 3 -> 1
Identity z _ -> z
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when Identity 42 "" is
Identity 41 _ -> 0
Identity 42 _ if 3 != 3 -> 1
Identity z _ -> z
"#
),
42,
i64
);
}
#[test]
fn if_guard_constructor_chain() {
assert_evals_to!(
indoc!(
r#"
when Identity 43 0 is
Identity 42 _ if 3 == 3 -> 43
# Identity 42 _ -> 1
Identity z _ -> z
"#
),
43,
i64
);
}
#[test]
fn if_guard_pattern_false() {
assert_evals_to!(

View file

@ -7,8 +7,8 @@ procedure Test.0 ():
let Test.20 = Just Test.21;
let Test.2 = Just Test.20;
joinpoint Test.17:
let Test.11 = 1i64;
ret Test.11;
let Test.10 = 1i64;
ret Test.10;
in
let Test.15 = 0i64;
let Test.16 = GetTagId Test.2;
@ -19,8 +19,8 @@ procedure Test.0 ():
let Test.14 = GetTagId Test.12;
let Test.18 = lowlevel Eq Test.13 Test.14;
if Test.18 then
let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2;
let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10;
let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2;
let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.11;
let Test.7 = 1i64;
let Test.6 = CallByName Num.24 Test.5 Test.7;
ret Test.6;

View file

@ -22,9 +22,9 @@ procedure Test.0 ():
let Test.7 = 1i64;
ret Test.7;
else
let Test.9 = 0i64;
ret Test.9;
let Test.8 = 0i64;
ret Test.8;
else
dec Test.2;
let Test.10 = 0i64;
ret Test.10;
let Test.9 = 0i64;
ret Test.9;

View file

@ -26,8 +26,8 @@ procedure Test.1 (Test.2):
let Test.29 = CallByName List.3 Test.2 Test.31;
let Test.8 = Struct {Test.29, Test.30};
joinpoint Test.26:
let Test.19 = Array [];
ret Test.19;
let Test.17 = Array [];
ret Test.17;
in
let Test.23 = StructAtIndex 1 Test.8;
let Test.24 = 1i64;
@ -39,10 +39,10 @@ procedure Test.1 (Test.2):
let Test.22 = GetTagId Test.20;
let Test.27 = lowlevel Eq Test.21 Test.22;
if Test.27 then
let Test.18 = StructAtIndex 0 Test.8;
let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.18;
let Test.17 = StructAtIndex 1 Test.8;
let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.17;
let Test.19 = StructAtIndex 0 Test.8;
let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.19;
let Test.18 = StructAtIndex 1 Test.8;
let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.18;
let Test.16 = 0i64;
let Test.10 = CallByName List.4 Test.2 Test.16 Test.5;
let Test.11 = 0i64;

View file

@ -24,8 +24,8 @@ procedure Test.1 (Test.2, Test.3, Test.4):
let Test.32 = CallByName List.3 Test.4 Test.2;
let Test.13 = Struct {Test.32, Test.33};
joinpoint Test.29:
let Test.22 = Array [];
ret Test.22;
let Test.20 = Array [];
ret Test.20;
in
let Test.26 = StructAtIndex 1 Test.13;
let Test.27 = 1i64;
@ -37,10 +37,10 @@ procedure Test.1 (Test.2, Test.3, Test.4):
let Test.25 = GetTagId Test.23;
let Test.30 = lowlevel Eq Test.24 Test.25;
if Test.30 then
let Test.21 = StructAtIndex 0 Test.13;
let Test.6 = UnionAtIndex (Id 1) (Index 0) Test.21;
let Test.20 = StructAtIndex 1 Test.13;
let Test.7 = UnionAtIndex (Id 1) (Index 0) Test.20;
let Test.22 = StructAtIndex 0 Test.13;
let Test.6 = UnionAtIndex (Id 1) (Index 0) Test.22;
let Test.21 = StructAtIndex 1 Test.13;
let Test.7 = UnionAtIndex (Id 1) (Index 0) Test.21;
let Test.15 = CallByName List.4 Test.4 Test.2 Test.7;
let Test.14 = CallByName List.4 Test.15 Test.3 Test.6;
ret Test.14;

View file

@ -7,8 +7,8 @@ procedure Test.0 ():
let Test.20 = Just Test.21;
let Test.2 = Just Test.20;
joinpoint Test.17:
let Test.11 = 1i64;
ret Test.11;
let Test.10 = 1i64;
ret Test.10;
in
let Test.15 = 0i64;
let Test.16 = GetTagId Test.2;
@ -19,8 +19,8 @@ procedure Test.0 ():
let Test.14 = GetTagId Test.12;
let Test.18 = lowlevel Eq Test.13 Test.14;
if Test.18 then
let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2;
let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10;
let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2;
let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.11;
let Test.7 = 1i64;
let Test.6 = CallByName Num.24 Test.5 Test.7;
ret Test.6;

View file

@ -141,7 +141,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time.
* Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked.
* File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes. Arrow navigation should allow you to quickly view different versions of the file.
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( .
* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( . You should be able to apply suggestions in rapid succession. e.g. if you copy some roc code from the internet you should be able to apply 5 import suggestions quickly.
* Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e).
* Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut.
* Command to "benchmark this function" or "benchmark this test" with flamegraph and execution time per line.
@ -153,6 +153,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
* search through a database of our zullip questions
* ...
* smart insert: press a shortcut and enter a plain english description of a code snippet you need. Examples: "convert string to list of chars", "sort list of records by field foo descending", "plot this list with date on x-axis"...
* After the user has refactored code to be simpler, try finding other places in the code base where the same simplification can be made.
#### Autocomplete
@ -224,6 +225,12 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
The API and documentation are meant to interface with humans.
* [DocC](https://developer.apple.com/videos/play/wwdc2021/10166/) neat documentation approach for swift.
## General Plugin Ideas
### Inspiration
- [Boop](https://github.com/IvanMathy/Boop) scriptable scratchpad for developers. Contains collection of useful conversions: json formatting, url encoding, encode to base64...
## General Thoughts/Ideas
Thoughts and ideas possibly taken from above inspirations or separate.

View file

@ -1,5 +0,0 @@
#!/bin/bash
sed -i -e 's/\/\/pub mod mvc/pub mod mvc/g' src/lib.rs
sed -i -e 's/\/\/pub mod text_buffer/pub mod text_buffer/g' src/lib.rs
sed -i -e 's/^mod mvc/\/\/mod mvc/g' src/lib.rs
sed -i -e 's/^mod text_buffer/\/\/mod text_buffer/g' src/lib.rs

View file

@ -83,22 +83,24 @@ impl MarkupNode {
) -> EdResult<(usize, usize)> {
match self {
MarkupNode::Nested { children_ids, .. } => {
let mark_position_opt = children_ids.iter().position(|&c_id| c_id == child_id);
if let Some(child_index) = mark_position_opt {
let mut mark_child_index_opt: Option<usize> = None;
let mut child_ids_with_ast: Vec<MarkNodeId> = Vec::new();
let self_ast_id = self.get_ast_node_id();
let child_ids_with_ast = children_ids
.iter()
.filter(|c_id| {
let child_mark_node = markup_node_pool.get(**c_id);
for (indx, &mark_child_id) in children_ids.iter().enumerate() {
if mark_child_id == child_id {
mark_child_index_opt = Some(indx);
}
let child_mark_node = markup_node_pool.get(mark_child_id);
// a node that points to the same ast_node as the parent is a ',', '[', ']'
// those are not "real" ast children
child_mark_node.get_ast_node_id() != self_ast_id
})
.copied()
.collect::<Vec<MarkNodeId>>();
if child_mark_node.get_ast_node_id() != self_ast_id {
child_ids_with_ast.push(mark_child_id)
}
}
if let Some(child_index) = mark_child_index_opt {
if child_index == (children_ids.len() - 1) {
let ast_child_index = child_ids_with_ast.len();