diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 2baef62d38..821cbb7468 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -969,6 +969,21 @@ mod cli_run { TestCliCommands::Run, ) } + #[test] + #[serial(cli_platform)] + #[cfg_attr(windows, ignore)] + fn ingested_file_bytes_no_ann() { + test_roc_app( + "examples/cli", + "ingested-file-bytes-no-ann.roc", + &[], + &[], + &[], + "162088\n", + UseValgrind::No, + TestCliCommands::Run, + ) + } #[test] #[serial(zig_platform_parser_package_basic_cli_url)] diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index f81b746c7d..0e992b9ba1 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -167,7 +167,7 @@ enum PendingValueDef<'a> { /// Ingested file IngestedFile( Loc, - Loc>, + Option>>, Loc>, ), } @@ -2336,7 +2336,7 @@ fn canonicalize_pending_value_def<'a>( None, ) } - IngestedFile(loc_pattern, loc_ann, path_literal) => { + IngestedFile(loc_pattern, opt_loc_ann, path_literal) => { let relative_path = if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value { ingested_path @@ -2373,23 +2373,29 @@ fn canonicalize_pending_value_def<'a>( let loc_expr = Loc::at(path_literal.region, expr); - let can_ann = canonicalize_annotation( - env, - scope, - &loc_ann.value, - loc_ann.region, - var_store, - pending_abilities_in_scope, - AnnotationFor::Value, - ); + let opt_loc_can_ann = if let Some(loc_ann) = opt_loc_ann { + let can_ann = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + pending_abilities_in_scope, + AnnotationFor::Value, + ); - output.references.union_mut(&can_ann.references); + output.references.union_mut(&can_ann.references); + + Some(Loc::at(loc_ann.region, can_ann)) + } else { + None + }; let def = single_can_def( loc_pattern, loc_expr, var_store.fresh(), - Some(Loc::at(loc_ann.region, can_ann)), + opt_loc_can_ann, SendMap::default(), ); @@ -3108,9 +3114,9 @@ fn to_pending_value_def<'a>( }) } IngestedFileImport(ingested_file) => { - let typed_ident = ingested_file.name.item.extract_spaces().item; + let loc_name = ingested_file.name.item; - let symbol = match scope.introduce(typed_ident.ident.value.into(), typed_ident.ident.region) { + let symbol = match scope.introduce(loc_name.value.into(), loc_name.region) { Ok(symbol ) => symbol, Err((original, shadow, _)) => { env.problem(Problem::Shadowing { @@ -3123,9 +3129,9 @@ fn to_pending_value_def<'a>( } }; - let loc_pattern = Loc::at(typed_ident.ident.region, Pattern::Identifier(symbol)); + let loc_pattern = Loc::at(loc_name.region, Pattern::Identifier(symbol)); - PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, typed_ident.ann, ingested_file.path)) + PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, ingested_file.annotation.map(|ann| ann.annotation), ingested_file.path)) } Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"), } diff --git a/crates/compiler/fmt/src/def.rs b/crates/compiler/fmt/src/def.rs index 248d001b02..875aea0153 100644 --- a/crates/compiler/fmt/src/def.rs +++ b/crates/compiler/fmt/src/def.rs @@ -6,8 +6,8 @@ use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT} use crate::Buf; use roc_parse::ast::{ AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword, - ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, Spaces, StrLiteral, - TypeAnnotation, TypeDef, TypeHeader, ValueDef, + ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern, Spaces, + StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef, }; use roc_parse::header::Keyword; use roc_region::all::Loc; @@ -257,8 +257,9 @@ impl<'a> Formattable for IngestedFileImport<'a> { before_path, path: _, name, + annotation, } = self; - !before_path.is_empty() || name.is_multiline() + !before_path.is_empty() || name.keyword.is_multiline() || annotation.is_multiline() } fn format_with_options( @@ -272,6 +273,7 @@ impl<'a> Formattable for IngestedFileImport<'a> { before_path, path, name, + annotation, } = self; buf.indent(indent); @@ -281,7 +283,11 @@ impl<'a> Formattable for IngestedFileImport<'a> { fmt_default_spaces(buf, before_path, indent); fmt_str_literal(buf, path.value, indent); - name.format(buf, indent); + + name.keyword.format(buf, indent); + buf.push_str(name.item.value); + + annotation.format(buf, indent); } } @@ -361,6 +367,34 @@ impl Formattable for ImportExposingKeyword { } } +impl<'a> Formattable for IngestedFileAnnotation<'a> { + fn is_multiline(&self) -> bool { + let Self { + before_colon, + annotation, + } = self; + !before_colon.is_empty() || annotation.is_multiline() + } + + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + let Self { + before_colon, + annotation, + } = self; + + fmt_default_spaces(buf, before_colon, indent); + buf.push_str(":"); + buf.spaces(1); + annotation.format(buf, indent); + } +} + impl<'a> Formattable for ValueDef<'a> { fn is_multiline(&self) -> bool { use roc_parse::ast::ValueDef::*; diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index f1c5da15bc..20fe2a858e 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -5,9 +5,10 @@ use roc_parse::{ ast::{ AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr, Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias, - ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileImport, Module, - ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced, Spaces, StrLiteral, - StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, + ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, + IngestedFileImport, Module, ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced, + Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, + WhenBranch, }, header::{ AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName, @@ -600,6 +601,7 @@ impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> { before_path: &[], path: self.path.remove_spaces(arena), name: self.name.remove_spaces(arena), + annotation: self.annotation.remove_spaces(arena), } } } @@ -631,6 +633,15 @@ impl<'a> RemoveSpaces<'a> for ImportExposingKeyword { } } +impl<'a> RemoveSpaces<'a> for IngestedFileAnnotation<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + IngestedFileAnnotation { + before_colon: &[], + annotation: self.annotation.remove_spaces(arena), + } + } +} + impl<'a> RemoveSpaces<'a> for Implements<'a> { fn remove_spaces(&self, _arena: &'a Bump) -> Self { Implements::Implements diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 970c0e0231..b590c15673 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; +use crate::expr::merge_spaces; use crate::header::{ self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader, }; @@ -162,6 +163,8 @@ impl<'a> Module<'a> { Self::header_import_to_value_def(None, name, exposed, import.region) } header::ImportsEntry::IngestedFile(path, typed_ident) => { + let typed_ident = typed_ident.extract_spaces(); + ValueDef::IngestedFileImport(IngestedFileImport { before_path: &[], path: Loc { @@ -174,11 +177,16 @@ impl<'a> Module<'a> { item: ImportAsKeyword, after: &[], }, - item: Loc { - value: typed_ident, - region: import.region, - }, + item: typed_ident.item.ident, }, + annotation: Some(IngestedFileAnnotation { + before_colon: merge_spaces( + arena, + typed_ident.before, + typed_ident.item.spaces_before_colon, + ), + annotation: typed_ident.item.ann, + }), }) } }; @@ -1052,7 +1060,20 @@ pub struct ModuleImport<'a> { pub struct IngestedFileImport<'a> { pub before_path: &'a [CommentOrNewline<'a>], pub path: Loc>, - pub name: header::KeywordItem<'a, ImportAsKeyword, Loc>>>, + pub name: header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>, + pub annotation: Option>, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct IngestedFileAnnotation<'a> { + pub before_colon: &'a [CommentOrNewline<'a>], + pub annotation: Loc>, +} + +impl<'a> Malformed for IngestedFileAnnotation<'a> { + fn is_malformed(&self) -> bool { + self.annotation.value.is_malformed() + } } #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -2621,7 +2642,8 @@ impl<'a> Malformed for ValueDef<'a> { before_path: _, path, name: _, - }) => path.is_malformed(), + annotation, + }) => path.is_malformed() || annotation.is_malformed(), ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(), } } diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 645ec8bf5b..46d1463684 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -1,8 +1,8 @@ use crate::ast::{ is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword, - ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, RecordBuilderField, Spaceable, - Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef, + ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern, + RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef, }; use crate::blankspace::{ space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e, @@ -1050,17 +1050,15 @@ fn import_ingested_file_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> string_literal::parse_str_literal() )), name: import_ingested_file_as(), + annotation: optional(import_ingested_file_annotation()) }), ValueDef::IngestedFileImport ) } #[inline(always)] -fn import_ingested_file_as<'a>() -> impl Parser< - 'a, - header::KeywordItem<'a, ImportAsKeyword, Loc>>>, - EImport<'a>, -> { +fn import_ingested_file_as<'a>( +) -> impl Parser<'a, header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>, EImport<'a>> { record!(header::KeywordItem { keyword: module::spaces_around_keyword( ImportAsKeyword, @@ -1068,7 +1066,22 @@ fn import_ingested_file_as<'a>() -> impl Parser< EImport::IndentAs, EImport::IndentIngestedName ), - item: specialize_err(EImport::IngestedName, loc!(module::typed_ident())) + item: specialize_err( + |(), pos| EImport::IngestedName(pos), + loc!(lowercase_ident()) + ) + }) +} + +#[inline(always)] +fn import_ingested_file_annotation<'a>() -> impl Parser<'a, IngestedFileAnnotation<'a>, EImport<'a>> +{ + record!(IngestedFileAnnotation { + before_colon: skip_second!( + backtrackable(space0_e(EImport::IndentColon)), + byte(b':', EImport::Colon) + ), + annotation: specialize_err(EImport::Annotation, type_annotation::located(false)) }) } diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 386d9c8def..8060297054 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -542,7 +542,11 @@ pub enum EImport<'a> { IndentIngestedPath(Position), IngestedPath(Position), IndentIngestedName(Position), - IngestedName(ETypedIdent<'a>, Position), + IngestedName(Position), + IndentColon(Position), + Colon(Position), + IndentAnnotation(Position), + Annotation(EType<'a>, Position), Space(BadInputError, Position), } diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast index 0c782b5546..9546416114 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast @@ -32,16 +32,18 @@ Defs { item: ImportAsKeyword, after: [], }, - item: @29-39 TypedIdent { - ident: @29-33 "file", - spaces_before_colon: [], - ann: @36-39 Apply( + item: @29-33 "file", + }, + annotation: Some( + IngestedFileAnnotation { + before_colon: [], + annotation: @36-39 Apply( "", "Str", [], ), }, - }, + ), }, ), IngestedFileImport( @@ -56,10 +58,12 @@ Defs { item: ImportAsKeyword, after: [], }, - item: @70-85 TypedIdent { - ident: @70-74 "file", - spaces_before_colon: [], - ann: @78-85 Apply( + item: @70-74 "file", + }, + annotation: Some( + IngestedFileAnnotation { + before_colon: [], + annotation: @78-85 Apply( "", "List", [ @@ -71,7 +75,7 @@ Defs { ], ), }, - }, + ), }, ), ], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast index 3b0eb5f947..e02796c6bc 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast @@ -27,16 +27,18 @@ Defs( item: ImportAsKeyword, after: [], }, - item: @23-33 TypedIdent { - ident: @23-27 "data", - spaces_before_colon: [], - ann: @30-33 Apply( + item: @23-27 "data", + }, + annotation: Some( + IngestedFileAnnotation { + before_colon: [], + annotation: @30-33 Apply( "", "Str", [], ), }, - }, + ), }, ), ], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast new file mode 100644 index 0000000000..92d85e3979 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast @@ -0,0 +1,56 @@ +Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @0-27, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + IngestedFileImport( + IngestedFileImport { + before_path: [], + path: @7-19 PlainLine( + "users.json", + ), + name: KeywordItem { + keyword: Spaces { + before: [], + item: ImportAsKeyword, + after: [], + }, + item: @23-27 "data", + }, + annotation: None, + }, + ), + ], + }, + @29-43 SpaceBefore( + Apply( + @29-38 Var { + module_name: "", + ident: "parseJson", + }, + [ + @39-43 Var { + module_name: "", + ident: "data", + }, + ], + Space, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.roc new file mode 100644 index 0000000000..c39f5b8ce5 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.roc @@ -0,0 +1,3 @@ +import "users.json" as data + +parseJson data diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs index 0b27992457..a04782763d 100644 --- a/crates/compiler/test_syntax/tests/test_snapshots.rs +++ b/crates/compiler/test_syntax/tests/test_snapshots.rs @@ -302,9 +302,9 @@ mod test_snapshots { pass/destructure_tag_assignment.expr, pass/docs.expr, pass/empty_app_header.header, - pass/empty_module_header.header, pass/empty_hosted_header.header, pass/empty_list.expr, + pass/empty_module_header.header, pass/empty_package_header.header, pass/empty_platform_header.header, pass/empty_record.expr, @@ -333,6 +333,7 @@ mod test_snapshots { pass/ingested_file.moduledefs, pass/inline_import.expr, pass/inline_ingested_file.expr, + pass/inline_ingested_file_no_ann.expr, pass/int_with_underscore.expr, pass/lambda_in_chain.expr, pass/lambda_indent.expr, @@ -390,6 +391,7 @@ mod test_snapshots { pass/not_multiline_string.expr, pass/number_literal_suffixes.expr, pass/old_app_header.full, + pass/old_interface_header.header, pass/one_backpassing.expr, pass/one_char_string.expr, pass/one_def.expr, @@ -410,7 +412,6 @@ mod test_snapshots { pass/outdented_colon_in_record.expr, pass/outdented_list.expr, pass/outdented_record.expr, - pass/old_interface_header.header, pass/packed_singleton_list.expr, pass/parens_in_type_def_apply.expr, pass/parens_in_value_def_annotation.expr, diff --git a/examples/cli/ingested-file-bytes-no-ann.roc b/examples/cli/ingested-file-bytes-no-ann.roc new file mode 100644 index 0000000000..57836bfac8 --- /dev/null +++ b/examples/cli/ingested-file-bytes-no-ann.roc @@ -0,0 +1,11 @@ +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" } + +import pf.Stdout +import "../../LICENSE" as license + +main = + license + |> List.map Num.toU64 + |> List.sum + |> Num.toStr + |> Stdout.line!