Skip to content

Conversation

ilya-biryukov
Copy link
Contributor

Thanks to Justin Stitt for bringing this up, providing test cases and a
direction for the fix!

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Aug 1, 2025
@ilya-biryukov
Copy link
Contributor Author

This also includes changes from #151534, I will rebase it after the relevant PR lands.

@llvmbot
Copy link
Member

llvmbot commented Aug 1, 2025

@llvm/pr-subscribers-clang

Author: Ilya Biryukov (ilya-biryukov)

Changes

Thanks to Justin Stitt for bringing this up, providing test cases and a
direction for the fix!


Full diff: https://github.com/llvm/llvm-project/pull/151666.diff

3 Files Affected:

  • (modified) clang/lib/Frontend/FrontendAction.cpp (+102-18)
  • (added) clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp (+117)
  • (modified) clang/test/Frontend/dump-minimization-hints.cpp (+47-2)
diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index 1d82fc775b28a..4ceb78ee81bad 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -9,6 +9,7 @@
 #include "clang/Frontend/FrontendAction.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
 #include "clang/AST/DeclGroup.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/DiagnosticOptions.h"
@@ -38,6 +39,7 @@
 #include "clang/Serialization/ASTDeserializationListener.h"
 #include "clang/Serialization/ASTReader.h"
 #include "clang/Serialization/GlobalModuleIndex.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/BuryPointer.h"
@@ -82,17 +84,29 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
     if (!IsCollectingDecls)
       return;
     if (!D || isa<TranslationUnitDecl>(D) || isa<LinkageSpecDecl>(D) ||
-        isa<NamespaceDecl>(D)) {
+        isa<NamespaceDecl>(D) || isa<ExportDecl>(D)) {
       // These decls cover a lot of nested declarations that might not be used,
       // reducing the granularity and making the output less useful.
       return;
     }
-    if (auto *DC = D->getDeclContext(); !DC || !DC->isFileContext()) {
-      // We choose to work at namespace level to reduce complexity and the
-      // number of cases we care about.
+    if (isa<ParmVarDecl>(D)) {
+      // Parameters are covered by their functions.
       return;
     }
+    auto *DC = D->getLexicalDeclContext();
+    if (!DC || !shouldIncludeDeclsIn(DC))
+      return;
+
     PendingDecls.push_back(D);
+    for (; (isa<ExportDecl>(DC) || isa<NamespaceDecl>(DC)) &&
+           ProcessedDeclContexts.insert(DC).second;
+         DC = DC->getLexicalParent()) {
+      // Add any interesting decl contexts that we have not seen before.
+      // Note that we filter them out from DeclRead as that would include all
+      // redeclarations of namespaces, potentially those that do not have any
+      // imported declarations.
+      PendingDecls.push_back(cast<Decl>(DC));
+    }
   }
 
   struct Position {
@@ -141,23 +155,25 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
       OptionalFileEntryRef Ref;
     };
     llvm::DenseMap<const FileEntry *, FileData> FileToRanges;
+
     for (const Decl *D : PendingDecls) {
-      CharSourceRange R = SM.getExpansionRange(D->getSourceRange());
-      if (!R.isValid())
-        continue;
+      for (CharSourceRange R : getRangesToMark(D)) {
+        if (!R.isValid())
+          continue;
 
-      auto *F = SM.getFileEntryForID(SM.getFileID(R.getBegin()));
-      if (F != SM.getFileEntryForID(SM.getFileID(R.getEnd()))) {
-        // Such cases are rare and difficult to handle.
-        continue;
-      }
+        auto *F = SM.getFileEntryForID(SM.getFileID(R.getBegin()));
+        if (F != SM.getFileEntryForID(SM.getFileID(R.getEnd()))) {
+          // Such cases are rare and difficult to handle.
+          continue;
+        }
 
-      auto &Data = FileToRanges[F];
-      if (!Data.Ref)
-        Data.Ref = SM.getFileEntryRefForID(SM.getFileID(R.getBegin()));
-      Data.FromTo.push_back(
-          {Position::GetBeginSpelling(SM, R),
-           Position::GetEndSpelling(SM, R, D->getLangOpts())});
+        auto &Data = FileToRanges[F];
+        if (!Data.Ref)
+          Data.Ref = SM.getFileEntryRefForID(SM.getFileID(R.getBegin()));
+        Data.FromTo.push_back(
+            {Position::GetBeginSpelling(SM, R),
+             Position::GetEndSpelling(SM, R, D->getLangOpts())});
+      }
     }
 
     // To simplify output, merge consecutive and intersecting ranges.
