diff --git a/crates/compiler/fmt/src/def.rs b/crates/compiler/fmt/src/def.rs index 5bdbe32b43c..ac3d3eed5b8 100644 --- a/crates/compiler/fmt/src/def.rs +++ b/crates/compiler/fmt/src/def.rs @@ -1000,6 +1000,7 @@ pub fn fmt_body<'a>( && !matches!(body.extract_spaces().item, Expr::Defs(..)) && !matches!(body.extract_spaces().item, Expr::Return(..)) && !matches!(body.extract_spaces().item, Expr::Backpassing(..)) + && !matches!(body.extract_spaces().item, Expr::DbgStmt { .. }) && !starts_with_expect_ident(body) } else { false diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 3eed0b7d426..233040bc062 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -236,6 +236,7 @@ fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc>, EEx positive_number_literal_help() )), loc(specialize_err(EExpr::Closure, closure_help(options))), + loc(crash_kw()), loc(specialize_err(EExpr::Dbg, dbg_kw())), loc(try_kw()), loc(record_literal_help()), @@ -284,12 +285,13 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { } fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { - move |arena: &'a Bump, state: State<'a>, min_indent: u32| { + (move |arena: &'a Bump, state: State<'a>, min_indent: u32| { let (_, _, next_state) = crate::parser::keyword(crate::keyword::CRASH, EExpr::Crash) .parse(arena, state, min_indent)?; Ok((MadeProgress, Expr::Crash, next_state)) - } + }) + .trace("crash_kw") } fn loc_possibly_negative_or_negated_term<'a>( diff --git a/crates/compiler/parse/src/ident.rs b/crates/compiler/parse/src/ident.rs index 6d1ae664f39..2e8f625f2c7 100644 --- a/crates/compiler/parse/src/ident.rs +++ b/crates/compiler/parse/src/ident.rs @@ -1,4 +1,5 @@ use crate::ast::TryTarget; +use crate::keyword::is_allowed_identifier; use crate::parser::Progress::{self, *}; use crate::parser::{BadInputError, EExpr, ParseResult, Parser}; use crate::state::State; @@ -60,7 +61,7 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { move |_, state: State<'a>, _min_indent: u32| match chomp_lowercase_part(state.bytes()) { Err(progress) => Err((progress, ())), Ok(ident) => { - if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) { + if !is_allowed_identifier(ident) { Err((NoProgress, ())) } else { let width = ident.len(); @@ -87,7 +88,7 @@ pub fn lowercase_ident_keyword_e<'a>() -> impl Parser<'a, &'a str, ()> { move |_, state: State<'a>, _min_indent: u32| match chomp_lowercase_part(state.bytes()) { Err(progress) => Err((progress, ())), Ok(ident) => { - if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) { + if !is_allowed_identifier(ident) { Err((MadeProgress, ())) } else { let width = ident.len(); @@ -137,7 +138,7 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { move |_, state: State<'a>, _min_indent: u32| match chomp_anycase_part(state.bytes()) { Err(progress) => Err((progress, ())), Ok(ident) => { - if crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) { + if !is_allowed_identifier(ident) { Err((MadeProgress, ())) } else { let width = ident.len(); @@ -165,11 +166,9 @@ pub fn parse_ident<'a>( let state = advance_state!(state, width as usize)?; if let Ident::Access { module_name, parts } = ident { if module_name.is_empty() { - if let Some(first) = parts.first() { - for keyword in crate::keyword::KEYWORDS.iter() { - if first == &Accessor::RecordField(keyword) { - return Err((NoProgress, EExpr::Start(initial.pos()))); - } + if let Some(Accessor::RecordField(ident)) = parts.first() { + if !is_allowed_identifier(ident) { + return Err((NoProgress, EExpr::Start(initial.pos()))); } } } diff --git a/crates/compiler/parse/src/keyword.rs b/crates/compiler/parse/src/keyword.rs index 125a38486e1..8169a0678a8 100644 --- a/crates/compiler/parse/src/keyword.rs +++ b/crates/compiler/parse/src/keyword.rs @@ -21,7 +21,13 @@ pub const WHERE: &str = "where"; // These keywords are valid in headers pub const PLATFORM: &str = "platform"; -pub const KEYWORDS: [&str; 12] = [ +pub const KEYWORDS: [&str; 11] = [ IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, RETURN, CRASH, - "expect!", // not itself a keyword, but it's problematic if we allow an identifier like this! ]; + +pub fn is_allowed_identifier(mut ident: &str) -> bool { + if ident.ends_with('!') { + ident = &ident[..ident.len() - 1]; + } + !crate::keyword::KEYWORDS.iter().any(|kw| &ident == kw) +} diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index 8defff66297..2423b7eb0a2 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -17,7 +17,7 @@ use crate::parser::{ }; use crate::parser::{ allocated, backtrackable, byte, fail, optional, specialize_err, specialize_err_ref, two_bytes, - word, EType, ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, Parser, + EType, ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, Parser, Progress::*, }; use crate::state::State; @@ -322,7 +322,10 @@ fn loc_type_in_parens<'a>( specialize_err_ref(ETypeInParens::Type, arrow()), Sep::FunctionArrow ), - map(word(keyword::WHERE, ETypeInParens::End), |_| Sep::Where), + map( + crate::parser::keyword(keyword::WHERE, ETypeInParens::End), + |_| Sep::Where + ), ]; match sep.parse(arena, state.clone(), 0) { @@ -707,7 +710,7 @@ fn implements_clause<'a>() -> impl Parser<'a, Loc>, EType<' ), skip_first( // Parse "implements"; we don't care about this keyword - word(crate::keyword::IMPLEMENTS, EType::TImplementsClause), + crate::parser::keyword(crate::keyword::IMPLEMENTS, EType::TImplementsClause), // Parse "Hash & ..."; this may be qualified from another module like "Hash.Hash" absolute_column_min_indent(ability_chain()), ), @@ -734,7 +737,7 @@ fn implements_clause_chain<'a>( move |arena, state: State<'a>, min_indent: u32| { let (_, (spaces_before, ()), state) = and( space0_e(EType::TIndentStart), - word(crate::keyword::WHERE, EType::TWhereBar), + crate::parser::keyword(crate::keyword::WHERE, EType::TWhereBar), ) .parse(arena, state, min_indent)?; @@ -768,7 +771,7 @@ fn parse_implements_clause_chain_after_where<'a>( pub fn implements_abilities<'a>() -> impl Parser<'a, Loc>, EType<'a>> { increment_min_indent(skip_first( // Parse "implements"; we don't care about this keyword - word(crate::keyword::IMPLEMENTS, EType::TImplementsClause), + crate::parser::keyword(crate::keyword::IMPLEMENTS, EType::TImplementsClause), // Parse "Hash"; this may be qualified from another module like "Hash.Hash" space0_before_e( loc(map( diff --git a/crates/compiler/test_syntax/src/test_helpers.rs b/crates/compiler/test_syntax/src/test_helpers.rs index 3f7bfcded0b..487b8502efd 100644 --- a/crates/compiler/test_syntax/src/test_helpers.rs +++ b/crates/compiler/test_syntax/src/test_helpers.rs @@ -360,8 +360,8 @@ impl<'a> Input<'a> { * * * AST after formatting:\n{:#?}\n\n", self.as_str(), output.as_ref().as_str(), - ast_normalized, - reparsed_ast_normalized + actual, + reparsed_ast ); } diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.result-ast new file mode 100644 index 00000000000..83572402651 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.result-ast @@ -0,0 +1 @@ +Expr(Start(@0), @0) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.roc b/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.roc new file mode 100644 index 00000000000..61e69f7de50 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/d_assign_return_bang.expr.roc @@ -0,0 +1,3 @@ +D=return!- + e +z# diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/dbg_bang_neg_bang_if_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/fail/dbg_bang_neg_bang_if_bang.expr.result-ast new file mode 100644 index 00000000000..c7c6080e905 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/dbg_bang_neg_bang_if_bang.expr.result-ast @@ -0,0 +1 @@ +Expr(Start(@6), @0) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.roc b/crates/compiler/test_syntax/tests/snapshots/fail/dbg_bang_neg_bang_if_bang.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/fail/dbg_bang_neg_bang_if_bang.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/empty_record_assignment_d_when_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/fail/empty_record_assignment_d_when_bang.expr.result-ast new file mode 100644 index 00000000000..06ba3fab2a8 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/empty_record_assignment_d_when_bang.expr.result-ast @@ -0,0 +1 @@ +Expr(BadExprEnd(@5), @0) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.roc b/crates/compiler/test_syntax/tests/snapshots/fail/empty_record_assignment_d_when_bang.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/fail/empty_record_assignment_d_when_bang.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.result-ast new file mode 100644 index 00000000000..21ff396022c --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.result-ast @@ -0,0 +1 @@ +Expr(BadExprEnd(@12), @0) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.roc b/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.roc new file mode 100644 index 00000000000..114440732da --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/sub_minus_o_apply_minus_crash_bang.expr.roc @@ -0,0 +1,2 @@ +h- +-o -crash! diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.result-ast new file mode 100644 index 00000000000..f68466384af --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.result-ast @@ -0,0 +1 @@ +Expr(Type(TInParens(End(@5), @2), @2), @0) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.roc b/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.roc new file mode 100644 index 00000000000..cb55b03f6bf --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/fail/wherem_implementsf.expr.roc @@ -0,0 +1,4 @@ +s:(s +wherem +implementsF)A +_ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.formatted.roc deleted file mode 100644 index ee081f0dab1..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.formatted.roc +++ /dev/null @@ -1 +0,0 @@ -dbg ! -(!if!) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.result-ast deleted file mode 100644 index 600f42df56d..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_bang_neg_bang_if_bang.expr.result-ast +++ /dev/null @@ -1,19 +0,0 @@ -@0-9 Apply( - @0-3 Dbg, - [ - @3-9 UnaryOp( - @4-9 UnaryOp( - @5-9 UnaryOp( - @6-9 Var { - module_name: "", - ident: "if!", - }, - @5-6 Not, - ), - @4-5 Negate, - ), - @3-4 Not, - ), - ], - Space, -) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc new file mode 100644 index 00000000000..9f4f4e3faaa --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc @@ -0,0 +1,4 @@ +{} = + dbg c + c +e \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.result-ast new file mode 100644 index 00000000000..200ff195f93 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.result-ast @@ -0,0 +1,60 @@ +@0-15 SpaceAfter( + Defs( + Defs { + tags: [ + EitherIndex(2147483648), + ], + regions: [ + @0-13, + ], + space_before: [ + Slice { start: 0, length: 0 }, + ], + space_after: [ + Slice { start: 0, length: 0 }, + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @0-2 RecordDestructure( + [], + ), + @5-10 SpaceBefore( + DbgStmt { + first: @9-10 Var { + module_name: "", + ident: "c", + }, + extra_args: [], + continuation: @12-13 SpaceBefore( + Var { + module_name: "", + ident: "c", + }, + [ + Newline, + ], + ), + }, + [ + Newline, + ], + ), + ), + ], + }, + @14-15 SpaceBefore( + Var { + module_name: "", + ident: "e", + }, + [ + Newline, + ], + ), + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc new file mode 100644 index 00000000000..331930722f0 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc @@ -0,0 +1,4 @@ +{}= + dbg c + c +e diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.formatted.roc deleted file mode 100644 index c45f91f8687..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.formatted.roc +++ /dev/null @@ -1,2 +0,0 @@ -d when! -s \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.result-ast deleted file mode 100644 index 9989f19a96b..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment_d_when_bang.expr.result-ast +++ /dev/null @@ -1,52 +0,0 @@ -@0-14 Defs( - Defs { - tags: [ - EitherIndex(2147483648), - ], - regions: [ - @0-11, - ], - space_before: [ - Slice { start: 0, length: 0 }, - ], - space_after: [ - Slice { start: 0, length: 0 }, - ], - spaces: [], - type_defs: [], - value_defs: [ - Body( - @0-3 RecordDestructure( - Collection { - items: [], - final_comments: [ - Newline, - ], - }, - ), - @4-11 Apply( - @4-5 Var { - module_name: "", - ident: "d", - }, - [ - @6-11 Var { - module_name: "", - ident: "when!", - }, - ], - Space, - ), - ), - ], - }, - @13-14 SpaceBefore( - Var { - module_name: "", - ident: "s", - }, - [ - Newline, - ], - ), -) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.formatted.roc index 672adf6b033..056af036947 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.formatted.roc @@ -1,6 +1,5 @@ ( - i : - a # - where w implements I + i : a # + wherew implementsI e ) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.result-ast index 7ee0ef80a64..b504b8887f2 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_and_implements_lookalikes.expr.result-ast @@ -4,46 +4,48 @@ Defs { tags: [ EitherIndex(2147483648), + EitherIndex(2147483649), ], regions: [ - @1-26, + @1-4, + @8-26, ], space_before: [ Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + ], + spaces: [ + LineComment( + "", + ), ], - spaces: [], type_defs: [], value_defs: [ Annotation( @1-2 Identifier { ident: "i", }, - @3-26 Where( - @3-4 SpaceAfter( - BoundVariable( - "a", - ), - [ - LineComment( - "", - ), - ], - ), + @3-4 BoundVariable( + "a", + ), + ), + Stmt( + @8-26 Apply( + @8-14 Var { + module_name: "", + ident: "wherew", + }, [ - @13-26 ImplementsClause { - var: @13-14 "w", - abilities: [ - @25-26 Apply( - "", - "I", - [], - ), - ], + @15-26 Var { + module_name: "", + ident: "implementsI", }, ], + Space, ), ), ], diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs index 9f7ea7bdc97..67dc719258d 100644 --- a/crates/compiler/test_syntax/tests/test_snapshots.rs +++ b/crates/compiler/test_syntax/tests/test_snapshots.rs @@ -190,6 +190,8 @@ mod test_snapshots { fail/backpassing_after_annotation.expr, fail/bound_variable.expr, fail/comment_with_tab.expr, + fail/d_assign_return_bang.expr, + fail/dbg_bang_neg_bang_if_bang.expr, fail/def_missing_final_expression.expr, fail/def_without_newline.expr, fail/deprecated_interpolated_string.expr, @@ -197,6 +199,7 @@ mod test_snapshots { fail/elm_function_syntax.expr, fail/empty_or_pattern.expr, fail/empty_record_assign_expect_bang.expr, + fail/empty_record_assignment_d_when_bang.expr, fail/empty_return.expr, fail/error_inline_alias_argument_uppercase.expr, fail/error_inline_alias_not_an_alias.expr, @@ -247,6 +250,7 @@ mod test_snapshots { fail/return_in_pat.expr, fail/single_no_end.expr, fail/str_over_large_unicode_escape.expr, + fail/sub_minus_o_apply_minus_crash_bang.expr, fail/tab_crash.header, fail/tag_union_end.expr, fail/tag_union_lowercase_tag_name.expr, @@ -272,6 +276,7 @@ mod test_snapshots { fail/when_over_indented_int.expr, fail/when_over_indented_underscore.expr, fail/where_type_variable.expr, + fail/wherem_implementsf.expr, fail/wild_case_arrow.expr, malformed/bad_opaque_ref.expr, malformed/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 @@ -387,7 +392,6 @@ mod test_snapshots { pass/crazy_pat_ann.expr, pass/curried_function_type.expr, pass/dbg.expr, - pass/dbg_bang_neg_bang_if_bang.expr, pass/dbg_double.expr, pass/dbg_double_newline.expr, pass/dbg_extra_parens.expr, @@ -417,11 +421,11 @@ mod test_snapshots { pass/empty_platform_header.header, pass/empty_record.expr, pass/empty_record_assign_backpassing.expr, + pass/empty_record_assign_dbg.expr, pass/empty_record_assign_implements.expr, pass/empty_record_assign_return.expr, pass/empty_record_assign_tag.expr, pass/empty_record_assignment.expr, - pass/empty_record_assignment_d_when_bang.expr, pass/empty_record_eq_dbg.expr, pass/empty_record_eq_newlines_doubleeq.expr, pass/empty_record_newline_assign.expr,