Merge origin/master into fix/2547 Resolve verilog-equivalence.cc by keeping TokensAreWhitespaceDependent FormatEquivalent (covers MacroCallCloseToEndLine and line-ending macros). Resolve formatter_test.cc by keeping #2547, #2544, and #2542 regressions.
diff --git a/verible/verilog/analysis/verilog-equivalence.cc b/verible/verilog/analysis/verilog-equivalence.cc index 68c8c98..d5c12bea 100644 --- a/verible/verilog/analysis/verilog-equivalence.cc +++ b/verible/verilog/analysis/verilog-equivalence.cc
@@ -89,11 +89,17 @@ return IsUnlexed(verilog_tokentype(token.token_enum())); } -// MacroIdentifier vs MacroIdItem depends only on whether the macro ends the -// line (see POST_MACRO_ID in verilog.lex). Spelling-equal macros are -// format-equivalent across that reclassification. -static bool AreSpellingEqualLineEndingMacros(const TokenInfo &left, - const TokenInfo &right) { +// True when left/right differ only because surrounding whitespace changed +// token classification (e.g. MacroCallCloseToEndLine vs ')', or +// MacroIdentifier vs MacroIdItem with unchanged spelling). +static bool TokensAreWhitespaceDependentFormatEquivalent( + const TokenInfo &left, const TokenInfo &right) { + if ((left.token_enum() == verilog_tokentype::MacroCallCloseToEndLine && + right.text() == ")") || + (right.token_enum() == verilog_tokentype::MacroCallCloseToEndLine && + left.text() == ")")) { + return true; + } const auto is_line_ending_macro = [](int token_enum) { return token_enum == verilog_tokentype::MacroIdentifier || token_enum == verilog_tokentype::MacroIdItem; @@ -184,14 +190,8 @@ // Some token enums differ only by surrounding whitespace (e.g. whether a // macro or ')' ends a line). Treat those pairs as matching enums when the // spelling is unchanged so FormatEquivalent tolerates re-wrapping. - const bool whitespace_dependent_macro_enum_match = - ((l->token_enum() == verilog_tokentype::MacroCallCloseToEndLine && - r->text() == ")") || - (r->token_enum() == verilog_tokentype::MacroCallCloseToEndLine && - l->text() == ")") || - AreSpellingEqualLineEndingMacros(*l, *r)); if (l->token_enum() != r->token_enum() && - !whitespace_dependent_macro_enum_match) { + !TokensAreWhitespaceDependentFormatEquivalent(*l, *r)) { if (errstream != nullptr) { *errstream << "Mismatched token enums. got: "; token_printer(*l, *errstream); @@ -270,15 +270,7 @@ return IsWhitespace(verilog_tokentype(t.token_enum())); }, [=](const TokenInfo &l, const TokenInfo &r) { - // MacroCallCloseToEndLine should be considered equivalent to ')', as - // they are whitespace dependant - if (((r.token_enum() == verilog_tokentype::MacroCallCloseToEndLine) && - (l.text() == ")")) || - ((l.token_enum() == verilog_tokentype::MacroCallCloseToEndLine) && - (r.text() == ")"))) { - return true; - } - if (AreSpellingEqualLineEndingMacros(l, r)) { + if (TokensAreWhitespaceDependentFormatEquivalent(l, r)) { return true; } return l.EquivalentWithoutLocation(r);
diff --git a/verible/verilog/formatting/formatter_test.cc b/verible/verilog/formatting/formatter_test.cc index 13a9fb4..16e0572 100644 --- a/verible/verilog/formatting/formatter_test.cc +++ b/verible/verilog/formatting/formatter_test.cc
@@ -19456,6 +19456,50 @@ } } +// Regression for https://github.com/chipsalliance/verible/issues/2547: +// A localparam initialized to a sum of long macros must converge: infix `+` +// stays with the following operand so re-format does not oscillate between +// `+\n`MACRO` and `+ `MACRO`. +TEST(FormatterEndToEndTest, LongMacroSumLocalparamConverges) { + static constexpr FormatterTestCase kTestCases[] = { + {"module m;\n" + " localparam N =\n" + " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n" + " + `MACRO_GEN3_SCRAMBLE_REGIN\n" + " + `MACRO_GEN3_SCRAMBLE_REGOUT\n" + " ;\n" + "endmodule\n", + "module m;\n" + " localparam N =\n" + " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n" + " + `MACRO_GEN3_SCRAMBLE_REGIN\n" + " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n" + "endmodule\n"}, + // Already in pass-1 form must stay stable. + {"module m;\n" + " localparam N =\n" + " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n" + " + `MACRO_GEN3_SCRAMBLE_REGIN\n" + " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n" + "endmodule\n", + "module m;\n" + " localparam N =\n" + " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n" + " + `MACRO_GEN3_SCRAMBLE_REGIN\n" + " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n" + "endmodule\n"}, + }; + FormatStyle style; // default column_limit (100) + for (const auto &test_case : kTestCases) { + VLOG(1) << "code-to-format:\n" << test_case.input << "<EOF>"; + std::ostringstream stream; + const auto status = + FormatVerilog(test_case.input, "<filename>", style, stream); + EXPECT_OK(status) << status.message(); + EXPECT_EQ(stream.str(), test_case.expected) << "code:\n" << test_case.input; + } +} + // Regression for https://github.com/chipsalliance/verible/issues/2544: // Wrapping a $bits(...)'(...) cast may leave `MACRO at EOL, reclassifying // MacroIdentifier as MacroIdItem. FormatEquivalent must accept that, and
diff --git a/verible/verilog/formatting/tree-unwrapper.cc b/verible/verilog/formatting/tree-unwrapper.cc index 2286f8c..09fa8c0 100644 --- a/verible/verilog/formatting/tree-unwrapper.cc +++ b/verible/verilog/formatting/tree-unwrapper.cc
@@ -1641,6 +1641,53 @@ } } +// True when a leaf partition contains only a binary/infix operator (and +// optional comments/attributes). Used to normalize wrapping of expressions +// like `A + B + C` so operators always stay with the following operand. +static bool PartitionIsInfixOperatorOnly(const TokenPartitionTree &partition) { + if (!is_leaf(partition)) return false; + const auto tokens = partition.Value().TokensRange(); + if (tokens.empty()) return false; + + const verible::PreFormatToken *op = nullptr; + for (const auto &token : tokens) { + switch (token.TokenEnum()) { + case verilog_tokentype::TK_COMMENT_BLOCK: + case verilog_tokentype::TK_EOL_COMMENT: + case verilog_tokentype::TK_ATTRIBUTE: + break; + default: + if (GetFormatTokenType(static_cast<verilog_tokentype>( + token.TokenEnum())) != FormatTokenType::binary_operator || + op != nullptr) { + return false; + } + op = &token; + break; + } + } + return op != nullptr; +} + +// Always attach infix-operator-only partitions to the following operand. +// Attachment based on original newlines is unstable for macro sums: +// `A\n+\n`B vs `A\n+ `B produce different partition shapes and oscillate +// under re-format (GitHub issue 2547). +static void AttachInfixOperatorsToFollowingOperands( + TokenPartitionTree *partition) { + // Iterate by index; merges invalidate sibling pointers. + for (int i = 0; i < static_cast<int>(partition->Children().size()); ++i) { + auto &child = partition->Children()[i]; + if (!PartitionIsInfixOperatorOnly(child)) continue; + if (NextLeaf(child) == nullptr) continue; + VLOG(4) << "Attaching infix operator partition to following operand:\n" + << child; + verible::MergeLeafIntoNextLeaf(&child); + // Children shifted; re-check current index. + --i; + } +} + static void AttachTrailingSemicolonToPreviousPartition( TokenPartitionTree *partition) { // TODO(mglb): Replace this function with @@ -3137,6 +3184,7 @@ case NodeEnum::kParamDeclaration: { AttachTrailingSemicolonToPreviousPartition(&partition); AttachOpeningBraceToDeclarationsAssignmentOperator(&partition); + AttachInfixOperatorsToFollowingOperands(&partition); break; }