Merge pull request #2599 from hzeller/feature-20260910-smoke-params

Add command line options to smoke test.
diff --git a/shell.nix b/shell.nix
index 85163eb..408340b 100644
--- a/shell.nix
+++ b/shell.nix
@@ -8,6 +8,17 @@
   #verible_used_stdenv = pkgs.gcc15Stdenv;
   #verible_used_stdenv = pkgs.clang19Stdenv;
   bazel = pkgs.bazel_8;
+
+
+  userNixPath = ./user.nix;  # optional user config
+  userPackages =
+    if builtins.pathExists userNixPath
+    then
+      let loaded = import userNixPath;
+      in if builtins.isFunction loaded
+         then loaded { inherit pkgs; }
+         else loaded
+    else [];
 in
 verible_used_stdenv.mkDerivation {
   name = "verible-build-environment";
@@ -40,7 +51,7 @@
 
       llvmPackages_22.clang-tools    # for clang-tidy
       llvmPackages_19.clang-tools    # for clang-format
-    ];
+    ] ++ userPackages;
   shellHook = ''
       # clang tidy: use latest.
       export CLANG_TIDY=${pkgs.llvmPackages_22.clang-tools}/bin/clang-tidy
diff --git a/verible/verilog/analysis/checkers/BUILD b/verible/verilog/analysis/checkers/BUILD
index 2151eaa..4681607 100644
--- a/verible/verilog/analysis/checkers/BUILD
+++ b/verible/verilog/analysis/checkers/BUILD
@@ -1824,6 +1824,7 @@
         "//verible/common/text:symbol",
         "//verible/common/text:syntax-tree-context",
         "//verible/common/text:token-info",
+        "//verible/common/util:container-util",
         "//verible/verilog/CST:port",
         "//verible/verilog/CST:verilog-matchers",
         "//verible/verilog/analysis:descriptions",
diff --git a/verible/verilog/analysis/checkers/port-name-suffix-rule.cc b/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
index 85a0db3..ffecef3 100644
--- a/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
+++ b/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
@@ -28,6 +28,7 @@
 #include "verible/common/text/symbol.h"
 #include "verible/common/text/syntax-tree-context.h"
 #include "verible/common/text/token-info.h"
+#include "verible/common/util/container-util.h"
 #include "verible/verilog/CST/port.h"
 #include "verible/verilog/CST/verilog-matchers.h"
 #include "verible/verilog/analysis/descriptions.h"
@@ -90,10 +91,16 @@
        {"output", {"o", "no", "po"}},
        {"inout", {"io", "nio", "pio"}}};
 
-  // At this point it is guaranteed that the direction will be set to
-  // one of the expected values (used as keys in the map above).
-  // Therefore checking the suffix like this is safe
-  return suffixes.at(direction).count(suffix) == 1;
+  // `direction` is usually one of the map keys, but the grammar also permits a
+  // `ref` port direction, which has no suffix convention. FindWithDefault looks
+  // the direction up with an empty-set fallback, so an unknown direction (e.g.
+  // `ref`) has no required suffixes and is treated as "correct" (no violation),
+  // consistent with Violation() which also ignores non-input/output/inout
+  // directions.
+  static const std::set<std::string_view> kNoConvention;
+  const std::set<std::string_view> &valid =
+      verible::container::FindWithDefault(suffixes, direction, kNoConvention);
+  return valid.empty() || valid.count(suffix) == 1;
 }
 
 void PortNameSuffixRule::HandleSymbol(const Symbol &symbol,
@@ -113,8 +120,11 @@
         absl::StrSplit(name, '_', absl::SkipEmpty());
 
     if (name_parts.size() < 2) {
-      // No suffix at all
+      // No suffix at all. This also covers an all-underscore name (e.g. "_"),
+      // for which SkipEmpty leaves name_parts empty; return here so the
+      // name_parts.back() access below is not reached on an empty vector.
       Violation(direction, token, context);
+      return;
     }
 
     if (!IsSuffixCorrect(name_parts.back(), direction)) {
diff --git a/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc b/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
index 69f18f4..db69d71 100644
--- a/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
+++ b/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
@@ -57,6 +57,9 @@
       {"module t (input bit name_i); endmodule;"},
       {"module t (output bit abc_o); endmodule;"},
       {"module t (inout bit xyz_io); endmodule;"},
+      // A `ref` port has no suffix convention and must not be flagged (and must
+      // not crash the rule via std::map::at).
+      {"module t (ref logic data_x); endmodule;"},
       {"module t (input logic name_i,\n"
        "output logic abc_o,\n"
        "inout logic xyz_io,\n"
@@ -90,6 +93,10 @@
       {"module t (output logic ", {kToken, "_o"}, "); endmodule;"},
       {"module t (inout logic ", {kToken, "_io"}, "); endmodule;"},
 
+      // An all-underscore name splits (SkipEmpty) to an empty parts list; it
+      // must report a suffix violation, not dereference an empty vector.
+      {"module t (input logic ", {kToken, "_"}, "); endmodule;"},
+
       {"module t (input logic ", {kToken, "namei"}, "); endmodule;"},
       {"module t (input logic ", {kToken, "nam_ei"}, "); endmodule;"},
       {"module t (input logic ", {kToken, "name_o"}, "); endmodule;"},
diff --git a/verible/verilog/analysis/verilog-equivalence.cc b/verible/verilog/analysis/verilog-equivalence.cc
index f8a47d0..68c8c98 100644
--- a/verible/verilog/analysis/verilog-equivalence.cc
+++ b/verible/verilog/analysis/verilog-equivalence.cc
@@ -89,6 +89,20 @@
   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) {
+  const auto is_line_ending_macro = [](int token_enum) {
+    return token_enum == verilog_tokentype::MacroIdentifier ||
+           token_enum == verilog_tokentype::MacroIdItem;
+  };
+  return is_line_ending_macro(left.token_enum()) &&
+         is_line_ending_macro(right.token_enum()) &&
+         left.text() == right.text();
+}
+
 DiffStatus VerilogLexicallyEquivalent(
     std::string_view left, std::string_view right,
     const std::function<bool(const verible::TokenInfo &)> &remove_predicate,
@@ -167,11 +181,17 @@
   DiffStatus diff_status = DiffStatus::kEquivalent;
   auto recursive_comparator = [&](const TokenSequence::const_iterator l,
                                   const TokenSequence::const_iterator r) {
+    // 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() &&
-        !((l->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
-           r->text() == ")") ||
-          (r->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
-           l->text() == ")"))) {
+        !whitespace_dependent_macro_enum_match) {
       if (errstream != nullptr) {
         *errstream << "Mismatched token enums.  got: ";
         token_printer(*l, *errstream);
@@ -258,6 +278,9 @@
              (r.text() == ")"))) {
           return true;
         }
+        if (AreSpellingEqualLineEndingMacros(l, r)) {
+          return true;
+        }
         return l.EquivalentWithoutLocation(r);
       },
       errstream);
diff --git a/verible/verilog/analysis/verilog-equivalence_test.cc b/verible/verilog/analysis/verilog-equivalence_test.cc
index 90b0ee1..1cb9436 100644
--- a/verible/verilog/analysis/verilog-equivalence_test.cc
+++ b/verible/verilog/analysis/verilog-equivalence_test.cc
@@ -255,6 +255,21 @@
   }
 }
 
+// MacroIdentifier vs MacroIdItem depends on whether the macro ends the line.
+TEST(FormatEquivalentTest, EquivalenceOfMacroIdentifierAndMacroIdItem) {
+  const char *kSameSpelling[] = {
+      "assign x = f(`TOKEN);\n",
+      "assign x = f(\n`TOKEN\n);\n",
+  };
+  ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kEquivalent,
+                             kSameSpelling[0], kSameSpelling[1]);
+
+  // Different macro names remain different.
+  ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kDifferent,
+                             "assign x = f(`TOKEN);\n",
+                             "assign x = f(`OTHER);\n");
+}
+
 TEST(FormatEquivalentTest, DiagnosticMismatch) {
   const char *kTestCases[] = {
       "module foo;\n",
diff --git a/verible/verilog/formatting/formatter_test.cc b/verible/verilog/formatting/formatter_test.cc
index bd0f775..13a9fb4 100644
--- a/verible/verilog/formatting/formatter_test.cc
+++ b/verible/verilog/formatting/formatter_test.cc
@@ -19456,6 +19456,24 @@
   }
 }
 
+// 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
+// formatting must still pass verification.
+TEST(FormatterEndToEndTest, MacroBeforeCloseParenFormatEquivalent) {
+  static constexpr std::string_view kInput =
+      "module m;\n"
+      "  assign result_value = $bits(result_value)'( "
+      "compare_bytes(input_data[DATA_WIDTH_INT-1:0], "
+      "input_datak[STROBE_WIDTH_INT-1:0], `TOKEN_BYTE) );\n"
+      "endmodule\n";
+  FormatStyle style;
+  std::ostringstream stream;
+  const auto status = FormatVerilog(kInput, "<filename>", style, stream);
+  EXPECT_OK(status) << status.message();
+  EXPECT_THAT(stream.str(), testing::HasSubstr("`TOKEN_BYTE"));
+}
+
 // Regression for https://github.com/chipsalliance/verible/issues/2542:
 // Continuation EOL comments after a wrapped assign must keep a stable column
 // across re-format (convergence).