From a6a90178ceb42b328902855a1a135533743e7ff3 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Tue, 22 Nov 2022 19:07:16 -0800 Subject: [PATCH 1/2] Refactor record_type to use combinators --- crates/compiler/parse/src/type_annotation.rs | 41 +++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index 4574935919..dbefd27216 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -322,29 +322,24 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<' fn record_type<'a>( stop_at_surface_has: bool, ) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { - use crate::type_annotation::TypeAnnotation::*; - - (move |arena, state, min_indent| { - let (_, fields, state) = collection_trailing_sep_e!( - // word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen), - word1(b'{', ETypeRecord::Open), - loc!(record_type_field()), - word1(b',', ETypeRecord::End), - // word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd), - word1(b'}', ETypeRecord::End), - ETypeRecord::Open, - ETypeRecord::IndentEnd, - AssignedField::SpaceBefore - ) - .parse(arena, state, min_indent)?; - - let field_term = specialize_ref(ETypeRecord::Type, term(stop_at_surface_has)); - let (_, ext, state) = optional(allocated(field_term)).parse(arena, state, min_indent)?; - - let result = Record { fields, ext }; - - Ok((MadeProgress, result, state)) - }) + map!( + and!( + collection_trailing_sep_e!( + word1(b'{', ETypeRecord::Open), + loc!(record_type_field()), + word1(b',', ETypeRecord::End), + word1(b'}', ETypeRecord::End), + ETypeRecord::Open, + ETypeRecord::IndentEnd, + AssignedField::SpaceBefore + ), + optional(allocated(specialize_ref( + ETypeRecord::Type, + term(stop_at_surface_has) + ))) + ), + |(fields, ext)| { TypeAnnotation::Record { fields, ext } } + ) .trace("type_annotation:record_type") } From c6b527314468cea9e6d3a512fa63a88e2e9abe61 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Tue, 22 Nov 2022 19:08:17 -0800 Subject: [PATCH 2/2] Implement tuple type parsing Also change some tests with newly relaxed indentation requirements, and remove an irrelevant test (since unindented close parens are now perfectly valid, the test is no longer useful). --- crates/ast/src/lang/core/types.rs | 3 + crates/compiler/can/src/annotation.rs | 6 + crates/compiler/fmt/src/annotation.rs | 17 ++ crates/compiler/fmt/src/spaces.rs | 4 + crates/compiler/parse/src/ast.rs | 7 + crates/compiler/parse/src/parser.rs | 3 + crates/compiler/parse/src/type_annotation.rs | 51 ++++-- ...ion_with_tuple_ext_type.expr.formatted.roc | 4 + ...nction_with_tuple_ext_type.expr.result-ast | 136 ++++++++++++++++ .../function_with_tuple_ext_type.expr.roc | 4 + ...unction_with_tuple_type.expr.formatted.roc | 4 + .../function_with_tuple_type.expr.result-ast | 129 +++++++++++++++ .../pass/function_with_tuple_type.expr.roc | 4 + .../pass/tuple_type.expr.formatted.roc | 4 + .../snapshots/pass/tuple_type.expr.result-ast | 138 ++++++++++++++++ .../tests/snapshots/pass/tuple_type.expr.roc | 4 + .../pass/tuple_type_ext.expr.formatted.roc | 4 + .../pass/tuple_type_ext.expr.result-ast | 154 ++++++++++++++++++ .../snapshots/pass/tuple_type_ext.expr.roc | 4 + crates/compiler/parse/tests/test_parse.rs | 4 + crates/reporting/src/error/parse.rs | 21 +++ crates/reporting/tests/test_reporting.rs | 44 +---- 22 files changed, 703 insertions(+), 46 deletions(-) create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.formatted.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.formatted.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.result-ast create mode 100644 crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.formatted.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.result-ast create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.formatted.roc create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.result-ast create mode 100644 crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.roc diff --git a/crates/ast/src/lang/core/types.rs b/crates/ast/src/lang/core/types.rs index 36f8f89490..7ef0b52e51 100644 --- a/crates/ast/src/lang/core/types.rs +++ b/crates/ast/src/lang/core/types.rs @@ -405,6 +405,9 @@ pub fn to_type2<'a>( Type2::Variable(var) } + Tuple { fields: _, ext: _ } => { + todo!("tuple type"); + } Record { fields, ext, .. } => { let field_types_map = can_assigned_fields(env, scope, references, &fields.items, region); diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index 15e24ea0fd..a71a2c8f66 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -448,6 +448,9 @@ pub fn find_type_def_symbols( As(actual, _, _) => { stack.push(&actual.value); } + Tuple { fields: _, ext: _ } => { + todo!("find_type_def_symbols: Tuple"); + } Record { fields, ext } => { let mut inner_stack = Vec::with_capacity(fields.items.len()); @@ -869,6 +872,9 @@ fn can_annotation_help( } } + Tuple { fields: _, ext: _ } => { + todo!("tuple"); + } Record { fields, ext } => { let ext_type = can_extension_type( env, diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index e3cd669f67..6be2cb6945 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -176,6 +176,15 @@ impl<'a> Formattable for TypeAnnotation<'a> { annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline()) } + Tuple { fields, ext } => { + match ext { + Some(ann) if ann.value.is_multiline() => return true, + _ => {} + } + + fields.items.iter().any(|field| field.value.is_multiline()) + } + Record { fields, ext } => { match ext { Some(ann) if ann.value.is_multiline() => return true, @@ -297,6 +306,14 @@ impl<'a> Formattable for TypeAnnotation<'a> { } } + Tuple { fields, ext } => { + fmt_collection(buf, indent, Braces::Round, *fields, newlines); + + if let Some(loc_ext_ann) = *ext { + loc_ext_ann.value.format(buf, indent); + } + } + Record { fields, ext } => { fmt_collection(buf, indent, Braces::Curly, *fields, newlines); diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index 5b83fd43dc..90faebfcde 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -776,6 +776,10 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> { vars: vars.remove_spaces(arena), }, ), + TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple { + fields: fields.remove_spaces(arena), + ext: ext.remove_spaces(arena), + }, TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { fields: fields.remove_spaces(arena), ext: ext.remove_spaces(arena), diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index c952f75fce..acaba20676 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -527,6 +527,13 @@ pub enum TypeAnnotation<'a> { ext: Option<&'a Loc>>, }, + Tuple { + fields: Collection<'a, Loc>>, + /// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. + /// This is None if it's a closed record annotation like `{ name: Str }`. + ext: Option<&'a Loc>>, + }, + /// A tag union, e.g. `[ TagUnion { /// The row type variable in an open tag union, e.g. the `a` in `[Foo, Bar]a`. diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index cf30c6a37b..9391dfeabe 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -672,6 +672,9 @@ pub enum ETypeTagUnion<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeInParens<'a> { + /// e.g. (), which isn't a valid type + Empty(Position), + End(Position), Open(Position), /// diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index dbefd27216..e1f92e1b63 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -116,7 +116,7 @@ fn term<'a>(stop_at_surface_has: bool) -> impl Parser<'a, Loc one_of!( loc_wildcard(), loc_inferred(), - specialize(EType::TInParens, loc_type_in_parens()), + specialize(EType::TInParens, loc_type_in_parens(stop_at_surface_has)), loc!(specialize(EType::TRecord, record_type(stop_at_surface_has))), loc!(specialize( EType::TTagUnion, @@ -185,7 +185,7 @@ fn loc_applied_arg<'a>( one_of!( loc_wildcard(), loc_inferred(), - specialize(EType::TInParens, loc_type_in_parens()), + specialize(EType::TInParens, loc_type_in_parens(stop_at_surface_has)), loc!(specialize(EType::TRecord, record_type(stop_at_surface_has))), loc!(specialize( EType::TTagUnion, @@ -206,16 +206,45 @@ fn loc_applied_arg<'a>( ) } -fn loc_type_in_parens<'a>() -> impl Parser<'a, Loc>, ETypeInParens<'a>> { - between!( - word1(b'(', ETypeInParens::Open), - space0_around_ee( - specialize_ref(ETypeInParens::Type, expression(true, false)), - ETypeInParens::IndentOpen, - ETypeInParens::IndentEnd, - ), - word1(b')', ETypeInParens::IndentEnd) +fn loc_type_in_parens<'a>( + stop_at_surface_has: bool, +) -> impl Parser<'a, Loc>, ETypeInParens<'a>> { + then( + loc!(and!( + collection_trailing_sep_e!( + word1(b'(', ETypeInParens::Open), + specialize_ref(ETypeInParens::Type, expression(true, false)), + word1(b',', ETypeInParens::End), + word1(b')', ETypeInParens::End), + ETypeInParens::Open, + ETypeInParens::IndentEnd, + TypeAnnotation::SpaceBefore + ), + optional(allocated(specialize_ref( + ETypeInParens::Type, + term(stop_at_surface_has) + ))) + )), + |_arena, state, progress, item| { + let Loc { + region, + value: (fields, ext), + } = item; + if fields.len() > 1 || ext.is_some() { + Ok(( + MadeProgress, + Loc::at(region, TypeAnnotation::Tuple { fields, ext }), + state, + )) + } else if fields.len() == 1 { + Ok((MadeProgress, fields.items[0], state)) + } else { + debug_assert!(fields.is_empty()); + Err((progress, ETypeInParens::Empty(state.pos()))) + } + }, ) + .trace("type_annotation:type_in_parens") } #[inline(always)] diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.formatted.roc b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.formatted.roc new file mode 100644 index 0000000000..0f097ea6a8 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.formatted.roc @@ -0,0 +1,4 @@ +f : (Str)a -> (Str)a +f = \x -> x + +f ("Str", 42) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast new file mode 100644 index 0000000000..e37c89546d --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast @@ -0,0 +1,136 @@ +Defs( + Defs { + tags: [ + Index(2147483649), + ], + regions: [ + @0-32, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-1 Identifier( + "f", + ), + @4-20 Function( + [ + @4-10 Tuple { + fields: [ + @5-8 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @9-10 BoundVariable( + "a", + ), + ), + }, + ], + @14-20 Tuple { + fields: [ + @15-18 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @19-20 BoundVariable( + "a", + ), + ), + }, + ), + ), + AnnotatedBody { + ann_pattern: @0-1 Identifier( + "f", + ), + ann_type: @4-20 Function( + [ + @4-10 Tuple { + fields: [ + @5-8 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @9-10 BoundVariable( + "a", + ), + ), + }, + ], + @14-20 Tuple { + fields: [ + @15-18 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @19-20 BoundVariable( + "a", + ), + ), + }, + ), + comment: None, + body_pattern: @21-22 Identifier( + "f", + ), + body_expr: @25-32 Closure( + [ + @26-27 Identifier( + "x", + ), + ], + @31-32 Var { + module_name: "", + ident: "x", + }, + ), + }, + ], + }, + @34-47 SpaceBefore( + Apply( + @34-35 Var { + module_name: "", + ident: "f", + }, + [ + @36-47 Tuple( + [ + @37-42 Str( + PlainLine( + "Str", + ), + ), + @44-46 Num( + "42", + ), + ], + ), + ], + Space, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.roc b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.roc new file mode 100644 index 0000000000..9357c626e1 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_ext_type.expr.roc @@ -0,0 +1,4 @@ +f : (Str)a -> (Str)a +f = \x -> x + +f ("Str", 42) diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.formatted.roc b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.formatted.roc new file mode 100644 index 0000000000..98cad54ca6 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.formatted.roc @@ -0,0 +1,4 @@ +f : I64 -> (I64, I64) +f = \x -> (x, x + 1) + +f 42 \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.result-ast new file mode 100644 index 0000000000..50ccc54f6f --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.result-ast @@ -0,0 +1,129 @@ +Defs( + Defs { + tags: [ + Index(2147483649), + ], + regions: [ + @0-42, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-1 Identifier( + "f", + ), + @4-21 Function( + [ + @4-7 Apply( + "", + "I64", + [], + ), + ], + @11-21 Tuple { + fields: [ + @12-15 Apply( + "", + "I64", + [], + ), + @17-20 Apply( + "", + "I64", + [], + ), + ], + ext: None, + }, + ), + ), + AnnotatedBody { + ann_pattern: @0-1 Identifier( + "f", + ), + ann_type: @4-21 Function( + [ + @4-7 Apply( + "", + "I64", + [], + ), + ], + @11-21 Tuple { + fields: [ + @12-15 Apply( + "", + "I64", + [], + ), + @17-20 Apply( + "", + "I64", + [], + ), + ], + ext: None, + }, + ), + comment: None, + body_pattern: @22-23 Identifier( + "f", + ), + body_expr: @26-42 Closure( + [ + @27-28 Identifier( + "x", + ), + ], + @32-42 Tuple( + [ + @33-34 Var { + module_name: "", + ident: "x", + }, + @36-41 BinOps( + [ + ( + @36-37 Var { + module_name: "", + ident: "x", + }, + @38-39 Plus, + ), + ], + @40-41 Num( + "1", + ), + ), + ], + ), + ), + }, + ], + }, + @44-48 SpaceBefore( + Apply( + @44-45 Var { + module_name: "", + ident: "f", + }, + [ + @46-48 Num( + "42", + ), + ], + Space, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.roc b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.roc new file mode 100644 index 0000000000..481fddbb80 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/function_with_tuple_type.expr.roc @@ -0,0 +1,4 @@ +f : I64 -> (I64, I64) +f = \x -> (x, x + 1) + +f 42 diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.formatted.roc b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.formatted.roc new file mode 100644 index 0000000000..7f1f6e3191 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.formatted.roc @@ -0,0 +1,4 @@ +f : (Str, Str) -> (Str, Str) +f = \x -> x + +f (1, 2) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.result-ast new file mode 100644 index 0000000000..3aae783860 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.result-ast @@ -0,0 +1,138 @@ +Defs( + Defs { + tags: [ + Index(2147483649), + ], + regions: [ + @0-39, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-1 Identifier( + "f", + ), + @3-27 Function( + [ + @3-13 Tuple { + fields: [ + @4-7 Apply( + "", + "Str", + [], + ), + @9-12 Apply( + "", + "Str", + [], + ), + ], + ext: None, + }, + ], + @17-27 Tuple { + fields: [ + @18-21 Apply( + "", + "Str", + [], + ), + @23-26 Apply( + "", + "Str", + [], + ), + ], + ext: None, + }, + ), + ), + AnnotatedBody { + ann_pattern: @0-1 Identifier( + "f", + ), + ann_type: @3-27 Function( + [ + @3-13 Tuple { + fields: [ + @4-7 Apply( + "", + "Str", + [], + ), + @9-12 Apply( + "", + "Str", + [], + ), + ], + ext: None, + }, + ], + @17-27 Tuple { + fields: [ + @18-21 Apply( + "", + "Str", + [], + ), + @23-26 Apply( + "", + "Str", + [], + ), + ], + ext: None, + }, + ), + comment: None, + body_pattern: @28-29 Identifier( + "f", + ), + body_expr: @32-39 Closure( + [ + @33-34 Identifier( + "x", + ), + ], + @38-39 Var { + module_name: "", + ident: "x", + }, + ), + }, + ], + }, + @41-49 SpaceBefore( + Apply( + @41-42 Var { + module_name: "", + ident: "f", + }, + [ + @43-49 Tuple( + [ + @44-45 Num( + "1", + ), + @47-48 Num( + "2", + ), + ], + ), + ], + Space, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.roc b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.roc new file mode 100644 index 0000000000..df9c603722 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type.expr.roc @@ -0,0 +1,4 @@ +f: (Str, Str) -> (Str, Str) +f = \x -> x + +f (1, 2) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.formatted.roc b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.formatted.roc new file mode 100644 index 0000000000..434088cf8f --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.formatted.roc @@ -0,0 +1,4 @@ +f : (Str, Str)a -> (Str, Str)a +f = \x -> x + +f (1, 2) \ No newline at end of file diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.result-ast new file mode 100644 index 0000000000..3d9bab17da --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.result-ast @@ -0,0 +1,154 @@ +Defs( + Defs { + tags: [ + Index(2147483649), + ], + regions: [ + @0-41, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-1 Identifier( + "f", + ), + @3-29 Function( + [ + @3-14 Tuple { + fields: [ + @4-7 Apply( + "", + "Str", + [], + ), + @9-12 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @13-14 BoundVariable( + "a", + ), + ), + }, + ], + @18-29 Tuple { + fields: [ + @19-22 Apply( + "", + "Str", + [], + ), + @24-27 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @28-29 BoundVariable( + "a", + ), + ), + }, + ), + ), + AnnotatedBody { + ann_pattern: @0-1 Identifier( + "f", + ), + ann_type: @3-29 Function( + [ + @3-14 Tuple { + fields: [ + @4-7 Apply( + "", + "Str", + [], + ), + @9-12 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @13-14 BoundVariable( + "a", + ), + ), + }, + ], + @18-29 Tuple { + fields: [ + @19-22 Apply( + "", + "Str", + [], + ), + @24-27 Apply( + "", + "Str", + [], + ), + ], + ext: Some( + @28-29 BoundVariable( + "a", + ), + ), + }, + ), + comment: None, + body_pattern: @30-31 Identifier( + "f", + ), + body_expr: @34-41 Closure( + [ + @35-36 Identifier( + "x", + ), + ], + @40-41 Var { + module_name: "", + ident: "x", + }, + ), + }, + ], + }, + @43-51 SpaceBefore( + Apply( + @43-44 Var { + module_name: "", + ident: "f", + }, + [ + @45-51 Tuple( + [ + @46-47 Num( + "1", + ), + @49-50 Num( + "2", + ), + ], + ), + ], + Space, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.roc b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.roc new file mode 100644 index 0000000000..cce4b4c704 --- /dev/null +++ b/crates/compiler/parse/tests/snapshots/pass/tuple_type_ext.expr.roc @@ -0,0 +1,4 @@ +f: (Str, Str)a -> (Str, Str)a +f = \x -> x + +f (1, 2) \ No newline at end of file diff --git a/crates/compiler/parse/tests/test_parse.rs b/crates/compiler/parse/tests/test_parse.rs index 4c7c8a45b1..fc3886de5a 100644 --- a/crates/compiler/parse/tests/test_parse.rs +++ b/crates/compiler/parse/tests/test_parse.rs @@ -186,6 +186,8 @@ mod test_parse { pass/list_patterns.expr, pass/lowest_float.expr, pass/lowest_int.expr, + pass/tuple_type.expr, + pass/tuple_type_ext.expr, pass/malformed_ident_due_to_underscore.expr, pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399 @@ -302,6 +304,8 @@ mod test_parse { pass/when_with_negative_numbers.expr, pass/when_with_numbers.expr, pass/when_with_records.expr, + pass/function_with_tuple_type.expr, + pass/function_with_tuple_ext_type.expr, pass/where_clause_function.expr, pass/where_clause_multiple_bound_abilities.expr, pass/where_clause_multiple_has_across_newlines.expr, diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index e9ed73dd68..17d554e3f8 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -2907,6 +2907,27 @@ fn to_tinparens_report<'a>( } } + ETypeInParens::Empty(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack([ + alloc.reflow("I am partway through parsing a parenthesized type:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat([ + alloc.reflow(r"I was expecting to see an expression next."), + alloc.reflow(r"Note, Roc doesn't use '()' as a null type."), + ]), + ]); + + Report { + filename, + doc, + title: "EMPTY PARENTHESES".to_string(), + severity: Severity::RuntimeError, + } + } + ETypeInParens::End(pos) => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 0c6df4c8e8..f8ebf5d129 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -4410,13 +4410,14 @@ mod test_reporting { @r###" ── UNFINISHED PARENTHESES ────────────────── tmp/type_in_parens_start/Test.roc ─ - I just started parsing a type in parentheses, but I got stuck here: + I am partway through parsing a type in parentheses, but I got stuck + here: 4│ f : ( ^ - Tag unions look like [Many I64, None], so I was expecting to see a tag - name next. + I was expecting to see a parenthesis before this, so try adding a ) + and see if that helps? Note: I may be confused by indentation "### @@ -4436,12 +4437,12 @@ mod test_reporting { here: 4│ f : ( I64 - ^ + 5│ + 6│ + ^ - I was expecting to see a parenthesis before this, so try adding a ) - and see if that helps? - - Note: I may be confused by indentation + I was expecting to see a closing parenthesis before this, so try + adding a ) and see if that helps? "### ); @@ -6049,33 +6050,6 @@ All branches in an `if` must have the same type! "### ); - test_report!( - outdented_in_parens, - indoc!( - r#" - Box : ( - Str - ) - - 4 - "# - ), - @r###" - ── NEED MORE INDENTATION ──────────────────── tmp/outdented_in_parens/Test.roc ─ - - I am partway through parsing a type in parentheses, but I got stuck - here: - - 4│ Box : ( - 5│ Str - 6│ ) - ^ - - I need this parenthesis to be indented more. Try adding more spaces - before it! - "### - ); - test_report!( backpassing_type_error, indoc!(