Merge branch 'chipsalliance:master' into fix/2605
diff --git a/.clang-tidy b/.clang-tidy
index 70ee773..035549d 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -88,8 +88,12 @@
   -modernize-return-braced-init-list,
   -modernize-type-traits,
   -modernize-use-auto,
+  -modernize-use-constraints,
+  -modernize-use-designated-initializers,
   -modernize-use-emplace,
+  -modernize-use-integer-sign-comparison,
   -modernize-use-nodiscard,
+  -modernize-use-ranges,
   -modernize-use-trailing-return-type,
   -modernize-use-transparent-functors,
   misc-*,
diff --git a/.github/bin/make-compilation-db.sh b/.github/bin/make-compilation-db.sh
index 5038893..7aab755 100755
--- a/.github/bin/make-compilation-db.sh
+++ b/.github/bin/make-compilation-db.sh
@@ -21,22 +21,20 @@
 
 BAZEL_OPTS="-c opt --noshow_progress --remote_download_outputs=all"
 
+# Trigger necessary fetches from MODULE.bazel
+for f in abseil-cpp nlohmann_json protobuf re2 rules_flex zlib googletest ; do
+  "${BAZEL}" fetch --repo "@$f" > /dev/null 2>&1
+done
+
 # Bazel-build all targets that generate files, so that they can be
 # seen in dependency analysis.
-${BAZEL} build -k ${BAZEL_OPTS} $(${BANT} list-targets ... \
-                         -g 'genrule|cc_proto_library|genlex|genyacc' -c3)
-
-# Some selected targets to trigger all dependency fetches from MODULE.bazel
-# verilog-y-final to create a header, kzip creator to trigger build of any.pb.h
-# and some test that triggers fetching nlohmann_json and gtest
-${BAZEL} build -k ${BAZEL_OPTS} //verible/verilog/parser:verilog-y-final \
-  //verible/verilog/tools/kythe:verible-verilog-kythe-kzip-writer \
-  //verible/common/lsp:json-rpc-dispatcher_test
+"${BAZEL}" build ${BAZEL_OPTS} \
+           $(${BANT} list-targets -g "genrule|cc_proto_library" -m -c3 ...)
 
 # bant does not distinguish the compile flags per file yet, so instead of
 # a compile_commands.json, we can just as well create a simpler
 # compile_flags.txt which is easier to digest for all kinds of tools anyway.
-${BANT} compile-flags 2>/dev/null > compile_flags.txt
+${BANT} compile-flags -o compile_flags.txt
 
 # Bant does not see the flex dependency inside the toolchain yet.
 for d in bazel-out/../../../external/*flex*/src/FlexLexer.h ; do
diff --git a/.github/bin/run-build-cleaner.sh b/.github/bin/run-build-cleaner.sh
index e372646..f449fea 100755
--- a/.github/bin/run-build-cleaner.sh
+++ b/.github/bin/run-build-cleaner.sh
@@ -26,7 +26,8 @@
 done
 
 "${BAZEL}" build -k --remote_download_outputs=all \
-           $(${BANT} genrule-outputs ... -c2) > /dev/null 2>&1
+           $(${BANT} list-targets -g "genrule|cc_proto_library" -m -c3 ...) \
+           > /dev/null 2>&1
 
 if "${BANT}" dwyu $@; then
   echo "Dependencies ok." >&2
diff --git a/verible/common/analysis/lint-rule-status.cc b/verible/common/analysis/lint-rule-status.cc
index eb36ab6..7a79a42 100644
--- a/verible/common/analysis/lint-rule-status.cc
+++ b/verible/common/analysis/lint-rule-status.cc
@@ -60,7 +60,7 @@
 bool AutoFix::AddEdits(const std::set<ReplacementEdit> &new_edits) {
   // Check for conflicts
   for (const auto &edit : new_edits) {
-    if (edits_.find(edit) != edits_.end()) {
+    if (edits_.contains(edit)) {
       return false;
     }
   }
diff --git a/verible/common/analysis/matcher/bound-symbol-manager.cc b/verible/common/analysis/matcher/bound-symbol-manager.cc
index 7c2030c..e34dce5 100644
--- a/verible/common/analysis/matcher/bound-symbol-manager.cc
+++ b/verible/common/analysis/matcher/bound-symbol-manager.cc
@@ -27,7 +27,7 @@
 namespace matcher {
 
 bool BoundSymbolManager::ContainsSymbol(const std::string &id) const {
-  return bound_symbols_.find(id) != bound_symbols_.end();
+  return bound_symbols_.contains(id);
 }
 
 const Symbol *BoundSymbolManager::FindSymbol(const std::string &id) const {
diff --git a/verible/common/formatting/token-partition-tree.cc b/verible/common/formatting/token-partition-tree.cc
index 183fee5..7a296b1 100644
--- a/verible/common/formatting/token-partition-tree.cc
+++ b/verible/common/formatting/token-partition-tree.cc
@@ -783,6 +783,39 @@
 //
 // When "subpartitions" group has kAlwaysExpand policy, line break is forced
 // between each subpartition from the group.
+// kAppendFittingSubPartitions expects [header, args] or [header, args,
+// trailer]. Packed dimensions with $clog2(...) (issue #886) can split the
+// header into extra sibling leaves; if those are left in place, the port
+// list is treated as a trailer and dropped.
+// Only merge leading *leaf* fragments. Nested argument lists (non-leaves)
+// are flattened only when another non-leaf (the real port list) follows.
+static int CountNonLeafChildren(const TokenPartitionTree &node) {
+  const auto &children = node.Children();
+  return std::count_if(
+      children.begin(), children.end(),
+      [](const TokenPartitionTree &child) { return !is_leaf(child); });
+}
+
+static void CollapseHeaderFragmentsBeforeArgs(TokenPartitionTree *node) {
+  while (node->Children().size() > 2) {
+    auto &children = node->Children();
+    auto &first = children[0];
+    auto &second = children[1];
+    // Merge extra header leaves only when a nested argument list still
+    // follows.  All-leaf trees are the flattened one-argument form
+    // ([header, arg] or [header, arg, trailer]) and must be left intact.
+    if (is_leaf(first) && is_leaf(second) && CountNonLeafChildren(*node) >= 1) {
+      MergeConsecutiveSiblings(node, 0);
+      continue;
+    }
+    if (!is_leaf(second) && CountNonLeafChildren(*node) >= 2) {
+      FlattenOneChild(*node, 1);
+      continue;
+    }
+    break;
+  }
+}
+
 void ReshapeFittingSubpartitions(const BasicFormatStyle &style,
                                  TokenPartitionTree *node) {
   VLOG(4) << __FUNCTION__ << ", before:\n" << *node;
@@ -794,6 +827,11 @@
     return;
   }
 
+  CollapseHeaderFragmentsBeforeArgs(node);
+  if (node->Children().size() < 2) {
+    return;
+  }
+
   // Partition with arguments should have at least one argument
   const auto &children = node->Children();
   const auto &header = children[0];
diff --git a/verible/verilog/analysis/checkers/forbidden-macro-rule.cc b/verible/verilog/analysis/checkers/forbidden-macro-rule.cc
index 34c3da1..3410e52 100644
--- a/verible/verilog/analysis/checkers/forbidden-macro-rule.cc
+++ b/verible/verilog/analysis/checkers/forbidden-macro-rule.cc
@@ -73,7 +73,7 @@
   if (MacroCallMatcher().Matches(symbol, &manager)) {
     if (const verible::SyntaxTreeLeaf *leaf = manager.GetAsLeaf("name")) {
       const auto &imm = InvalidMacrosMap();
-      if (imm.find(std::string(leaf->get().text())) != imm.end()) {
+      if (imm.contains(std::string(leaf->get().text()))) {
         violations_.insert(
             verible::LintViolation(leaf->get(), FormatReason(*leaf), context));
       }
diff --git a/verible/verilog/analysis/checkers/forbidden-symbol-rule.cc b/verible/verilog/analysis/checkers/forbidden-symbol-rule.cc
index 04558ea..4f832c1 100644
--- a/verible/verilog/analysis/checkers/forbidden-symbol-rule.cc
+++ b/verible/verilog/analysis/checkers/forbidden-symbol-rule.cc
@@ -81,7 +81,7 @@
   if (IdMatcher().Matches(symbol, &manager)) {
     if (const verible::SyntaxTreeLeaf *leaf = manager.GetAsLeaf("name")) {
       const auto &ism = InvalidSymbolsMap();
-      if (ism.find(std::string(leaf->get().text())) != ism.end()) {
+      if (ism.contains(std::string(leaf->get().text()))) {
         violations_.insert(
             verible::LintViolation(leaf->get(), FormatReason(*leaf), context));
       }
diff --git a/verible/verilog/analysis/checkers/struct-union-name-style-rule.cc b/verible/verilog/analysis/checkers/struct-union-name-style-rule.cc
index cd31f80..0217374 100644
--- a/verible/verilog/analysis/checkers/struct-union-name-style-rule.cc
+++ b/verible/verilog/analysis/checkers/struct-union-name-style-rule.cc
@@ -106,13 +106,13 @@
             "Section with unit names need to start with digit", context));
         return;
       }
-      if (exceptions_.find(std::string(ns)) != exceptions_.end()) {
+      if (exceptions_.contains(std::string(ns))) {
         continue;  // number + unit exception found
       }
       const auto &alpha =
           std::find_if(ns.begin(), ns.end(), absl::ascii_isalpha);
       const auto ns_substr = std::string(alpha, ns.end());
-      if (exceptions_.find(ns_substr) == exceptions_.end()) {
+      if (!exceptions_.contains(ns_substr)) {
         violations_.insert(
             LintViolation(*identifier_leaf,
                           "found digit followed by unit that is "
diff --git a/verible/verilog/analysis/checkers/truncated-numeric-literal-rule.cc b/verible/verilog/analysis/checkers/truncated-numeric-literal-rule.cc
index 298bc6c..c6a17c2 100644
--- a/verible/verilog/analysis/checkers/truncated-numeric-literal-rule.cc
+++ b/verible/verilog/analysis/checkers/truncated-numeric-literal-rule.cc
@@ -18,6 +18,7 @@
 #include <cctype>
 #include <cmath>
 #include <cstddef>
+#include <numbers>
 #include <set>
 #include <string>
 #include <string_view>
@@ -136,11 +137,13 @@
       // without fully parsing the decimal number ?
       double v;
       if (absl::SimpleAtod(literal, &v) && !std::isinf(v)) {
-        return std::max(129, static_cast<int>(ceil(log(v) / log(2))));
+        return std::max(129,
+                        static_cast<int>(ceil(log(v) / std::numbers::ln2)));
       }
 
       // Uh, more than 300-ish decimal digits ? ... rough estimation it is.
-      return ceil((literal.length() - 1) * log(10) / log(2));
+      return ceil((literal.length() - 1) * std::numbers::ln10 /
+                  std::numbers::ln2);
     } break;
     default:
       break;  // unexpected base
diff --git a/verible/verilog/analysis/checkers/void-cast-rule.cc b/verible/verilog/analysis/checkers/void-cast-rule.cc
index c9d9216..658d45a 100644
--- a/verible/verilog/analysis/checkers/void-cast-rule.cc
+++ b/verible/verilog/analysis/checkers/void-cast-rule.cc
@@ -98,7 +98,7 @@
   if (FunctionMatcher().Matches(symbol, &manager)) {
     if (const verible::SyntaxTreeLeaf *function_id = manager.GetAsLeaf("id")) {
       const auto &bfs = ForbiddenFunctionsSet();
-      if (bfs.find(std::string(function_id->get().text())) != bfs.end()) {
+      if (bfs.contains(std::string(function_id->get().text()))) {
         violations_.insert(LintViolation(function_id->get(),
                                          FormatReason(*function_id), context));
       }
diff --git a/verible/verilog/analysis/flow-tree.cc b/verible/verilog/analysis/flow-tree.cc
index c5e2c23..1f28aea 100644
--- a/verible/verilog/analysis/flow-tree.cc
+++ b/verible/verilog/analysis/flow-tree.cc
@@ -139,8 +139,7 @@
   }
   auto macro_iterator = conditional_iterator + 1;
   auto macro_identifier = macro_iterator->text();
-  if (conditional_macro_id_.find(macro_identifier) ==
-      conditional_macro_id_.end()) {
+  if (!conditional_macro_id_.contains(macro_identifier)) {
     conditional_macro_id_[macro_identifier] = conditional_macros_counter_;
     conditional_macros_.push_back(macro_iterator);
     conditional_macros_counter_++;
diff --git a/verible/verilog/analysis/verilog-linter-configuration.cc b/verible/verilog/analysis/verilog-linter-configuration.cc
index 4703127..42ed8f4 100644
--- a/verible/verilog/analysis/verilog-linter-configuration.cc
+++ b/verible/verilog/analysis/verilog-linter-configuration.cc
@@ -163,7 +163,7 @@
     // same configuration file, warn the user about it.
     // NOTE: ignore producing a warning if there is no configuration, just
     // disabing/enabling
-    if (!setting.configuration.empty() && rules.count(*rule_iter)) {
+    if (!setting.configuration.empty() && rules.contains(*rule_iter)) {
       absl::StrAppend(error, error->empty() ? "" : "\n", kRepeatedFlagMessage,
                       " \"", rule_name, "\" = ", setting.configuration);
       parsed_correctly = false;
diff --git a/verible/verilog/formatting/BUILD b/verible/verilog/formatting/BUILD
index 491a5cf..1e175a2 100644
--- a/verible/verilog/formatting/BUILD
+++ b/verible/verilog/formatting/BUILD
@@ -224,7 +224,10 @@
     name = "formatter_module_test",
     srcs = ["formatter_module_test.cc"],
     deps = [
+        ":format-style",
+        ":formatter",
         ":formatter-test-utils",
+        "@abseil-cpp//absl/log",
         "@googletest//:gtest",
         "@googletest//:gtest_main",
     ],
diff --git a/verible/verilog/formatting/formatter_issue_regression_test.cc b/verible/verilog/formatting/formatter_issue_regression_test.cc
index de84377..f27b898 100644
--- a/verible/verilog/formatting/formatter_issue_regression_test.cc
+++ b/verible/verilog/formatting/formatter_issue_regression_test.cc
@@ -20,6 +20,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "verible/common/formatting/basic-format-style.h"
 #include "verible/common/util/logging.h"
 #include "verible/verilog/formatting/format-style.h"
 #include "verible/verilog/formatting/formatter-test-utils.h"
@@ -73,6 +74,23 @@
   }
 }
 
+// Regression for https://github.com/chipsalliance/verible/issues/2607:
+// A CRLF macro definition must keep a single line terminator so formatting
+// converges.
+TEST(FormatterEndToEndTest, DefineCrlfConverges) {
+  static constexpr FormatterTestCase kTestCases[] = {
+      {"`define A x.y\r\n"
+       "module m;\r\n"
+       "endmodule\r\n",
+       "`define A x.y\r\n"
+       "module m;\r\n"
+       "endmodule\r\n"},
+  };
+  FormatStyle style;
+  style.line_terminator = verible::LineTerminatorOptionStyle::kAuto;
+  RunFormatterTestCases(style, kTestCases);
+}
+
 // 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
@@ -172,6 +190,109 @@
   }
 }
 
+// Regression for https://github.com/chipsalliance/verible/issues/886:
+// Packed dimensions with $clog2()/$bits() used to split the function header
+// so ReshapeFittingSubpartitions dropped the port list.
+TEST(FormatterEndToEndTest, FunctionHeaderPackedDimSystemCallKeepsPorts) {
+  static constexpr FormatterTestCase kTestCases[] = {
+      {// Original issue sample (default column_limit 100)
+       "package foo;\n"
+       "  function some_large_return_type "
+       "[$clog2(some_large_contant_name)-1:0] "
+       "f_some_long_function( input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n",
+       "package foo;\n"
+       "  function some_large_return_type "
+       "[$clog2(some_large_contant_name)-1:0] "
+       "f_some_long_function(\n"
+       "      input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n"},
+      {// Short names still keep ports and stay on one line
+       "package foo;\n"
+       "  function logic [$clog2(N)-1:0] f(input int a, input int b);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n",
+       "package foo;\n"
+       "  function logic [$clog2(N)-1:0] f(input int a, input int b);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n"},
+      {// $bits() in packed dimensions
+       "package foo;\n"
+       "  function some_large_return_type [$bits(some_large_contant_name)-1:0] "
+       "f_some_long_function(input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n",
+       "package foo;\n"
+       "  function some_large_return_type [$bits(some_large_contant_name)-1:0] "
+       "f_some_long_function(\n"
+       "      input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n"},
+      {// Multi-argument system function in packed dimensions
+       "package foo;\n"
+       "  function some_large_return_type "
+       "[$clog2(some_large_contant_name, WIDTH)-1:0] "
+       "f_some_long_function(input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n",
+       "package foo;\n"
+       "  function some_large_return_type "
+       "[$clog2(some_large_contant_name, WIDTH)-1:0] "
+       "f_some_long_function(\n"
+       "      input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n"},
+      {// extern prototype
+       "class c;\n"
+       "  extern function some_large_return_type "
+       "[$clog2(some_large_contant_name)-1:0] "
+       "f_some_long_function(input int parameter_1, input int parameter_2);\n"
+       "endclass\n",
+       "class c;\n"
+       "  extern function some_large_return_type "
+       "[$clog2(some_large_contant_name)-1:0] "
+       "f_some_long_function(\n"
+       "      input int parameter_1, input int parameter_2);\n"
+       "endclass\n"},
+  };
+  FormatStyle style;  // default column_limit (100)
+  RunFormatterTestCases(style, kTestCases);
+}
+
+TEST(FormatterEndToEndTest, FunctionHeaderPackedDimSystemCallWrapsArgs) {
+  // Tight column limit still keeps the ports (the original bug dropped them).
+  // The header itself is longer than 40 columns, so it wraps.
+  static constexpr FormatterTestCase kTestCases[] = {
+      {"package foo;\n"
+       "  function some_large_return_type "
+       "[$clog2(some_large_contant_name)-1:0] "
+       "f_some_long_function( input int parameter_1, input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n",
+       "package foo;\n"
+       "  function\n"
+       "      some_large_return_type [$clog2(some_large_contant_name)-1\n"
+       "      :0] f_some_long_function(\n"
+       "      input int parameter_1,\n"
+       "      input int parameter_2);\n"
+       "    return 1;\n"
+       "  endfunction\n"
+       "endpackage\n"},
+  };
+  RunFormatterTestCases40(kTestCases);
+}
+
 // Regression for https://github.com/chipsalliance/verible/issues/2540:
 // Trailing EOL comment after `end` before `else if` must not change whether
 // the else-if assignment stays on one line across re-format (convergence).
@@ -244,6 +365,34 @@
   }
 }
 
+// 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)
+  RunFormatterTestCases(style, kTestCases);
+}
+
 // Regression for https://github.com/chipsalliance/verible/issues/2008
 // (also https://github.com/chipsalliance/verible/issues/2474 and
 // https://github.com/chipsalliance/verible/issues/2063):
diff --git a/verible/verilog/formatting/formatter_module_test.cc b/verible/verilog/formatting/formatter_module_test.cc
index c61da86..10d1cf8 100644
--- a/verible/verilog/formatting/formatter_module_test.cc
+++ b/verible/verilog/formatting/formatter_module_test.cc
@@ -15,8 +15,13 @@
 // Test cases in this file should be *insensitive* to wrapping penalties.
 // Penalty-sensitive tests belong in formatter-tuning_test.cc.
 
+#include <sstream>
+
+#include "absl/log/log.h"
 #include "gtest/gtest.h"
+#include "verible/verilog/formatting/format-style.h"
 #include "verible/verilog/formatting/formatter-test-utils.h"
+#include "verible/verilog/formatting/formatter.h"
 
 namespace verilog {
 namespace formatter {
diff --git a/verible/verilog/formatting/token-annotator.cc b/verible/verilog/formatting/token-annotator.cc
index d66042f..47a8669 100644
--- a/verible/verilog/formatting/token-annotator.cc
+++ b/verible/verilog/formatting/token-annotator.cc
@@ -120,6 +120,29 @@
       {NodeEnum::kConditionExpression});  // exclude
 }
 
+// '/' 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 &&
@@ -263,8 +290,7 @@
   // If a closing ')', that ends the definition name.
   if (left.TokenEnum() == verilog_tokentype::PP_Identifier &&
       right.TokenEnum() == verilog_tokentype::PP_define_body &&
-      right.Text().substr(0, 2) == "``" &&
-      right.OriginalLeadingSpaces().empty()) {
+      right.Text().starts_with("``") && right.OriginalLeadingSpaces().empty()) {
     return {0, "Preserve spacing in concatenated name"};
   }
 
diff --git a/verible/verilog/formatting/token-annotator_test.cc b/verible/verilog/formatting/token-annotator_test.cc
index c0f7e86..df27619 100644
--- a/verible/verilog/formatting/token-annotator_test.cc
+++ b/verible/verilog/formatting/token-annotator_test.cc
@@ -4788,6 +4788,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) {
diff --git a/verible/verilog/parser/verilog-lexer_test.cc b/verible/verilog/parser/verilog-lexer_test.cc
index 23d8cda..7e7c54b 100644
--- a/verible/verilog/parser/verilog-lexer_test.cc
+++ b/verible/verilog/parser/verilog-lexer_test.cc
@@ -472,6 +472,12 @@
 static std::initializer_list<LexerTestData> kMacroDefineTests = {
     {{PP_define, "`define"},
      {TK_SPACE, " "},
+     {PP_Identifier, "A"},
+     {TK_SPACE, " "},
+     {PP_define_body, "x.y"},
+     {TK_NEWLINE, "\r\n"}},
+    {{PP_define, "`define"},
+     {TK_SPACE, " "},
      {PP_Identifier, "FOO"},
      {PP_define_body, ""},
      {TK_NEWLINE, "\n"}},
diff --git a/verible/verilog/parser/verilog-lexical-context.cc b/verible/verilog/parser/verilog-lexical-context.cc
index 740e3fe..445dff7 100644
--- a/verible/verilog/parser/verilog-lexical-context.cc
+++ b/verible/verilog/parser/verilog-lexical-context.cc
@@ -46,7 +46,7 @@
        TK_endprogram, TK_endproperty, TK_endsequence, TK_endchecker,
        TK_endconnectrules, TK_enddiscipline, TK_endnature, TK_endparamset,
        TK_join, TK_join_any, TK_join_none});
-  return keywords->find(token_enum) != keywords->end();
+  return keywords->contains(token_enum);
 }
 
 void KeywordLabelStateMachine::UpdateState(int token_enum) {
diff --git a/verible/verilog/parser/verilog.lex b/verible/verilog/parser/verilog.lex
index 5c4cf3b..388d209 100644
--- a/verible/verilog/parser/verilog.lex
+++ b/verible/verilog/parser/verilog.lex
@@ -1235,7 +1235,10 @@
     yymore();
   }
   {DiscontinuedLine} {
-    yyless(yyleng-1);  /* return \n to input stream */
+    if (yyleng >= 2 && yytext[yyleng - 2] == '\r')
+      yyless(yyleng - 2);  /* return \r\n to input stream */
+    else
+      yyless(yyleng - 1);  /* return \n to input stream */
     UpdateLocation();
     yy_pop_state();
     /* Return a dummy token so the Location range of the definition (@$) spans
diff --git a/verible/verilog/preprocessor/verilog-preprocess.cc b/verible/verilog/preprocessor/verilog-preprocess.cc
index 39e816a..89ddb7d 100644
--- a/verible/verilog/preprocessor/verilog-preprocess.cc
+++ b/verible/verilog/preprocessor/verilog-preprocess.cc
@@ -535,7 +535,7 @@
   const auto &macro_name = *macro_name_extract.value();
   const bool negative_if = (*ifpos)->token_enum() == PP_ifndef;
   const auto &defs = preprocess_data_.macro_definitions;
-  const bool name_is_defined = defs.find(macro_name->text()) != defs.end();
+  const bool name_is_defined = defs.contains(macro_name->text());
   const bool condition_met = (name_is_defined ^ negative_if);
 
   if ((*ifpos)->token_enum() == PP_elsif) {