mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into complex-type-signatures
This commit is contained in:
commit
a68eb32b6f
16 changed files with 450 additions and 228 deletions
5
.github/workflows/benchmarks.yml
vendored
5
.github/workflows/benchmarks.yml
vendored
|
@ -1,4 +1,7 @@
|
|||
on: [pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
name: Benchmarks
|
||||
|
||||
|
|
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -1,4 +1,7 @@
|
|||
on: [pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
name: CI
|
||||
|
||||
|
|
24
.github/workflows/spellcheck.yml
vendored
Normal file
24
.github/workflows/spellcheck.yml
vendored
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)),
|
||||
},
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue