From 2e2d9dc3c12d151ca436f751cf4ad64944d9333b Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Tue, 24 Jan 2023 12:58:21 -0800 Subject: [PATCH] Warn if an interpolated identifier is followed by -. (#1375) --- src/compiler/parser.cc | 24 +++++++++++++++++-- .../gold/minus_interpolated_test.gold | 10 ++++++++ tests/negative/minus_interpolated_test.toit | 12 ++++++++++ tools/make_crc.toit | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/negative/gold/minus_interpolated_test.gold create mode 100644 tests/negative/minus_interpolated_test.toit diff --git a/src/compiler/parser.cc b/src/compiler/parser.cc index eca40f184..82357ebe6 100644 --- a/src/compiler/parser.cc +++ b/src/compiler/parser.cc @@ -2272,12 +2272,25 @@ Expression* Parser::parse_string_interpolate() { ListBuilder expressions; bool is_multiline = current_token() == Token::STRING_PART_MULTI_LINE; + bool last_interpolated_was_identifier = false; + auto last_identifier_range = Source::Range::invalid(); + auto check_minus_after_identifier = [&](Symbol current_data) { + if (last_interpolated_was_identifier && + current_data.c_str()[0] == '-' && + is_identifier_part(current_data.c_str()[1])) { + diagnostics()->report_warning(last_identifier_range, + "Interpolated identifiers followed by '-' must be parenthesized"); + } + }; Token::Kind end_token = is_multiline ? Token::STRING_END_MULTI_LINE : Token::STRING_END; Token::Kind kind; auto range = start; do { - parts.add(NEW_NODE(LiteralString(current_token_data(), is_multiline), range)); + Symbol current_data = current_token_data(); + check_minus_after_identifier(current_data); + parts.add(NEW_NODE(LiteralString(current_data, is_multiline), range)); consume(); + last_interpolated_was_identifier = false; scan_interpolated_part(); // We just passed $. LiteralString* format = null; @@ -2300,6 +2313,8 @@ Expression* Parser::parse_string_interpolate() { if (encountered_error) discard_buffered_scanner_states(); } else if (current_token() == Token::IDENTIFIER) { expression = parse_identifier(); + last_interpolated_was_identifier = true; + last_identifier_range = expression->range(); } else { if (current_token() == Token::EOS || current_token() == Token::DEDENT) { report_error("Incomplete string interpolation"); @@ -2316,6 +2331,7 @@ Expression* Parser::parse_string_interpolate() { if (!was_parenthesized) { while (true) { if (scanner_peek() == '[') { + last_interpolated_was_identifier = false; bool encountered_error; expression = parse_postfix_index(expression, &encountered_error); if (encountered_error) { @@ -2333,6 +2349,8 @@ Expression* Parser::parse_string_interpolate() { if (current_token() == Token::IDENTIFIER && is_current_token_attached()) { Identifier* name = parse_identifier(); expression = NEW_NODE(Dot(expression, name), range); + last_interpolated_was_identifier = true; + last_identifier_range = range; continue; // Try for another postfix. } else { report_error("Non-identifier member name"); @@ -2349,7 +2367,9 @@ Expression* Parser::parse_string_interpolate() { range = current_range(); } while (kind != end_token); - parts.add(NEW_NODE(LiteralString(current_token_data(), is_multiline), range)); + Symbol current_data = current_token_data(); + check_minus_after_identifier(current_data); + parts.add(NEW_NODE(LiteralString(current_data, is_multiline), range)); consume(); return NEW_NODE(LiteralStringInterpolation(parts.build(), formats.build(), expressions.build()), start); } diff --git a/tests/negative/gold/minus_interpolated_test.gold b/tests/negative/gold/minus_interpolated_test.gold new file mode 100644 index 000000000..8304aa15b --- /dev/null +++ b/tests/negative/gold/minus_interpolated_test.gold @@ -0,0 +1,10 @@ +tests/negative/minus_interpolated_test.toit:9:11: warning: Interpolated identifiers followed by '-' must be parenthesized + print "$foo-b" // Warning, since that would become a single identifier. + ^~~ +tests/negative/minus_interpolated_test.toit:10:11: warning: Interpolated identifiers followed by '-' must be parenthesized + print "$foo-3" // Same warning. + ^~~ +tests/negative/minus_interpolated_test.toit:12:3: error: Unresolved identifier: 'unresolved' + unresolved + ^~~~~~~~~~ +Compilation failed. diff --git a/tests/negative/minus_interpolated_test.toit b/tests/negative/minus_interpolated_test.toit new file mode 100644 index 000000000..9904fd7f1 --- /dev/null +++ b/tests/negative/minus_interpolated_test.toit @@ -0,0 +1,12 @@ +// Copyright (C) 2023 Toitware ApS. +// Use of this source code is governed by a Zero-Clause BSD license that can +// be found in the tests/LICENSE file. + +main: + foo := 499 + bar := 42 + print "$foo-" // OK because not followed by identifier character. + print "$foo-b" // Warning, since that would become a single identifier. + print "$foo-3" // Same warning. + print "$foo-$bar" // OK. + unresolved diff --git a/tools/make_crc.toit b/tools/make_crc.toit index b6836a91b..900550432 100644 --- a/tools/make_crc.toit +++ b/tools/make_crc.toit @@ -77,7 +77,7 @@ main args: Computes the $name checksum of the given \$data. The \$data must be a string or byte array. - Returns the checksum as a$(width == 8 ? "n" : "") $width-bit integer. + Returns the checksum as a$(width == 8 ? "n" : "") $(width)-bit integer. */ $name_snake data -> int: crc := Crc.$(endian)_endian $width --$polynomial_argument=$fields[2]$initial_string$xor_string