mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Always parse type signatures in let blocks.
Formerly, this would not parse foo : Int foo = 4 While this would bar = 3 foo : Int foo = 4 Now, the first def can also be a type annotation. This change performs some code duplication. This can probably be abstracted/unified, but I'm not sure how to do that in a clean way.
This commit is contained in:
parent
dbea3324a3
commit
502021d955
2 changed files with 170 additions and 3 deletions
127
src/parse/mod.rs
127
src/parse/mod.rs
|
@ -526,6 +526,81 @@ fn parse_def_expr<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_def_signature<'a>(
|
||||||
|
min_indent: u16,
|
||||||
|
colon_indent: u16,
|
||||||
|
arena: &'a Bump,
|
||||||
|
state: State<'a>,
|
||||||
|
loc_first_pattern: Located<Pattern<'a>>,
|
||||||
|
) -> ParseResult<'a, Expr<'a>> {
|
||||||
|
let original_indent = state.indent_col;
|
||||||
|
|
||||||
|
if original_indent < min_indent {
|
||||||
|
Err((
|
||||||
|
Fail {
|
||||||
|
attempting: state.attempting,
|
||||||
|
reason: FailReason::OutdentedTooFar,
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
))
|
||||||
|
// `<` because ':' should be same indent or greater
|
||||||
|
} else if colon_indent < original_indent {
|
||||||
|
panic!("TODO the : in this declaration seems outdented");
|
||||||
|
} else {
|
||||||
|
// Indented more beyond the original indent.
|
||||||
|
let indented_more = original_indent + 1;
|
||||||
|
|
||||||
|
then(
|
||||||
|
attempt!(
|
||||||
|
Attempting::Def,
|
||||||
|
and!(
|
||||||
|
// Parse the body of the first def. It doesn't need any spaces
|
||||||
|
// around it parsed, because both the subsquent defs and the
|
||||||
|
// final body will have space1_before on them.
|
||||||
|
//
|
||||||
|
// It should be indented more than the original, and it will
|
||||||
|
// end when outdented again.
|
||||||
|
type_annotation::located(indented_more),
|
||||||
|
and!(
|
||||||
|
// Optionally parse additional defs.
|
||||||
|
zero_or_more!(allocated(space1_before(
|
||||||
|
loc!(def(original_indent)),
|
||||||
|
original_indent,
|
||||||
|
))),
|
||||||
|
// Parse the final expression that will be returned.
|
||||||
|
// It should be indented the same amount as the original.
|
||||||
|
space1_before(
|
||||||
|
loc!(move |arena, state: State<'a>| {
|
||||||
|
parse_expr(original_indent, arena, state)
|
||||||
|
}),
|
||||||
|
original_indent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
move |arena, state, (loc_first_annotation, (mut defs, loc_ret))| {
|
||||||
|
let pat: Located<Pattern<'a>> = loc_first_pattern.clone();
|
||||||
|
let ann: Located<ast::TypeAnnotation<'a>> = loc_first_annotation;
|
||||||
|
let first_def: Def<'a> =
|
||||||
|
// TODO is there some way to eliminate this .clone() here?
|
||||||
|
Def::Annotation(pat, ann);
|
||||||
|
|
||||||
|
let loc_first_def = Located {
|
||||||
|
value: first_def,
|
||||||
|
region: loc_first_pattern.region,
|
||||||
|
};
|
||||||
|
|
||||||
|
// contrary to defs with an expression body, we must ensure the annotation comes just before its
|
||||||
|
// corresponding definition (the one with the body).
|
||||||
|
defs.insert(0, arena.alloc(loc_first_def));
|
||||||
|
|
||||||
|
Ok((Expr::Defs(defs, arena.alloc(loc_ret)), state))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.parse(arena, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
|
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
|
||||||
skip_first!(
|
skip_first!(
|
||||||
// If this is a reserved keyword ("if", "then", "case, "when"), then
|
// If this is a reserved keyword ("if", "then", "case, "when"), then
|
||||||
|
@ -930,7 +1005,10 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
// If there aren't any args, there may be a '=' or ':' after it.
|
// If there aren't any args, there may be a '=' or ':' after it.
|
||||||
// (It's a syntax error to write e.g. `foo bar =` - so if there
|
// (It's a syntax error to write e.g. `foo bar =` - so if there
|
||||||
// were any args, there is definitely no need to parse '=' or ':'!)
|
// were any args, there is definitely no need to parse '=' or ':'!)
|
||||||
and!(space0(min_indent), either!(equals_with_indent(), char(':')))
|
and!(
|
||||||
|
space0(min_indent),
|
||||||
|
either!(equals_with_indent(), colon_with_indent())
|
||||||
|
)
|
||||||
))
|
))
|
||||||
),
|
),
|
||||||
move |arena, state, (loc_ident, opt_extras)| {
|
move |arena, state, (loc_ident, opt_extras)| {
|
||||||
|
@ -974,8 +1052,26 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
|
|
||||||
Ok((answer, state))
|
Ok((answer, state))
|
||||||
}
|
}
|
||||||
Some(Either::Second((_space_list, Either::Second(())))) => {
|
Some(Either::Second((spaces_before_colon, Either::Second(colon_indent)))) => {
|
||||||
panic!("TODO handle annotation, making sure not to drop comments!");
|
let pattern: Pattern<'a> = Pattern::from_ident(arena, loc_ident.value);
|
||||||
|
let value = if spaces_before_colon.is_empty() {
|
||||||
|
pattern
|
||||||
|
} else {
|
||||||
|
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_colon)
|
||||||
|
};
|
||||||
|
let region = loc_ident.region;
|
||||||
|
let loc_pattern = Located { region, value };
|
||||||
|
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
|
||||||
|
let (parsed_expr, state) =
|
||||||
|
parse_def_signature(min_indent, colon_indent, arena, state, loc_pattern)?;
|
||||||
|
|
||||||
|
let answer = if spaces_after_equals.is_empty() {
|
||||||
|
parsed_expr
|
||||||
|
} else {
|
||||||
|
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((answer, state))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let ident = loc_ident.value.clone();
|
let ident = loc_ident.value.clone();
|
||||||
|
@ -1020,6 +1116,31 @@ pub fn equals_with_indent<'a>() -> impl Parser<'a, u16> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn colon_with_indent<'a>() -> impl Parser<'a, u16> {
|
||||||
|
move |_arena, state: State<'a>| {
|
||||||
|
let mut iter = state.input.chars();
|
||||||
|
|
||||||
|
match iter.next() {
|
||||||
|
Some(ch) if ch == ':' => {
|
||||||
|
match iter.peekable().peek() {
|
||||||
|
// The ':' must not be followed by `=`
|
||||||
|
Some(next_ch) if next_ch != &':' && next_ch != &'=' => {
|
||||||
|
Ok((state.indent_col, state.advance_without_indenting(1)?))
|
||||||
|
}
|
||||||
|
Some(next_ch) => Err(unexpected(*next_ch, 0, state, Attempting::Def)),
|
||||||
|
None => Err(unexpected_eof(
|
||||||
|
1,
|
||||||
|
Attempting::Def,
|
||||||
|
state.advance_without_indenting(1)?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ch) => Err(unexpected(ch, 0, state, Attempting::Def)),
|
||||||
|
None => Err(unexpected_eof(0, Attempting::Def, state)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn case_with_indent<'a>() -> impl Parser<'a, u16> {
|
pub fn case_with_indent<'a>() -> impl Parser<'a, u16> {
|
||||||
move |arena, state: State<'a>| {
|
move |arena, state: State<'a>| {
|
||||||
string(keyword::CASE)
|
string(keyword::CASE)
|
||||||
|
|
|
@ -1193,6 +1193,52 @@ mod test_parse {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_signature_def() {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let newline = bumpalo::vec![in &arena; Newline];
|
||||||
|
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||||
|
let signature = Def::Annotation(
|
||||||
|
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||||
|
Located::new(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
9,
|
||||||
|
roc::parse::ast::TypeAnnotation::Apply(&[], "Int", &[]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let def = Def::Body(
|
||||||
|
arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))),
|
||||||
|
arena.alloc(Located::new(1, 1, 6, 7, Int("4"))),
|
||||||
|
);
|
||||||
|
let loc_def = &*arena.alloc(Located::new(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
7,
|
||||||
|
Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature));
|
||||||
|
let defs = bumpalo::vec![in &arena; loc_ann, loc_def];
|
||||||
|
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
|
||||||
|
let loc_ret = Located::new(3, 3, 0, 2, ret);
|
||||||
|
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||||
|
|
||||||
|
assert_parses_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
foo : Int
|
||||||
|
foo = 4
|
||||||
|
|
||||||
|
42
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
expected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// CASE
|
// CASE
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue