diff --git a/.github/workflows/basic_cli_test_arm64.yml b/.github/workflows/basic_cli_test_arm64.yml index ce9a06fef6..dd581a21d9 100644 --- a/.github/workflows/basic_cli_test_arm64.yml +++ b/.github/workflows/basic_cli_test_arm64.yml @@ -53,10 +53,6 @@ jobs: latestTag=$(git describe --tags $(git rev-list --tags --max-count=1)) git checkout $latestTag - # temp issue with new string interpolation syntax - # TODO undo when 0.7.2 or 0.8.0 is released - - run: sed -i 's/\$//g' basic-cli/examples/tcp-client.roc - - name: Run all tests with latest roc nightly and latest basic-cli release run: | sed -i 's/x86_64/arm64/g' ./ci/test_latest_release.sh diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index f5d8c29b73..cdb36cef09 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -4903,10 +4903,8 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>( Attribute::get_named_enum_kind_id("byval"), c_abi_type.as_any_type_enum(), ); - let nonnull = context.create_type_attribute( - Attribute::get_named_enum_kind_id("nonnull"), - c_abi_type.as_any_type_enum(), - ); + let nonnull = context + .create_enum_attribute(Attribute::get_named_enum_kind_id("nonnull"), 0); // C return pointer goes at the beginning of params, and we must skip it if it exists. let returns_pointer = matches!(cc_return, CCReturn::ByPointer); let param_index = i as u32 + returns_pointer as u32; diff --git a/crates/compiler/parse/src/string_literal.rs b/crates/compiler/parse/src/string_literal.rs index 7d111cf52c..3bb467baac 100644 --- a/crates/compiler/parse/src/string_literal.rs +++ b/crates/compiler/parse/src/string_literal.rs @@ -173,6 +173,8 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri }; } + let mut preceded_by_dollar = false; + while let Some(&byte) = bytes.next() { // This is for the byte we just grabbed from the iterator. segment_parsed_bytes += 1; @@ -349,68 +351,6 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri return Err((MadeProgress, EString::EndlessSingleLine(start_state.pos()))); } } - b'$' if !is_single_quote => { - // This is for the byte we're about to parse. - segment_parsed_bytes += 1; - - // iff the '$' is followed by '(', this is string interpolation. - if let Some(b'(') = bytes.next() { - // We're about to begin string interpolation! - // - // End the previous segment so we can begin a new one. - // Retroactively end it right before the `$` char we parsed. - // (We can't use end_segment! here because it ends it right after - // the just-parsed character, which here would be '(' rather than '$') - // Don't push anything if the string would be empty. - if segment_parsed_bytes > 2 { - // exclude the 2 chars we just parsed, namely '$' and '(' - let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)]; - - match std::str::from_utf8(string_bytes) { - Ok(string) => { - state.advance_mut(string.len()); - - segments.push(StrSegment::Plaintext(string)); - } - Err(_) => { - return Err(( - MadeProgress, - EString::Space(BadInputError::BadUtf8, state.pos()), - )); - } - } - } - - // Advance past the `$(` - state.advance_mut(2); - - let original_byte_count = state.bytes().len(); - - // Parse an arbitrary expression, followed by ')' - let (_progress, loc_expr, new_state) = skip_second!( - specialize_ref( - EString::Format, - loc(allocated(reset_min_indent(expr::expr_help()))) - ), - word1(b')', EString::FormatEnd) - ) - .parse(arena, state, min_indent)?; - - // Advance the iterator past the expr we just parsed. - for _ in 0..(original_byte_count - new_state.bytes().len()) { - bytes.next(); - } - - segments.push(StrSegment::Interpolated(loc_expr)); - - // Reset the segment - segment_parsed_bytes = 0; - state = new_state; - } - - // If the '$' wasn't followed by '(', then this wasn't interpolation, - // and we don't need to do anything special. - } b'\\' => { // We're about to begin an escaped segment of some sort! // @@ -510,10 +450,67 @@ pub fn parse_str_like_literal<'a>() -> impl Parser<'a, StrLikeLiteral<'a>, EStri } } } + b'(' if preceded_by_dollar && !is_single_quote => { + // We're about to begin string interpolation! + // + // End the previous segment so we can begin a new one. + // Retroactively end it right before the `$` char we parsed. + // (We can't use end_segment! here because it ends it right after + // the just-parsed character, which here would be '(' rather than '$') + // Don't push anything if the string would be empty. + if segment_parsed_bytes > 2 { + // exclude the 2 chars we just parsed, namely '$' and '(' + let string_bytes = &state.bytes()[0..(segment_parsed_bytes - 2)]; + + match std::str::from_utf8(string_bytes) { + Ok(string) => { + state.advance_mut(string.len()); + + segments.push(StrSegment::Plaintext(string)); + } + Err(_) => { + return Err(( + MadeProgress, + EString::Space(BadInputError::BadUtf8, state.pos()), + )); + } + } + } + + // Advance past the `$(` + state.advance_mut(2); + + let original_byte_count = state.bytes().len(); + + // Parse an arbitrary expression, followed by ')' + let (_progress, loc_expr, new_state) = skip_second!( + specialize_ref( + EString::Format, + loc(allocated(reset_min_indent(expr::expr_help()))) + ), + word1(b')', EString::FormatEnd) + ) + .parse(arena, state, min_indent)?; + + // Advance the iterator past the expr we just parsed. + for _ in 0..(original_byte_count - new_state.bytes().len()) { + bytes.next(); + } + + segments.push(StrSegment::Interpolated(loc_expr)); + + // Reset the segment + segment_parsed_bytes = 0; + state = new_state; + } _ => { // All other characters need no special handling. } } + + // iff the '$' is followed by '(', this is string interpolation. + // We'll check for the '(' on the next iteration of the loop. + preceded_by_dollar = byte == b'$'; } // We ran out of characters before finding a closed quote diff --git a/crates/compiler/parse/tests/test_parse.rs b/crates/compiler/parse/tests/test_parse.rs index 357b0842da..93e92728aa 100644 --- a/crates/compiler/parse/tests/test_parse.rs +++ b/crates/compiler/parse/tests/test_parse.rs @@ -200,6 +200,36 @@ mod test_parse { }); } + #[test] + fn string_of_just_dollar_sign() { + let arena = Bump::new(); + + assert_eq!( + Ok(Expr::Str(PlainLine("$"))), + parse_expr_with(&arena, arena.alloc(r#""$""#)) + ); + } + + #[test] + fn string_beginning_with_dollar() { + let arena = Bump::new(); + + assert_eq!( + Ok(Expr::Str(PlainLine("$foo"))), + parse_expr_with(&arena, arena.alloc(r#""$foo""#)) + ); + } + + #[test] + fn string_ending_with_dollar() { + let arena = Bump::new(); + + assert_eq!( + Ok(Expr::Str(PlainLine("foo$"))), + parse_expr_with(&arena, arena.alloc(r#""foo$""#)) + ); + } + #[test] fn string_with_interpolation_in_back() { assert_segments(r#""Hello $(name)""#, |arena| {