@@ -188,10 +204,64 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
 
 private:
   std::vector<const Decl *> PendingDecls;
+  llvm::DenseSet<const DeclContext *> ProcessedDeclContexts;
   bool IsCollectingDecls = true;
   const SourceManager &SM;
   std::unique_ptr<llvm::raw_ostream> OS;
 
+  static bool shouldIncludeDeclsIn(const DeclContext* DC) {
+    assert(DC && "DC is null");
+    // We choose to work at namespace level to reduce complexity and the number
+    // of cases we care about.
+    // We still need to carefully handle composite declarations like
+    // `ExportDecl`.
+    for (; DC; DC = DC->getLexicalParent()) {
+      if (DC->isFileContext())
+        return true;
+      if (isa<ExportDecl>(DC))
+        continue; // Depends on the parent.
+      return false;
+    }
+    llvm_unreachable("DeclConext chain must end with a translation unit");
+  }
+  llvm::SmallVector<CharSourceRange, 2> getRangesToMark(const Decl *D) {
+    if (auto *ED = dyn_cast<ExportDecl>(D)) {
+      if (!ED->hasBraces())
+        return {SM.getExpansionRange(ED->getExportLoc())};
+
+      return {SM.getExpansionRange(SourceRange(
+                  ED->getExportLoc(),
+                  lexForLBrace(ED->getExportLoc(), D->getLangOpts()))),
+              SM.getExpansionRange(ED->getRBraceLoc())};
+    }
+
+    auto *NS = dyn_cast<NamespaceDecl>(D);
+    if (!NS)
+      return {SM.getExpansionRange(D->getSourceRange())};
+
+    SourceLocation LBraceLoc;
+    if (NS->isAnonymousNamespace()) {
+      LBraceLoc = NS->getLocation();
+    } else {
+      // Start with the location of the identifier.
+      SourceLocation TokenBeforeLBrace = NS->getLocation();
+      if (NS->hasAttrs()) {
+        for (auto *A : NS->getAttrs()) {
+          // But attributes may go after it.
+          if (SM.isBeforeInTranslationUnit(TokenBeforeLBrace,
+                                           A->getRange().getEnd())) {
+            // Give up, the attributes are often coming from macros and we
+            // cannot skip them reliably.
+            return {};
+          }
+        }
+      }
+      LBraceLoc = lexForLBrace(TokenBeforeLBrace, D->getLangOpts());
+    }
+    return {SM.getExpansionRange(SourceRange(NS->getBeginLoc(), LBraceLoc)),
+            SM.getExpansionRange(NS->getRBraceLoc())};
+  }
+
   void printJson(llvm::ArrayRef<RequiredRanges> Result) {
     *OS << "{\n";
     *OS << R"(  "required_ranges": [)" << "\n";
@@ -227,6 +297,20 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
     *OS << "  ]\n";
     *OS << "}\n";
   }
+
+  SourceLocation lexForLBrace(SourceLocation TokenBeforeLBrace,
+                              const LangOptions &LangOpts) {
+    // Now skip one token, the next should be the lbrace.
+    Token Tok;
+    if (Lexer::getRawToken(TokenBeforeLBrace, Tok, SM, LangOpts, true) ||
+        Lexer::getRawToken(Tok.getEndLoc(), Tok, SM, LangOpts, true) ||
+        Tok.getKind() != tok::l_brace) {
+      // On error or if we did not find the token we expected, avoid marking
+      // everything inside the namespace as used.
+      return SourceLocation();
+    }
+    return Tok.getLocation();
+  }
 };
 
 /// Dumps deserialized declarations.
