Merge pull request #6717 from roc-lang/optional-ingested-ann

Optional annotation in ingested file imports
This commit is contained in:
Ayaz 2024-05-07 21:18:12 -05:00 committed by GitHub
commit cc1bc68eb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 238 additions and 56 deletions

View file

@ -969,6 +969,21 @@ mod cli_run {
TestCliCommands::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] #[test]
#[serial(zig_platform_parser_package_basic_cli_url)] #[serial(zig_platform_parser_package_basic_cli_url)]

View file

@ -167,7 +167,7 @@ enum PendingValueDef<'a> {
/// Ingested file /// Ingested file
IngestedFile( IngestedFile(
Loc<Pattern>, Loc<Pattern>,
Loc<ast::TypeAnnotation<'a>>, Option<Loc<ast::TypeAnnotation<'a>>>,
Loc<ast::StrLiteral<'a>>, Loc<ast::StrLiteral<'a>>,
), ),
} }
@ -2336,7 +2336,7 @@ fn canonicalize_pending_value_def<'a>(
None, None,
) )
} }
IngestedFile(loc_pattern, loc_ann, path_literal) => { IngestedFile(loc_pattern, opt_loc_ann, path_literal) => {
let relative_path = let relative_path =
if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value { if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value {
ingested_path ingested_path
@ -2373,23 +2373,29 @@ fn canonicalize_pending_value_def<'a>(
let loc_expr = Loc::at(path_literal.region, expr); let loc_expr = Loc::at(path_literal.region, expr);
let can_ann = canonicalize_annotation( let opt_loc_can_ann = if let Some(loc_ann) = opt_loc_ann {
env, let can_ann = canonicalize_annotation(
scope, env,
&loc_ann.value, scope,
loc_ann.region, &loc_ann.value,
var_store, loc_ann.region,
pending_abilities_in_scope, var_store,
AnnotationFor::Value, 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( let def = single_can_def(
loc_pattern, loc_pattern,
loc_expr, loc_expr,
var_store.fresh(), var_store.fresh(),
Some(Loc::at(loc_ann.region, can_ann)), opt_loc_can_ann,
SendMap::default(), SendMap::default(),
); );
@ -3108,9 +3114,9 @@ fn to_pending_value_def<'a>(
}) })
} }
IngestedFileImport(ingested_file) => { 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, Ok(symbol ) => symbol,
Err((original, shadow, _)) => { Err((original, shadow, _)) => {
env.problem(Problem::Shadowing { 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"), Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
} }

View file

@ -6,8 +6,8 @@ use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT}
use crate::Buf; use crate::Buf;
use roc_parse::ast::{ use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword, AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, Spaces, StrLiteral, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern, Spaces,
TypeAnnotation, TypeDef, TypeHeader, ValueDef, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use roc_parse::header::Keyword; use roc_parse::header::Keyword;
use roc_region::all::Loc; use roc_region::all::Loc;
@ -257,8 +257,9 @@ impl<'a> Formattable for IngestedFileImport<'a> {
before_path, before_path,
path: _, path: _,
name, name,
annotation,
} = self; } = self;
!before_path.is_empty() || name.is_multiline() !before_path.is_empty() || name.keyword.is_multiline() || annotation.is_multiline()
} }
fn format_with_options( fn format_with_options(
@ -272,6 +273,7 @@ impl<'a> Formattable for IngestedFileImport<'a> {
before_path, before_path,
path, path,
name, name,
annotation,
} = self; } = self;
buf.indent(indent); buf.indent(indent);
@ -281,7 +283,11 @@ impl<'a> Formattable for IngestedFileImport<'a> {
fmt_default_spaces(buf, before_path, indent); fmt_default_spaces(buf, before_path, indent);
fmt_str_literal(buf, path.value, 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> { impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*; use roc_parse::ast::ValueDef::*;

View file

@ -5,9 +5,10 @@ use roc_parse::{
ast::{ ast::{
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr, AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias, Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileImport, Module, ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced, Spaces, StrLiteral, IngestedFileImport, Module, ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced,
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
WhenBranch,
}, },
header::{ header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName, AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
@ -600,6 +601,7 @@ impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
before_path: &[], before_path: &[],
path: self.path.remove_spaces(arena), path: self.path.remove_spaces(arena),
name: self.name.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> { impl<'a> RemoveSpaces<'a> for Implements<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self { fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Implements::Implements Implements::Implements

View file

@ -1,5 +1,6 @@
use std::fmt::Debug; use std::fmt::Debug;
use crate::expr::merge_spaces;
use crate::header::{ use crate::header::{
self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader, 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) Self::header_import_to_value_def(None, name, exposed, import.region)
} }
header::ImportsEntry::IngestedFile(path, typed_ident) => { header::ImportsEntry::IngestedFile(path, typed_ident) => {
let typed_ident = typed_ident.extract_spaces();
ValueDef::IngestedFileImport(IngestedFileImport { ValueDef::IngestedFileImport(IngestedFileImport {
before_path: &[], before_path: &[],
path: Loc { path: Loc {
@ -174,11 +177,16 @@ impl<'a> Module<'a> {
item: ImportAsKeyword, item: ImportAsKeyword,
after: &[], after: &[],
}, },
item: Loc { item: typed_ident.item.ident,
value: typed_ident,
region: import.region,
},
}, },
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 struct IngestedFileImport<'a> {
pub before_path: &'a [CommentOrNewline<'a>], pub before_path: &'a [CommentOrNewline<'a>],
pub path: Loc<StrLiteral<'a>>, pub path: Loc<StrLiteral<'a>>,
pub name: header::KeywordItem<'a, ImportAsKeyword, Loc<Spaced<'a, header::TypedIdent<'a>>>>, pub name: header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>,
pub annotation: Option<IngestedFileAnnotation<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IngestedFileAnnotation<'a> {
pub before_colon: &'a [CommentOrNewline<'a>],
pub annotation: Loc<TypeAnnotation<'a>>,
}
impl<'a> Malformed for IngestedFileAnnotation<'a> {
fn is_malformed(&self) -> bool {
self.annotation.value.is_malformed()
}
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -2621,7 +2642,8 @@ impl<'a> Malformed for ValueDef<'a> {
before_path: _, before_path: _,
path, path,
name: _, name: _,
}) => path.is_malformed(), annotation,
}) => path.is_malformed() || annotation.is_malformed(),
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(), ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
} }
} }

View file

@ -1,8 +1,8 @@
use crate::ast::{ use crate::ast::{
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword, Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileImport, ModuleImport, Pattern, RecordBuilderField, Spaceable, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern,
Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef, RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use crate::blankspace::{ use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e, 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() string_literal::parse_str_literal()
)), )),
name: import_ingested_file_as(), name: import_ingested_file_as(),
annotation: optional(import_ingested_file_annotation())
}), }),
ValueDef::IngestedFileImport ValueDef::IngestedFileImport
) )
} }
#[inline(always)] #[inline(always)]
fn import_ingested_file_as<'a>() -> impl Parser< fn import_ingested_file_as<'a>(
'a, ) -> impl Parser<'a, header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>, EImport<'a>> {
header::KeywordItem<'a, ImportAsKeyword, Loc<Spaced<'a, header::TypedIdent<'a>>>>,
EImport<'a>,
> {
record!(header::KeywordItem { record!(header::KeywordItem {
keyword: module::spaces_around_keyword( keyword: module::spaces_around_keyword(
ImportAsKeyword, ImportAsKeyword,
@ -1068,7 +1066,22 @@ fn import_ingested_file_as<'a>() -> impl Parser<
EImport::IndentAs, EImport::IndentAs,
EImport::IndentIngestedName 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))
}) })
} }

View file

@ -542,7 +542,11 @@ pub enum EImport<'a> {
IndentIngestedPath(Position), IndentIngestedPath(Position),
IngestedPath(Position), IngestedPath(Position),
IndentIngestedName(Position), IndentIngestedName(Position),
IngestedName(ETypedIdent<'a>, Position), IngestedName(Position),
IndentColon(Position),
Colon(Position),
IndentAnnotation(Position),
Annotation(EType<'a>, Position),
Space(BadInputError, Position), Space(BadInputError, Position),
} }

View file

@ -32,16 +32,18 @@ Defs {
item: ImportAsKeyword, item: ImportAsKeyword,
after: [], after: [],
}, },
item: @29-39 TypedIdent { item: @29-33 "file",
ident: @29-33 "file", },
spaces_before_colon: [], annotation: Some(
ann: @36-39 Apply( IngestedFileAnnotation {
before_colon: [],
annotation: @36-39 Apply(
"", "",
"Str", "Str",
[], [],
), ),
}, },
}, ),
}, },
), ),
IngestedFileImport( IngestedFileImport(
@ -56,10 +58,12 @@ Defs {
item: ImportAsKeyword, item: ImportAsKeyword,
after: [], after: [],
}, },
item: @70-85 TypedIdent { item: @70-74 "file",
ident: @70-74 "file", },
spaces_before_colon: [], annotation: Some(
ann: @78-85 Apply( IngestedFileAnnotation {
before_colon: [],
annotation: @78-85 Apply(
"", "",
"List", "List",
[ [
@ -71,7 +75,7 @@ Defs {
], ],
), ),
}, },
}, ),
}, },
), ),
], ],

