mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 22:09:09 +00:00
Support '??' syntax for optional record fields
This commit is contained in:
parent
3d4dd5b583
commit
a24fe430b4
21 changed files with 42 additions and 43 deletions
|
@ -871,7 +871,7 @@ mapWithIndexHelp = \src, dest, func, index, length ->
|
||||||
## All of these options are compatible with the others. For example, you can use `At` or `After`
|
## All of these options are compatible with the others. For example, you can use `At` or `After`
|
||||||
## with `start` regardless of what `end` and `step` are set to.
|
## with `start` regardless of what `end` and `step` are set to.
|
||||||
range : _
|
range : _
|
||||||
range = \{ start, end, step ? 0 } ->
|
range = \{ start, end, step ?? 0 } ->
|
||||||
{ calcNext, stepIsPositive } =
|
{ calcNext, stepIsPositive } =
|
||||||
if step == 0 then
|
if step == 0 then
|
||||||
when T start end is
|
when T start end is
|
||||||
|
|
|
@ -618,8 +618,8 @@ isGte : Num a, Num a -> Bool
|
||||||
##
|
##
|
||||||
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
||||||
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
||||||
isApproxEq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool
|
isApproxEq : Frac a, Frac a, { rtol ?? Frac a, atol ?? Frac a } -> Bool
|
||||||
isApproxEq = \x, y, { rtol ? 0.00001, atol ? 0.00000001 } ->
|
isApproxEq = \x, y, { rtol ?? 0.00001, atol ?? 0.00000001 } ->
|
||||||
eq = x <= y && x >= y
|
eq = x <= y && x >= y
|
||||||
meetsTolerance = Num.absDiff x y <= Num.max atol (rtol * Num.max (Num.abs x) (Num.abs y))
|
meetsTolerance = Num.absDiff x y <= Num.max atol (rtol * Num.max (Num.abs x) (Num.abs y))
|
||||||
eq || meetsTolerance
|
eq || meetsTolerance
|
||||||
|
|
|
@ -386,7 +386,7 @@ impl<'a> Nodify<'a> for AssignedField<'a, TypeAnnotation<'a>> {
|
||||||
assigned_field_value_to_node(n.into_bump_str(), arena, sp, &value.value, ":", flags)
|
assigned_field_value_to_node(n.into_bump_str(), arena, sp, &value.value, ":", flags)
|
||||||
}
|
}
|
||||||
AssignedField::OptionalValue(name, sp, value) => {
|
AssignedField::OptionalValue(name, sp, value) => {
|
||||||
assigned_field_value_to_node(name.value, arena, sp, &value.value, "?", flags)
|
assigned_field_value_to_node(name.value, arena, sp, &value.value, "??", flags)
|
||||||
}
|
}
|
||||||
AssignedField::LabelOnly(name) => NodeInfo {
|
AssignedField::LabelOnly(name) => NodeInfo {
|
||||||
before: &[],
|
before: &[],
|
||||||
|
@ -512,7 +512,7 @@ fn format_assigned_field_help<T>(
|
||||||
|
|
||||||
buf.spaces(separator_spaces);
|
buf.spaces(separator_spaces);
|
||||||
buf.indent(indent);
|
buf.indent(indent);
|
||||||
buf.push('?');
|
buf.push_str("??");
|
||||||
buf.spaces(1);
|
buf.spaces(1);
|
||||||
ann.value.format(buf, indent);
|
ann.value.format(buf, indent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -701,16 +701,8 @@ fn fmt_apply(
|
||||||
if !expr.before.is_empty() {
|
if !expr.before.is_empty() {
|
||||||
format_spaces(buf, expr.before, Newlines::Yes, indent);
|
format_spaces(buf, expr.before, Newlines::Yes, indent);
|
||||||
}
|
}
|
||||||
expr.item.format_with_options(
|
expr.item
|
||||||
buf,
|
.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
|
||||||
if use_commas_and_parens {
|
|
||||||
Parens::NotNeeded
|
|
||||||
} else {
|
|
||||||
Parens::InApply
|
|
||||||
},
|
|
||||||
Newlines::Yes,
|
|
||||||
indent,
|
|
||||||
);
|
|
||||||
|
|
||||||
if use_commas_and_parens {
|
if use_commas_and_parens {
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
|
|
|
@ -268,7 +268,7 @@ fn fmt_pattern_only(
|
||||||
Pattern::OptionalField(name, loc_pattern) => {
|
Pattern::OptionalField(name, loc_pattern) => {
|
||||||
buf.indent(indent);
|
buf.indent(indent);
|
||||||
snakify_camel_ident(buf, name);
|
snakify_camel_ident(buf, name);
|
||||||
buf.push_str(" ?");
|
buf.push_str(" ??");
|
||||||
buf.spaces(1);
|
buf.spaces(1);
|
||||||
loc_pattern.format(buf, indent);
|
loc_pattern.format(buf, indent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4538,8 +4538,8 @@ mod test_reporting {
|
||||||
4│ f : { foo bar }
|
4│ f : { foo bar }
|
||||||
^
|
^
|
||||||
|
|
||||||
I was expecting to see a colon, question mark, comma or closing curly
|
I was expecting to see a colon, two question marks (??), comma or
|
||||||
brace.
|
closing curly brace.
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2082,6 +2082,7 @@ pub fn merge_spaces<'a>(
|
||||||
/// If the given Expr would parse the same way as a valid Pattern, convert it.
|
/// If the given Expr would parse the same way as a valid Pattern, convert it.
|
||||||
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
|
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
|
||||||
fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, ()> {
|
fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, ()> {
|
||||||
|
println!("expr_to_pattern_help");
|
||||||
let mut expr = expr.extract_spaces();
|
let mut expr = expr.extract_spaces();
|
||||||
|
|
||||||
while let Expr::ParensAround(loc_expr) = &expr.item {
|
while let Expr::ParensAround(loc_expr) = &expr.item {
|
||||||
|
@ -3587,7 +3588,10 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
|
||||||
optional(either(
|
optional(either(
|
||||||
and(byte(b':', ERecord::Colon), record_field_expr()),
|
and(byte(b':', ERecord::Colon), record_field_expr()),
|
||||||
and(
|
and(
|
||||||
byte(b'?', ERecord::QuestionMark),
|
and(
|
||||||
|
byte(b'?', ERecord::QuestionMark),
|
||||||
|
optional(byte(b'?', ERecord::QuestionMark)),
|
||||||
|
),
|
||||||
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(true))),
|
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(true))),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::blankspace::{space0_before_optional_after, space0_e, spaces, spaces_b
|
||||||
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
|
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
|
||||||
use crate::keyword;
|
use crate::keyword;
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
self, backtrackable, byte, collection_trailing_sep_e, fail_when, loc, map, map_with_arena,
|
self, and, backtrackable, byte, collection_trailing_sep_e, fail_when, loc, map, map_with_arena,
|
||||||
optional, skip_first, skip_second, specialize_err, specialize_err_ref, then, three_bytes,
|
optional, skip_first, skip_second, specialize_err, specialize_err_ref, then, three_bytes,
|
||||||
two_bytes, zero_or_more, EPattern, PInParens, PList, PRecord, Parser,
|
two_bytes, zero_or_more, EPattern, PInParens, PList, PRecord, Parser,
|
||||||
};
|
};
|
||||||
|
@ -551,7 +551,10 @@ fn record_pattern_field<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, PRecord<'a>>
|
||||||
// (This is true in both literals and types.)
|
// (This is true in both literals and types.)
|
||||||
let (_, opt_loc_val, state) = optional(either(
|
let (_, opt_loc_val, state) = optional(either(
|
||||||
byte(b':', PRecord::Colon),
|
byte(b':', PRecord::Colon),
|
||||||
byte(b'?', PRecord::Optional),
|
and(
|
||||||
|
byte(b'?', PRecord::Optional),
|
||||||
|
optional(byte(b'?', PRecord::Optional)),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.parse(arena, state, min_indent)?;
|
.parse(arena, state, min_indent)?;
|
||||||
|
|
||||||
|
|
|
@ -588,7 +588,10 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'
|
||||||
// (This is true in both literals and types.)
|
// (This is true in both literals and types.)
|
||||||
let (_, opt_loc_val, state) = optional(either(
|
let (_, opt_loc_val, state) = optional(either(
|
||||||
byte(b':', ETypeRecord::Colon),
|
byte(b':', ETypeRecord::Colon),
|
||||||
byte(b'?', ETypeRecord::Optional),
|
and(
|
||||||
|
byte(b'?', ETypeRecord::Optional),
|
||||||
|
optional(byte(b'?', ETypeRecord::Optional)),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.parse(arena, state, min_indent)?;
|
.parse(arena, state, min_indent)?;
|
||||||
|
|
||||||
|
|
|
@ -102,10 +102,7 @@ fn round_trip_once(input: Input<'_>, options: Options) -> Option<String> {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
|
||||||
let actual = match input.parse_in(&arena) {
|
let actual = match input.parse_in(&arena) {
|
||||||
Ok(a) => {
|
Ok(a) => a,
|
||||||
println!("actual {a:#?}");
|
|
||||||
a
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if options.minimize_initial_parse_error {
|
if options.minimize_initial_parse_error {
|
||||||
return Some(format!("Initial parse failed: {:?}", e.normalize(&arena)));
|
return Some(format!("Initial parse failed: {:?}", e.normalize(&arena)));
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
\I { p ? Y
|
\I { p ?? Y
|
||||||
Y } [] ->
|
Y } [] ->
|
||||||
K # (
|
K # (
|
|
@ -1,5 +1,5 @@
|
||||||
O
|
O
|
||||||
{ p ? if
|
{ p ?? if
|
||||||
a
|
a
|
||||||
then
|
then
|
||||||
A
|
A
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
module { x, y ? 0 } -> [menu]
|
module { x, y ?? 0 } -> [menu]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
l?
|
l??
|
||||||
"""
|
"""
|
||||||
""",
|
""",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
i? p,
|
i?? p,
|
||||||
}
|
}
|
|
@ -1,2 +1,2 @@
|
||||||
{ i ? Y } = p
|
{ i ?? Y } = p
|
||||||
Q
|
Q
|
|
@ -1,5 +1,5 @@
|
||||||
0 : {
|
0 : {
|
||||||
i
|
i
|
||||||
? d,
|
?? d,
|
||||||
}
|
}
|
||||||
O
|
O
|
|
@ -1,4 +1,4 @@
|
||||||
{ e ? f
|
{ e ?? f
|
||||||
4 } = f
|
4 } = f
|
||||||
e
|
e
|
||||||
r
|
r
|
|
@ -1,8 +1,8 @@
|
||||||
M
|
M
|
||||||
{ s ? s
|
{ s ?? s
|
||||||
{ J &
|
{ J &
|
||||||
} }
|
} }
|
||||||
{ s ? s
|
{ s ?? s
|
||||||
{ J &
|
{ J &
|
||||||
} } : p
|
} } : p
|
||||||
y
|
y
|
|
@ -2497,13 +2497,13 @@ mod test_fmt {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
f : { a ?Str }
|
f : { a ??Str }
|
||||||
|
|
||||||
f"
|
f"
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
f : { a ? Str }
|
f : { a ?? Str }
|
||||||
|
|
||||||
f"
|
f"
|
||||||
),
|
),
|
||||||
|
@ -2513,7 +2513,7 @@ mod test_fmt {
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
f : {
|
f : {
|
||||||
a ?Str,
|
a ??Str,
|
||||||
}
|
}
|
||||||
|
|
||||||
f"
|
f"
|
||||||
|
@ -2521,7 +2521,7 @@ mod test_fmt {
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
f : {
|
f : {
|
||||||
a ? Str,
|
a ?? Str,
|
||||||
}
|
}
|
||||||
|
|
||||||
f"
|
f"
|
||||||
|
@ -2743,7 +2743,7 @@ mod test_fmt {
|
||||||
r"
|
r"
|
||||||
f :
|
f :
|
||||||
{
|
{
|
||||||
someField ? Int * # comment 1
|
someField ?? Int * # comment 1
|
||||||
,
|
,
|
||||||
# comment 2
|
# comment 2
|
||||||
}
|
}
|
||||||
|
@ -2753,7 +2753,7 @@ mod test_fmt {
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
f : {
|
f : {
|
||||||
some_field ? Int *, # comment 1
|
some_field ?? Int *, # comment 1
|
||||||
# comment 2
|
# comment 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2889,7 +2889,7 @@ fn to_trecord_report<'a>(
|
||||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||||
alloc.concat([
|
alloc.concat([
|
||||||
alloc.reflow(
|
alloc.reflow(
|
||||||
r"I was expecting to see a colon, question mark, comma or closing curly brace.",
|
r"I was expecting to see a colon, two question marks (??), comma or closing curly brace.",
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue