diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 1279035120..cd47a37128 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -1,4 +1,7 @@ -on: [pull_request] +on: + pull_request: + paths-ignore: + - '**.md' name: Benchmarks diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fd1f8f542..f3e6f6051a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,7 @@ -on: [pull_request] +on: + pull_request: + paths-ignore: + - '**.md' name: CI diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml new file mode 100644 index 0000000000..fd319f4e65 --- /dev/null +++ b/.github/workflows/spellcheck.yml @@ -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 diff --git a/Earthfile b/Earthfile index 2ccbe3f7b9..52e51f15d4 100644 --- a/Earthfile +++ b/Earthfile @@ -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 diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index d63f4dad5e..be0aa9bb1d 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -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); diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index d374e99463..ef6a508c54 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -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(&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, Guard<'a>, Pattern<'a>)>, + guard: Guard<'a>, + patterns: Vec<(Vec, Pattern<'a>)>, } fn to_decision_tree(raw_branches: Vec) -> 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) -> 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, + mut edges: Vec<(GuardedTest<'a>, DecisionTree<'a>)>, + default: Option>>, +) -> 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, Guard<'a>, Pattern<'a>), - path_patterns: &mut Vec<(Vec, Guard<'a>, Pattern<'a>)>, + path_pattern: (Vec, Pattern<'a>), + path_patterns: &mut Vec<(Vec, 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