diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index f74e39ceabc..efba31324d3 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -874,7 +874,7 @@ map_with_index_help = \src, dest, func, index, length -> ## 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. range : _ -range = \{ start, end, step ? 0 } -> +range = \{ start, end, step ?? 0 } -> { calc_next, step_is_positive } = if step == 0 then when T(start, end) is diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 906dc43207e..9c237839363 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -617,8 +617,8 @@ is_gte : Num a, Num a -> Bool ## ## If either argument is [*NaN*](Num.is_nan), returns `Bool.false` no matter what. (*NaN* ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -is_approx_eq : Frac a, Frac a, { rtol ? Frac a, atol ? Frac a } -> Bool -is_approx_eq = \x, y, { rtol ? 0.00001, atol ? 0.00000001 } -> +is_approx_eq : Frac a, Frac a, { rtol ?? Frac a, atol ?? Frac a } -> Bool +is_approx_eq = \x, y, { rtol ?? 0.00001, atol ?? 0.00000001 } -> eq = x <= y && x >= y meets_tolerance = Num.abs_diff(x, y) <= Num.max(atol, (rtol * Num.max(Num.abs(x), Num.abs(y)))) eq || meets_tolerance diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index eff2c010307..45d3ee2ee82 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -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) } 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 { before: &[], @@ -512,7 +512,7 @@ fn format_assigned_field_help( buf.spaces(separator_spaces); buf.indent(indent); - buf.push('?'); + buf.push_str("??"); buf.spaces(1); ann.value.format(buf, indent); } diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 1dd9427f08a..149ec457064 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -701,16 +701,8 @@ fn fmt_apply( if !expr.before.is_empty() { format_spaces(buf, expr.before, Newlines::Yes, indent); } - expr.item.format_with_options( - buf, - if use_commas_and_parens { - Parens::NotNeeded - } else { - Parens::InApply - }, - Newlines::Yes, - indent, - ); + expr.item + .format_with_options(buf, Parens::InApply, Newlines::Yes, indent); if use_commas_and_parens { buf.push('('); diff --git a/crates/compiler/fmt/src/pattern.rs b/crates/compiler/fmt/src/pattern.rs index b256e47f918..ef65e277417 100644 --- a/crates/compiler/fmt/src/pattern.rs +++ b/crates/compiler/fmt/src/pattern.rs @@ -268,7 +268,7 @@ fn fmt_pattern_only( Pattern::OptionalField(name, loc_pattern) => { buf.indent(indent); snakify_camel_ident(buf, name); - buf.push_str(" ?"); + buf.push_str(" ??"); buf.spaces(1); loc_pattern.format(buf, indent); } diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 8adf461aa0d..f4dd519ade9 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -4541,8 +4541,8 @@ mod test_reporting { 4│ f : { foo bar } ^ - I was expecting to see a colon, question mark, comma or closing curly - brace. + I was expecting to see a colon, two question marks (??), comma or + closing curly brace. " ); diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 715520f39b8..cc076385a82 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -3587,7 +3587,10 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> { optional(either( and(byte(b':', ERecord::Colon), record_field_expr()), and( - byte(b'?', ERecord::QuestionMark), + and( + byte(b'?', ERecord::QuestionMark), + optional(byte(b'?', ERecord::SecondQuestionMark)), + ), spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(true))), ), )), diff --git a/crates/compiler/parse/src/normalize.rs b/crates/compiler/parse/src/normalize.rs index 6ab1abad0ec..857adb01aeb 100644 --- a/crates/compiler/parse/src/normalize.rs +++ b/crates/compiler/parse/src/normalize.rs @@ -1219,6 +1219,7 @@ impl<'a> Normalize<'a> for ERecord<'a> { ERecord::UnderscoreField(_pos) => ERecord::Field(Position::zero()), ERecord::Colon(_) => ERecord::Colon(Position::zero()), ERecord::QuestionMark(_) => ERecord::QuestionMark(Position::zero()), + ERecord::SecondQuestionMark(_) => ERecord::SecondQuestionMark(Position::zero()), ERecord::Arrow(_) => ERecord::Arrow(Position::zero()), ERecord::Ampersand(_) => ERecord::Ampersand(Position::zero()), ERecord::Expr(inner_err, _) => { @@ -1393,6 +1394,9 @@ impl<'a> Normalize<'a> for ETypeAbilityImpl<'a> { ETypeAbilityImpl::Space(*inner_err, Position::zero()) } ETypeAbilityImpl::QuestionMark(_) => ETypeAbilityImpl::QuestionMark(Position::zero()), + ETypeAbilityImpl::SecondQuestionMark(_) => { + ETypeAbilityImpl::SecondQuestionMark(Position::zero()) + } ETypeAbilityImpl::Ampersand(_) => ETypeAbilityImpl::Ampersand(Position::zero()), ETypeAbilityImpl::Expr(inner_err, _) => { ETypeAbilityImpl::Expr(arena.alloc(inner_err.normalize(arena)), Position::zero()) @@ -1471,7 +1475,8 @@ impl<'a> Normalize<'a> for ETypeRecord<'a> { ETypeRecord::Open(_) => ETypeRecord::Open(Position::zero()), ETypeRecord::Field(_) => ETypeRecord::Field(Position::zero()), ETypeRecord::Colon(_) => ETypeRecord::Colon(Position::zero()), - ETypeRecord::Optional(_) => ETypeRecord::Optional(Position::zero()), + ETypeRecord::OptionalFirst(_) => ETypeRecord::OptionalFirst(Position::zero()), + ETypeRecord::OptionalSecond(_) => ETypeRecord::OptionalSecond(Position::zero()), ETypeRecord::Type(inner_err, _) => { ETypeRecord::Type(arena.alloc(inner_err.normalize(arena)), Position::zero()) } @@ -1491,7 +1496,8 @@ impl<'a> Normalize<'a> for PRecord<'a> { PRecord::Open(_) => PRecord::Open(Position::zero()), PRecord::Field(_) => PRecord::Field(Position::zero()), PRecord::Colon(_) => PRecord::Colon(Position::zero()), - PRecord::Optional(_) => PRecord::Optional(Position::zero()), + PRecord::OptionalFirst(_) => PRecord::OptionalFirst(Position::zero()), + PRecord::OptionalSecond(_) => PRecord::OptionalSecond(Position::zero()), PRecord::Pattern(inner_err, _) => { PRecord::Pattern(arena.alloc(inner_err.normalize(arena)), Position::zero()) } diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index acc9b53b1a5..500be5bc846 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -683,6 +683,7 @@ pub enum ERecord<'a> { UnderscoreField(Position), Colon(Position), QuestionMark(Position), + SecondQuestionMark(Position), Arrow(Position), Ampersand(Position), @@ -706,6 +707,7 @@ impl<'a> ERecord<'a> { | ERecord::UnderscoreField(p) | ERecord::Colon(p) | ERecord::QuestionMark(p) + | ERecord::SecondQuestionMark(p) | ERecord::Arrow(p) | ERecord::Ampersand(p) | ERecord::Space(_, p) => Region::from_pos(*p), @@ -1107,7 +1109,8 @@ pub enum PRecord<'a> { Field(Position), Colon(Position), - Optional(Position), + OptionalFirst(Position), + OptionalSecond(Position), Pattern(&'a EPattern<'a>, Position), Expr(&'a EExpr<'a>, Position), @@ -1127,7 +1130,8 @@ impl<'a> PRecord<'a> { | PRecord::Open(p) | PRecord::Field(p) | PRecord::Colon(p) - | PRecord::Optional(p) + | PRecord::OptionalFirst(p) + | PRecord::OptionalSecond(p) | PRecord::Space(_, p) => Region::from_pos(*p), } } @@ -1244,7 +1248,8 @@ pub enum ETypeRecord<'a> { Field(Position), Colon(Position), - Optional(Position), + OptionalFirst(Position), + OptionalSecond(Position), Type(&'a EType<'a>, Position), Space(BadInputError, Position), @@ -1266,7 +1271,8 @@ impl<'a> ETypeRecord<'a> { | ETypeRecord::Open(p) | ETypeRecord::Field(p) | ETypeRecord::Colon(p) - | ETypeRecord::Optional(p) + | ETypeRecord::OptionalFirst(p) + | ETypeRecord::OptionalSecond(p) | ETypeRecord::Space(_, p) | ETypeRecord::IndentOpen(p) | ETypeRecord::IndentColon(p) @@ -1394,6 +1400,7 @@ pub enum ETypeAbilityImpl<'a> { Prefix(Position), QuestionMark(Position), + SecondQuestionMark(Position), Ampersand(Position), Expr(&'a EExpr<'a>, Position), IndentBar(Position), @@ -1416,6 +1423,7 @@ impl<'a> ETypeAbilityImpl<'a> { | ETypeAbilityImpl::Space(_, p) | ETypeAbilityImpl::Prefix(p) | ETypeAbilityImpl::QuestionMark(p) + | ETypeAbilityImpl::SecondQuestionMark(p) | ETypeAbilityImpl::Ampersand(p) | ETypeAbilityImpl::IndentBar(p) | ETypeAbilityImpl::IndentAmpersand(p) => Region::from_pos(*p), @@ -1435,6 +1443,7 @@ impl<'a> From> for ETypeAbilityImpl<'a> { ERecord::Space(s, p) => ETypeAbilityImpl::Space(s, p), ERecord::Prefix(p) => ETypeAbilityImpl::Prefix(p), ERecord::QuestionMark(p) => ETypeAbilityImpl::QuestionMark(p), + ERecord::SecondQuestionMark(p) => ETypeAbilityImpl::SecondQuestionMark(p), ERecord::Ampersand(p) => ETypeAbilityImpl::Ampersand(p), ERecord::Expr(e, p) => ETypeAbilityImpl::Expr(e, p), } diff --git a/crates/compiler/parse/src/pattern.rs b/crates/compiler/parse/src/pattern.rs index 5d590038dd9..e94ac3cf124 100644 --- a/crates/compiler/parse/src/pattern.rs +++ b/crates/compiler/parse/src/pattern.rs @@ -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::keyword; 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, two_bytes, zero_or_more, EPattern, PInParens, PList, PRecord, Parser, }; @@ -551,7 +551,10 @@ fn record_pattern_field<'a>() -> impl Parser<'a, Loc>, PRecord<'a>> // (This is true in both literals and types.) let (_, opt_loc_val, state) = optional(either( byte(b':', PRecord::Colon), - byte(b'?', PRecord::Optional), + and( + byte(b'?', PRecord::OptionalFirst), + optional(byte(b'?', PRecord::OptionalSecond)), + ), )) .parse(arena, state, min_indent)?; diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index ead88c358dd..59deeb859d7 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -588,7 +588,10 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<' // (This is true in both literals and types.) let (_, opt_loc_val, state) = optional(either( byte(b':', ETypeRecord::Colon), - byte(b'?', ETypeRecord::Optional), + and( + byte(b'?', ETypeRecord::OptionalFirst), + optional(byte(b'?', ETypeRecord::OptionalSecond)), + ), )) .parse(arena, state, min_indent)?; diff --git a/crates/compiler/test_syntax/src/minimize.rs b/crates/compiler/test_syntax/src/minimize.rs index 6bbe2fd602b..19b7e16ceed 100644 --- a/crates/compiler/test_syntax/src/minimize.rs +++ b/crates/compiler/test_syntax/src/minimize.rs @@ -102,10 +102,7 @@ fn round_trip_once(input: Input<'_>, options: Options) -> Option { let arena = Bump::new(); let actual = match input.parse_in(&arena) { - Ok(a) => { - println!("actual {a:#?}"); - a - } + Ok(a) => a, Err(e) => { if options.minimize_initial_parse_error { return Some(format!("Initial parse failed: {:?}", e.normalize(&arena))); diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_complex_pattern_indent_issue.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/closure_complex_pattern_indent_issue.expr.formatted.roc index 4a4be5405e0..540a97f970a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/closure_complex_pattern_indent_issue.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_complex_pattern_indent_issue.expr.formatted.roc @@ -1,3 +1,3 @@ -\I { p ? Y +\I { p ?? Y Y } [] -> K # ( \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/if_in_record_field_opt_pat.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/if_in_record_field_opt_pat.expr.formatted.roc index a5817a11d52..81fc55f32e7 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/if_in_record_field_opt_pat.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/if_in_record_field_opt_pat.expr.formatted.roc @@ -1,5 +1,5 @@ O - { p ? if + { p ?? if a then A diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/module_with_optional_param.header.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/module_with_optional_param.header.formatted.roc index d3d39f28a6f..e9f8a6bc20e 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/module_with_optional_param.header.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/module_with_optional_param.header.formatted.roc @@ -1 +1 @@ -module { x, y ? 0 } -> [menu] +module { x, y ?? 0 } -> [menu] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_opt_field.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_opt_field.expr.formatted.roc index 0e7ecd88b96..652cd0cd72c 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_opt_field.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_opt_field.expr.formatted.roc @@ -1,5 +1,5 @@ { - l? + l?? """ """, } diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_opt_field.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_opt_field.expr.formatted.roc index ddca639ed19..cd981a8c14b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_opt_field.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_opt_field.expr.formatted.roc @@ -1,3 +1,3 @@ { - i? p, + i?? p, } \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_pat.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_pat.expr.formatted.roc index 45bbf90e78a..e891f92d1a7 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_pat.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_pat.expr.formatted.roc @@ -1,2 +1,2 @@ -{ i ? Y } = p +{ i ?? Y } = p Q \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_ty.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_ty.expr.formatted.roc index 058df007df3..d2325d77874 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_ty.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opt_field_newline_in_ty.expr.formatted.roc @@ -1,5 +1,5 @@ 0 : { i - ? d, + ?? d, } O \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opt_record_field_pat_assign.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opt_record_field_pat_assign.expr.formatted.roc index fb811c1ffa8..017dc723a0b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opt_record_field_pat_assign.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opt_record_field_pat_assign.expr.formatted.roc @@ -1,4 +1,4 @@ -{ e ? f +{ e ?? f 4 } = f e r \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_opt_field_bonanza.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_opt_field_bonanza.expr.formatted.roc index 881835923f7..da39c04a1a0 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_opt_field_bonanza.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_opt_field_bonanza.expr.formatted.roc @@ -1,8 +1,8 @@ M - { s ? s + { s ?? s { J & } } - { s ? s + { s ?? s { J & } } : p y \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/test_fmt.rs b/crates/compiler/test_syntax/tests/test_fmt.rs index 0abef3660d8..767939ccbd4 100644 --- a/crates/compiler/test_syntax/tests/test_fmt.rs +++ b/crates/compiler/test_syntax/tests/test_fmt.rs @@ -2497,13 +2497,13 @@ mod test_fmt { expr_formats_to( indoc!( r" - f : { a ?Str } + f : { a ??Str } f" ), indoc!( r" - f : { a ? Str } + f : { a ?? Str } f" ), @@ -2513,7 +2513,7 @@ mod test_fmt { indoc!( r" f : { - a ?Str, + a ??Str, } f" @@ -2521,7 +2521,7 @@ mod test_fmt { indoc!( r" f : { - a ? Str, + a ?? Str, } f" @@ -2743,7 +2743,7 @@ mod test_fmt { r" f : { - someField ? Int * # comment 1 + someField ?? Int * # comment 1 , # comment 2 } @@ -2753,7 +2753,7 @@ mod test_fmt { indoc!( r" f : { - some_field ? Int *, # comment 1 + some_field ?? Int *, # comment 1 # comment 2 } diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index 9b9e6689b7c..4784ed0f64f 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -2449,7 +2449,7 @@ fn to_precord_report<'a>( PRecord::Colon(_) => { unreachable!("because `foo` is a valid field; the colon is not required") } - PRecord::Optional(_) => { + PRecord::OptionalFirst(_) | PRecord::OptionalSecond(_) => { unreachable!("because `foo` is a valid field; the question mark is not required") } @@ -2889,7 +2889,7 @@ fn to_trecord_report<'a>( alloc.region_with_subregion(lines.convert_region(surroundings), region, severity), alloc.concat([ 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.", ), ]), ]); @@ -2976,7 +2976,7 @@ fn to_trecord_report<'a>( ETypeRecord::Colon(_) => { unreachable!("because `foo` is a valid field; the colon is not required") } - ETypeRecord::Optional(_) => { + ETypeRecord::OptionalFirst(_) | ETypeRecord::OptionalSecond(_) => { unreachable!("because `foo` is a valid field; the question mark is not required") } diff --git a/docker/devcontainer/Dockerfile b/docker/devcontainer/Dockerfile new file mode 100644 index 00000000000..3c9aa2d53bd --- /dev/null +++ b/docker/devcontainer/Dockerfile @@ -0,0 +1,31 @@ +# Use the base image for rust +FROM mcr.microsoft.com/devcontainers/rust:1-1-bullseye + +# Install required packages and configure features + +# Install packages required by onCreateCommand +RUN apt-get update && \ + apt-get install -y pkg-config libz-dev libzstd-dev valgrind + +# Install Zig 0.13.0 +ARG ZIG_VERSION=0.13.0 +RUN apt-get update && \ + apt-get install -y wget curl && \ + curl -o zig.tar.gz https://ziglang.org/download/${ZIG_VERSION}/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \ + tar -xf zig.tar.gz && \ + mv zig-linux-x86_64-${ZIG_VERSION} /usr/local/zig && \ + rm zig.tar.gz && \ + ln -s /usr/local/zig/zig /usr/local/bin/zig && \ + apt-get remove -y --purge wget curl && \ + apt-get autoremove -y + +# Install LLVM 18 +ARG LLVM_VERSION=18 +RUN apt-get update && \ + apt-get install -y wget gnupg software-properties-common && \ + wget https://apt.llvm.org/llvm.sh && \ + chmod +x llvm.sh && \ + ./llvm.sh ${LLVM_VERSION} && \ + rm llvm.sh && \ + apt-get remove -y --purge wget gnupg software-properties-common && \ + apt-get autoremove -y \ No newline at end of file