Merge branch 'chipsalliance:master' into fix/2352
diff --git a/verible/verilog/formatting/formatter_test.cc b/verible/verilog/formatting/formatter_test.cc
index aad3e60..894254f 100644
--- a/verible/verilog/formatting/formatter_test.cc
+++ b/verible/verilog/formatting/formatter_test.cc
@@ -128,6 +128,41 @@
   RunFormatterTestCases40(kSmokeFormatterTestCases);
 }
 
+// Regression for https://github.com/chipsalliance/verible/issues/2352:
+// '/' between identifiers in a macro argument is a path separator and must
+// not be spaced as a division operator (that breaks compiles).
+TEST(FormatterEndToEndTest, MacroArgPathSeparatorsKeepNoSpace) {
+  static constexpr FormatterTestCase kTestCases[] = {
+      {// Original issue sample
+       "`PROJECT_INCLUDE(`PATH_MY_MODULE/src/config_class.sv)\n",
+       "`PROJECT_INCLUDE(`PATH_MY_MODULE/src/config_class.sv)\n"},
+      {// Extra spaces around '/' are removed in macro args
+       "`PROJECT_INCLUDE(`PATH_MY_MODULE / src / config_class.sv)\n",
+       "`PROJECT_INCLUDE(`PATH_MY_MODULE/src/config_class.sv)\n"},
+      {// Nested directories
+       "`INCLUDE(foo/bar/baz.svh)\n", "`INCLUDE(foo/bar/baz.svh)\n"},
+      {// Path as a later argument
+       "`LOAD(cfg, `ROOT/hw/ip/file.sv)\n",
+       "`LOAD(cfg, `ROOT/hw/ip/file.sv)\n"},
+      {// Division between identifiers outside macros still gets spaces
+       "module m;\n"
+       "  assign x = a/b;\n"
+       "endmodule\n",
+       "module m;\n"
+       "  assign x = a / b;\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;
+  }
+}
+
 }  // namespace
 }  // namespace formatter
 }  // namespace verilog
diff --git a/verible/verilog/formatting/token-annotator.cc b/verible/verilog/formatting/token-annotator.cc
index ebaeb21..fa3aadd 100644
--- a/verible/verilog/formatting/token-annotator.cc
+++ b/verible/verilog/formatting/token-annotator.cc
@@ -120,6 +120,29 @@
       {});
 }
 
+// '/' between identifiers inside a macro argument is a filesystem path
+// (e.g. `INCLUDE(`PATH/src/file.sv)), not a division operator (issue #2352).
+static bool InMacroArgumentContext(const SyntaxTreeContext &context) {
+  return context.IsInsideFirst({NodeEnum::kMacroArgList, NodeEnum::kMacroCall,
+                                NodeEnum::kMacroGenericItem},
+                               {});
+}
+
+static bool IsPathSeparatorSlashInMacroArg(
+    const PreFormatToken &left, const PreFormatToken &right,
+    const SyntaxTreeContext &left_context,
+    const SyntaxTreeContext &right_context) {
+  if (left.TokenEnum() != '/' && right.TokenEnum() != '/') return false;
+  const bool identifier_on_other_side =
+      (left.TokenEnum() == '/' &&
+       right.format_token_enum == FormatTokenType::identifier) ||
+      (right.TokenEnum() == '/' &&
+       left.format_token_enum == FormatTokenType::identifier);
+  if (!identifier_on_other_side) return false;
+  return InMacroArgumentContext(left_context) ||
+         InMacroArgumentContext(right_context);
+}
+
 static bool IsAnySemicolon(const PreFormatToken &ftoken) {
   // These are just syntactically disambiguated versions of ';'.
   return ftoken.TokenEnum() == ';' ||
@@ -228,6 +251,10 @@
   // Consider assignment operators in the same class as binary operators.
   if (left.format_token_enum == FormatTokenType::binary_operator ||
       right.format_token_enum == FormatTokenType::binary_operator) {
+    if (IsPathSeparatorSlashInMacroArg(left, right, left_context,
+                                       right_context)) {
+      return {0, "No space around '/' path separators in macro arguments"};
+    }
     // Inside [], allows 0 or 1 spaces, and symmetrize.
     // TODO(fangism): make this behavior configurable
     if (right.format_token_enum == FormatTokenType::binary_operator &&
diff --git a/verible/verilog/formatting/token-annotator_test.cc b/verible/verilog/formatting/token-annotator_test.cc
index 04c9832..d2df9c9 100644
--- a/verible/verilog/formatting/token-annotator_test.cc
+++ b/verible/verilog/formatting/token-annotator_test.cc
@@ -4715,6 +4715,48 @@
           {/* unspecified context */},
           {1, SpacingOptions::kUndecided},
       },
+      // '/' between identifiers is a path separator in macro args (#2352),
+      // but remains a binary operator in other contexts.
+      {
+          DefaultStyle,
+          {verilog_tokentype::MacroIdentifier, "`PATH"},
+          {'/', "/"},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {0, SpacingOptions::kUndecided},
+      },
+      {
+          DefaultStyle,
+          {'/', "/"},
+          {verilog_tokentype::SymbolIdentifier, "src"},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {0, SpacingOptions::kUndecided},
+      },
+      {
+          DefaultStyle,
+          {verilog_tokentype::SymbolIdentifier, "src"},
+          {'/', "/"},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {NodeEnum::kMacroArgList, NodeEnum::kMacroCall},
+          {0, SpacingOptions::kUndecided},
+      },
+      {
+          DefaultStyle,
+          {verilog_tokentype::SymbolIdentifier, "a"},
+          {'/', "/"},
+          {/* expression, not a macro argument */},
+          {/* expression, not a macro argument */},
+          {1, SpacingOptions::kUndecided},
+      },
+      {
+          DefaultStyle,
+          {'/', "/"},
+          {verilog_tokentype::SymbolIdentifier, "b"},
+          {/* expression, not a macro argument */},
+          {/* expression, not a macro argument */},
+          {1, SpacingOptions::kUndecided},
+      },
   };
   int test_index = 0;
   for (const auto &test_case : kTestCases) {