View file

@ -27,16 +27,18 @@ Defs(
item: ImportAsKeyword, item: ImportAsKeyword,
after: [], after: [],
}, },
item: @23-33 TypedIdent { item: @23-27 "data",
ident: @23-27 "data", },
spaces_before_colon: [], annotation: Some(
ann: @30-33 Apply( IngestedFileAnnotation {
before_colon: [],
annotation: @30-33 Apply(
"", "",
"Str", "Str",
[], [],
), ),
}, },
}, ),
}, },
), ),
], ],

View file

@ -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,
],
),
)

View file

@ -0,0 +1,3 @@
import "users.json" as data
parseJson data

View file

@ -302,9 +302,9 @@ mod test_snapshots {
pass/destructure_tag_assignment.expr, pass/destructure_tag_assignment.expr,
pass/docs.expr, pass/docs.expr,
pass/empty_app_header.header, pass/empty_app_header.header,
pass/empty_module_header.header,
pass/empty_hosted_header.header, pass/empty_hosted_header.header,
pass/empty_list.expr, pass/empty_list.expr,
pass/empty_module_header.header,
pass/empty_package_header.header, pass/empty_package_header.header,
pass/empty_platform_header.header, pass/empty_platform_header.header,
pass/empty_record.expr, pass/empty_record.expr,
@ -333,6 +333,7 @@ mod test_snapshots {
pass/ingested_file.moduledefs, pass/ingested_file.moduledefs,
pass/inline_import.expr, pass/inline_import.expr,
pass/inline_ingested_file.expr, pass/inline_ingested_file.expr,
pass/inline_ingested_file_no_ann.expr,
pass/int_with_underscore.expr, pass/int_with_underscore.expr,
pass/lambda_in_chain.expr, pass/lambda_in_chain.expr,
pass/lambda_indent.expr, pass/lambda_indent.expr,
@ -390,6 +391,7 @@ mod test_snapshots {
pass/not_multiline_string.expr, pass/not_multiline_string.expr,
pass/number_literal_suffixes.expr, pass/number_literal_suffixes.expr,
pass/old_app_header.full, pass/old_app_header.full,
pass/old_interface_header.header,
pass/one_backpassing.expr, pass/one_backpassing.expr,
pass/one_char_string.expr, pass/one_char_string.expr,
pass/one_def.expr, pass/one_def.expr,
@ -410,7 +412,6 @@ mod test_snapshots {
pass/outdented_colon_in_record.expr, pass/outdented_colon_in_record.expr,
pass/outdented_list.expr, pass/outdented_list.expr,
pass/outdented_record.expr, pass/outdented_record.expr,
pass/old_interface_header.header,
pass/packed_singleton_list.expr, pass/packed_singleton_list.expr,
pass/parens_in_type_def_apply.expr, pass/parens_in_type_def_apply.expr,
pass/parens_in_value_def_annotation.expr, pass/parens_in_value_def_annotation.expr,

View file

@ -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!