diff --git a/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp b/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp
new file mode 100644
index 0000000000000..978af4cc7c0ad
--- /dev/null
+++ b/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp
@@ -0,0 +1,117 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++20 %t/foo.cppm -emit-module-interface -o %t/foo.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -dump-minimization-hints=%t/decls
+// RUN: cat %t/decls
+// RUN: cat %t/decls | FileCheck -check-prefix=RANGE %s
+// RANGE:{
+// RANGE-NEXT:"required_ranges": [
+// RANGE-NEXT:  {
+// RANGE-NEXT:    "file": "/usr/local/google/home/ibiryukov/code/llvm-project/build/tools/clang/test/Frontend/Output/dump-minimization-hints-cpp20-modules.cpp.tmp/foo.cppm",
+// RANGE-NEXT:    "range": [
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 3,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 3,
+// RANGE-NEXT:          "column": 22
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 3
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 9
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 10
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 43
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 6,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 6,
+// RANGE-NEXT:          "column": 2
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 7
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 8
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 25
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 9,
+// RANGE-NEXT:          "column": 3
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 9,
+// RANGE-NEXT:          "column": 36
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 11,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 11,
+// RANGE-NEXT:          "column": 2
+// RANGE-NEXT:        }
+// RANGE-NEXT:      }
+// RANGE-NEXT:    ]
+// RANGE-NEXT:  }
+// RANGE-NEXT:]
+// RANGE-NEXT:}
+
+//--- foo.cppm
+export module foo;
+
+namespace piecemeal { // line 3
+  export int used(int n) { return n + 1; }
+  export int unused(int n) { return n + 2; }
+}
+
+export namespace whole { // line 8
+  int used(int n) { return n + 1; }
+  int unused(int n) { return n + 3; }
+} // line 11
+
+//--- use.cpp
+import foo;
+
+int main() {
+  piecemeal::used(4);  // only of the functions used from each namespace.
+  whole::used(4);
+}
diff --git a/clang/test/Frontend/dump-minimization-hints.cpp b/clang/test/Frontend/dump-minimization-hints.cpp
index 273fd7f4ecd63..4c5dfbc343cd4 100644
--- a/clang/test/Frontend/dump-minimization-hints.cpp
+++ b/clang/test/Frontend/dump-minimization-hints.cpp
@@ -59,6 +59,36 @@
 // RANGE-NEXT:            "line": 23,
 // RANGE-NEXT:            "column": 2
 // RANGE-NEXT:          }
+// RANGE-NEXT:        },
+// RANGE-NEXT:        {
+// RANGE-NEXT:          "from": {
+// RANGE-NEXT:            "line": 31,
+// RANGE-NEXT:            "column": 1
+// RANGE-NEXT:          },
+// RANGE-NEXT:          "to": {
+// RANGE-NEXT:            "line": 31,
+// RANGE-NEXT:            "column": 27
+// RANGE-NEXT:          }
+// RANGE-NEXT:        },
+// RANGE-NEXT:        {
+// RANGE-NEXT:          "from": {
+// RANGE-NEXT:            "line": 32,
+// RANGE-NEXT:            "column": 3
+// RANGE-NEXT:          },
+// RANGE-NEXT:          "to": {
+// RANGE-NEXT:            "line": 32,
+// RANGE-NEXT:            "column": 12
+// RANGE-NEXT:          }
+// RANGE-NEXT:        },
+// RANGE-NEXT:        {
+// RANGE-NEXT:          "from": {
+// RANGE-NEXT:            "line": 34,
+// RANGE-NEXT:            "column": 1
+// RANGE-NEXT:          },
+// RANGE-NEXT:          "to": {
+// RANGE-NEXT:            "line": 34,
+// RANGE-NEXT:            "column": 2
+// RANGE-NEXT:          }
 // RANGE-NEXT:        }
 // RANGE-NEXT:      ]
 // RANGE-NEXT:    }
@@ -88,7 +118,7 @@ int multiply(int a, int b) {
     return a * b;
 }
 
-inline int unused_by_foo() {} // line 17
+inline void unused_by_foo() {} // line 17
 
 inline void recursively_used_by_foo() {} // line 19
 inline int used_by_foo() { // line 20
@@ -98,6 +128,20 @@ inline int used_by_foo() { // line 20
 
 struct UnusedByFoo {};
 
+namespace ns_unused_by_foo {
+  void x();
+}
+
+namespace ns_used_by_foo { // line 31
+  void x(); // line 32
+  void unused_y(); 
+} // line 34
+
+// Does not have any declarations that are used, so 
+// will not be marked as used.
+namespace ns_used_by_foo { 
+  void unused_z();
+}
 //--- foo.cpp
 #include "foo.h"
 int global_value = 5;
@@ -107,5 +151,6 @@ int main() {
   int doubled_value = multiply(current_value, 2);
   int final_result = doubled_value + global_value;
 
-  return used_by_foo();
+  used_by_foo();
+  ns_used_by_foo::x();
 }

Copy link

github-actions bot commented Aug 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@ilya-biryukov ilya-biryukov force-pushed the better-hints-cpp20-export branch 2 times, most recently from 8193cff to 6795491 Compare August 1, 2025 08:22
Copy link
Contributor

@emaxx-google emaxx-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! FWIW we don't support C++20 modules in C-Vise yet so it won't benefit at the moment, but it's good to have this in place if/once C-Vise gets this.

Copy link
Contributor

@VitaNuo VitaNuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Contributor

@abhilash1910 abhilash1910 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !

Thanks to Justin Stitt for bringing this up, providing test cases and a
direction for the fix!
@ilya-biryukov ilya-biryukov force-pushed the better-hints-cpp20-export branch from 6795491 to d1c95d8 Compare August 1, 2025 12:28
@ilya-biryukov ilya-biryukov merged commit 0cfe9f7 into llvm:main Aug 4, 2025
9 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Aug 4, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-sie-win running on sie-win-worker while building clang at step 7 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/46/builds/21165

Here is the relevant piece of the build log for the reference
Step 7 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'lld :: COFF/import_weak_alias.test' FAILED ********************
Exit Code: 3221225477

Command Output (stdout):
--
# RUN: at line 3
split-file Z:\b\llvm-clang-x86_64-sie-win\llvm-project\lld\test\COFF\import_weak_alias.test Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir
# executed command: split-file 'Z:\b\llvm-clang-x86_64-sie-win\llvm-project\lld\test\COFF\import_weak_alias.test' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir'
# note: command had no output on stdout or stderr
# RUN: at line 4
z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe --filetype=obj -triple=x86_64-windows-msvc Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/foo.s -o Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe' --filetype=obj -triple=x86_64-windows-msvc 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/foo.s' -o 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj'
# note: command had no output on stdout or stderr
# RUN: at line 5
z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe --filetype=obj -triple=x86_64-windows-msvc Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/qux.s -o Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\llvm-mc.exe' --filetype=obj -triple=x86_64-windows-msvc 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dir/qux.s' -o 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj'
# note: command had no output on stdout or stderr
# RUN: at line 6
z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj -out:Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dll -dll
# executed command: 'z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.qux.obj' 'Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.foo.obj' '-out:Z:\b\llvm-clang-x86_64-sie-win\build\tools\lld\test\COFF\Output\import_weak_alias.test.tmp.dll' -dll
# .---command stderr------------
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
# | Stack dump:
# | 0.	Program arguments: z:\\b\\llvm-clang-x86_64-sie-win\\build\\bin\\lld-link.exe Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.qux.obj Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.foo.obj -out:Z:\\b\\llvm-clang-x86_64-sie-win\\build\\tools\\lld\\test\\COFF\\Output\\import_weak_alias.test.tmp.dll -dll
# | Exception Code: 0xC0000005
# | #0 0x00007ff8b39c1b39 (C:\Windows\System32\KERNELBASE.dll+0x41b39)
# | #1 0x00007ff7dfbebb58 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xcbb58)
# | #2 0x00007ff7dfc7331b (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0x15331b)
# | #3 0x00007ff7dfbcd9ea (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xad9ea)
# | #4 0x00007ff7dfbcda54 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0xada54)
# | #5 0x00007ff7e2340a24 (z:\b\llvm-clang-x86_64-sie-win\build\bin\lld-link.exe+0x2820a24)
# | #6 0x00007ff8b6ad7ac4 (C:\Windows\System32\KERNEL32.DLL+0x17ac4)
# | #7 0x00007ff8b6ffa8c1 (C:\Windows\SYSTEM32\ntdll.dll+0x5a8c1)
# `-----------------------------
# error: command failed with exit status: 0xc0000005

--

********************


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants