[clang] [APINotes] Upstream Sema logic to apply API Notes to decls (PR #73017)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 21 09:01:38 PST 2023
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Egor Zhdan (egorzhdan)
<details>
<summary>Changes</summary>
This upstreams more of the Clang API Notes functionality that is currently implemented in the Apple fork: https://github.com/apple/llvm-project/tree/next/clang/lib/APINotes
This is the largest chunk of the API Notes functionality in the upstreaming process. I will soon submit a follow-up patch to actually enable usage of this functionality by having a Clang driver flag that enables API Notes, along with tests.
(it might be easier to review commit-by-commit)
---
Patch is 74.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/73017.diff
19 Files Affected:
- (modified) clang/include/clang/Basic/Attr.td (+45-1)
- (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+3)
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7)
- (modified) clang/include/clang/Lex/Lexer.h (+1-1)
- (modified) clang/include/clang/Parse/Parser.h (+12)
- (modified) clang/include/clang/Sema/Sema.h (+36)
- (modified) clang/lib/Parse/ParseDecl.cpp (+65)
- (modified) clang/lib/Parse/Parser.cpp (+5)
- (modified) clang/lib/Sema/CMakeLists.txt (+1)
- (added) clang/lib/Sema/SemaAPINotes.cpp (+1014)
- (modified) clang/lib/Sema/SemaDecl.cpp (+35)
- (modified) clang/lib/Sema/SemaDeclAttr.cpp (+23-14)
- (modified) clang/lib/Sema/SemaDeclCXX.cpp (+5-1)
- (modified) clang/lib/Sema/SemaDeclObjC.cpp (+4)
- (modified) clang/lib/Sema/SemaObjCProperty.cpp (+5)
- (modified) clang/lib/Sema/SemaTemplate.cpp (+7)
- (modified) clang/lib/Sema/SemaType.cpp (+109-72)
- (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+2)
- (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+25-1)
``````````diff
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index c2fbdfc66c540d6..acfb75a3dee3e7a 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -301,6 +301,9 @@ class VariadicEnumArgument<string name, string type, list<string> values,
bit IsExternalType = isExternalType;
}
+// Represents an attribute wrapped by another attribute.
+class AttrArgument<string name, bit opt = 0> : Argument<name, opt>;
+
// This handles one spelling of an attribute.
class Spelling<string name, string variety, int version = 1> {
string Name = name;
@@ -2257,7 +2260,7 @@ def ObjCBridgeRelated : InheritableAttr {
def NSErrorDomain : InheritableAttr {
let Spellings = [GNU<"ns_error_domain">];
let Subjects = SubjectList<[Enum], ErrorDiag>;
- let Args = [DeclArgument<Var, "ErrorDomain">];
+ let Args = [IdentifierArgument<"ErrorDomain">];
let Documentation = [NSErrorDomainDocs];
}
@@ -2593,6 +2596,22 @@ def SwiftError : InheritableAttr {
let Documentation = [SwiftErrorDocs];
}
+def SwiftImportAsNonGeneric : InheritableAttr {
+ // This attribute has no spellings as it is only ever created implicitly
+ // from API notes.
+ let Spellings = [];
+ let SemaHandler = 0;
+ let Documentation = [InternalOnly];
+}
+
+def SwiftImportPropertyAsAccessors : InheritableAttr {
+ // This attribute has no spellings as it is only ever created implicitly
+ // from API notes.
+ let Spellings = [];
+ let SemaHandler = 0;
+ let Documentation = [InternalOnly];
+}
+
def SwiftName : InheritableAttr {
let Spellings = [GNU<"swift_name">];
let Args = [StringArgument<"Name">];
@@ -2614,6 +2633,31 @@ def SwiftPrivate : InheritableAttr {
let SimpleHandler = 1;
}
+def SwiftVersioned : Attr {
+ // This attribute has no spellings as it is only ever created implicitly
+ // from API notes.
+ let Spellings = [];
+ let Args = [VersionArgument<"Version">, AttrArgument<"AttrToAdd">,
+ BoolArgument<"IsReplacedByActive">];
+ let SemaHandler = 0;
+ let Documentation = [InternalOnly];
+}
+
+def SwiftVersionedRemoval : Attr {
+ // This attribute has no spellings as it is only ever created implicitly
+ // from API notes.
+ let Spellings = [];
+ let Args = [VersionArgument<"Version">, UnsignedArgument<"RawKind">,
+ BoolArgument<"IsReplacedByActive">];
+ let SemaHandler = 0;
+ let Documentation = [InternalOnly];
+ let AdditionalMembers = [{
+ attr::Kind getAttrKindToRemove() const {
+ return static_cast<attr::Kind>(getRawKind());
+ }
+ }];
+}
+
def NoDeref : TypeAttr {
let Spellings = [Clang<"noderef">];
let Documentation = [NoDerefDocs];
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index c3753ca2828e25e..02c5b9527f5d5f3 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1606,6 +1606,9 @@ def err_pragma_invalid_keyword : Error<
def err_pragma_pipeline_invalid_keyword : Error<
"invalid argument; expected 'disable'">;
+// API notes.
+def err_type_unparsed : Error<"unparsed tokens following type">;
+
// Pragma unroll support.
def warn_pragma_unroll_cuda_value_in_parens : Warning<
"argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 990692c06d7d3a8..f09f2899754690d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10667,6 +10667,13 @@ def warn_imp_cast_drops_unaligned : Warning<
} // end of sema category
+let CategoryName = "API Notes Issue" in {
+
+def err_incompatible_replacement_type : Error<
+ "API notes replacement type %0 has a different size from original type %1">;
+
+} // end of API Notes category
+
let CategoryName = "OpenMP Issue" in {
// OpenMP support.
def err_omp_expected_var_arg : Error<
diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h
index 899e665e7454652..b6ecc7e5ded9e2a 100644
--- a/clang/include/clang/Lex/Lexer.h
+++ b/clang/include/clang/Lex/Lexer.h
@@ -198,11 +198,11 @@ class Lexer : public PreprocessorLexer {
/// from. Currently this is only used by _Pragma handling.
SourceLocation getFileLoc() const { return FileLoc; }
-private:
/// Lex - Return the next token in the file. If this is the end of file, it
/// return the tok::eof token. This implicitly involves the preprocessor.
bool Lex(Token &Result);
+private:
/// Called when the preprocessor is in 'dependency scanning lexing mode'.
bool LexDependencyDirectiveToken(Token &Result);
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 465453826c0b982..034a8f90abbfaf7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3609,6 +3609,18 @@ class Parser : public CodeCompletionHandler {
ParseConceptDefinition(const ParsedTemplateInfo &TemplateInfo,
SourceLocation &DeclEnd);
+ /// Parse the given string as a type.
+ ///
+ /// This is a dangerous utility function currently employed only by API notes.
+ /// It is not a general entry-point for safely parsing types from strings.
+ ///
+ /// \param TypeStr The string to be parsed as a type.
+ /// \param Context The name of the context in which this string is being
+ /// parsed, which will be used in diagnostics.
+ /// \param IncludeLoc The location at which this parse was triggered.
+ TypeResult ParseTypeFromString(StringRef TypeStr, StringRef Context,
+ SourceLocation IncludeLoc);
+
//===--------------------------------------------------------------------===//
// Modules
DeclGroupPtrTy ParseModuleDecl(Sema::ModuleImportState &ImportState);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 59806bcbcbb2dbc..0b300df3ef5b666 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -955,6 +955,10 @@ class Sema final {
OpaqueParser = P;
}
+ /// Callback to the parser to parse a type expressed as a string.
+ std::function<TypeResult(StringRef, StringRef, SourceLocation)>
+ ParseTypeFromStringCallback;
+
class DelayedDiagnostics;
class DelayedDiagnosticsState {
@@ -3017,6 +3021,9 @@ class Sema final {
ParmVarDecl *BuildParmVarDeclForTypedef(DeclContext *DC,
SourceLocation Loc,
QualType T);
+ QualType AdjustParameterTypeForObjCAutoRefCount(QualType T,
+ SourceLocation NameLoc,
+ TypeSourceInfo *TSInfo);
ParmVarDecl *CheckParameter(DeclContext *DC, SourceLocation StartLoc,
SourceLocation NameLoc, IdentifierInfo *Name,
QualType T, TypeSourceInfo *TSInfo,
@@ -4742,6 +4749,12 @@ class Sema final {
bool checkCommonAttributeFeatures(const Stmt *S, const ParsedAttr &A,
bool SkipArgCountCheck = false);
+ /// Map any API notes provided for this declaration to attributes on the
+ /// declaration.
+ ///
+ /// Triggered by declaration-attribute processing.
+ void ProcessAPINotes(Decl *D);
+
/// Determine if type T is a valid subject for a nonnull and similar
/// attributes. By default, we look through references (the behavior used by
/// nonnull), but if the second parameter is true, then we treat a reference
@@ -4802,6 +4815,29 @@ class Sema final {
/// Valid types should not have multiple attributes with different CCs.
const AttributedType *getCallingConvAttributedType(QualType T) const;
+ /// Check whether a nullability type specifier can be added to the given
+ /// type through some means not written in source (e.g. API notes).
+ ///
+ /// \param Type The type to which the nullability specifier will be
+ /// added. On success, this type will be updated appropriately.
+ ///
+ /// \param Nullability The nullability specifier to add.
+ ///
+ /// \param DiagLoc The location to use for diagnostics.
+ ///
+ /// \param AllowArrayTypes Whether to accept nullability specifiers on an
+ /// array type (e.g., because it will decay to a pointer).
+ ///
+ /// \param OverrideExisting Whether to override an existing, locally-specified
+ /// nullability specifier rather than complaining about the conflict.
+ ///
+ /// \returns true if nullability cannot be applied, false otherwise.
+ bool CheckImplicitNullabilityTypeSpecifier(QualType &Type,
+ NullabilityKind Nullability,
+ SourceLocation DiagLoc,
+ bool AllowArrayTypes,
+ bool OverrideExisting);
+
/// Process the attributes before creating an attributed statement. Returns
/// the semantic attributes that have been processed.
void ProcessStmtAttributes(Stmt *Stmt, const ParsedAttributes &InAttrs,
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 8cb5b09fd3b0fa6..421fec3bed58534 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -8028,6 +8028,71 @@ bool Parser::TryAltiVecTokenOutOfLine(DeclSpec &DS, SourceLocation Loc,
return false;
}
+TypeResult Parser::ParseTypeFromString(StringRef TypeStr, StringRef Context,
+ SourceLocation IncludeLoc) {
+ // Consume (unexpanded) tokens up to the end-of-directive.
+ SmallVector<Token, 4> Tokens;
+ {
+ // Create a new buffer from which we will parse the type.
+ auto &SourceMgr = PP.getSourceManager();
+ FileID FID = SourceMgr.createFileID(
+ llvm::MemoryBuffer::getMemBufferCopy(TypeStr, Context), SrcMgr::C_User,
+ 0, 0, IncludeLoc);
+
+ // Form a new lexer that references the buffer.
+ Lexer L(FID, SourceMgr.getBufferOrFake(FID), PP);
+ L.setParsingPreprocessorDirective(true);
+
+ // Lex the tokens from that buffer.
+ Token Tok;
+ do {
+ L.Lex(Tok);
+ Tokens.push_back(Tok);
+ } while (Tok.isNot(tok::eod));
+ }
+
+ // Replace the "eod" token with an "eof" token identifying the end of
+ // the provided string.
+ Token &EndToken = Tokens.back();
+ EndToken.startToken();
+ EndToken.setKind(tok::eof);
+ EndToken.setLocation(Tok.getLocation());
+ EndToken.setEofData(TypeStr.data());
+
+ // Add the current token back.
+ Tokens.push_back(Tok);
+
+ // Enter the tokens into the token stream.
+ PP.EnterTokenStream(Tokens, /*DisableMacroExpansion=*/false,
+ /*IsReinject=*/false);
+
+ // Consume the current token so that we'll start parsing the tokens we
+ // added to the stream.
+ ConsumeAnyToken();
+
+ // Enter a new scope.
+ ParseScope LocalScope(this, 0);
+
+ // Parse the type.
+ TypeResult Result = ParseTypeName(nullptr);
+
+ // Check if we parsed the whole thing.
+ if (Result.isUsable() &&
+ (Tok.isNot(tok::eof) || Tok.getEofData() != TypeStr.data())) {
+ Diag(Tok.getLocation(), diag::err_type_unparsed);
+ }
+
+ // There could be leftover tokens (e.g. because of an error).
+ // Skip through until we reach the 'end of directive' token.
+ while (Tok.isNot(tok::eof))
+ ConsumeAnyToken();
+
+ // Consume the end token.
+ if (Tok.is(tok::eof) && Tok.getEofData() == TypeStr.data())
+ ConsumeAnyToken();
+ return Result;
+}
+
void Parser::DiagnoseBitIntUse(const Token &Tok) {
// If the token is for _ExtInt, diagnose it as being deprecated. Otherwise,
// the token is about _BitInt and gets (potentially) diagnosed as use of an
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 1baeb2aeb021faa..97bcbb605dcbd75 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -70,6 +70,11 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies)
PP.addCommentHandler(CommentSemaHandler.get());
PP.setCodeCompletionHandler(*this);
+
+ Actions.ParseTypeFromStringCallback =
+ [this](StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc) {
+ return this->ParseTypeFromString(TypeStr, Context, IncludeLoc);
+ };
}
DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) {
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 1856a88e9a3271a..4f72bce98fbbec3 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -27,6 +27,7 @@ add_clang_library(clangSema
Sema.cpp
SemaAccess.cpp
SemaAttr.cpp
+ SemaAPINotes.cpp
SemaAvailability.cpp
SemaCXXScopeSpec.cpp
SemaCast.cpp
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
new file mode 100644
index 000000000000000..4f2ac1acfc01a9d
--- /dev/null
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -0,0 +1,1014 @@
+//===--- SemaAPINotes.cpp - API Notes Handling ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the mapping from API notes to declaration attributes.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/APINotes/APINotesReader.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Sema/SemaInternal.h"
+
+using namespace clang;
+
+namespace {
+enum IsActive_t : bool { IsNotActive, IsActive };
+enum IsReplacement_t : bool { IsNotReplacement, IsReplacement };
+
+struct VersionedInfoMetadata {
+ /// An empty version refers to unversioned metadata.
+ VersionTuple Version;
+ unsigned IsActive : 1;
+ unsigned IsReplacement : 1;
+
+ VersionedInfoMetadata(VersionTuple Version, IsActive_t Active,
+ IsReplacement_t Replacement)
+ : Version(Version), IsActive(Active == IsActive_t::IsActive),
+ IsReplacement(Replacement == IsReplacement_t::IsReplacement) {}
+};
+} // end anonymous namespace
+
+/// Determine whether this is a multi-level pointer type.
+static bool isMultiLevelPointerType(QualType Type) {
+ QualType Pointee = Type->getPointeeType();
+ if (Pointee.isNull())
+ return false;
+
+ return Pointee->isAnyPointerType() || Pointee->isObjCObjectPointerType() ||
+ Pointee->isMemberPointerType();
+}
+
+/// Apply nullability to the given declaration.
+static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability,
+ VersionedInfoMetadata Metadata) {
+ if (!Metadata.IsActive)
+ return;
+
+ QualType Type;
+
+ // Nullability for a function/method appertains to the retain type.
+ if (auto Function = dyn_cast<FunctionDecl>(D))
+ Type = Function->getReturnType();
+ else if (auto Method = dyn_cast<ObjCMethodDecl>(D))
+ Type = Method->getReturnType();
+ else if (auto Value = dyn_cast<ValueDecl>(D))
+ Type = Value->getType();
+ else if (auto Property = dyn_cast<ObjCPropertyDecl>(D))
+ Type = Property->getType();
+ else
+ return;
+
+ // Check the nullability specifier on this type.
+ QualType OrigType = Type;
+ S.CheckImplicitNullabilityTypeSpecifier(Type, Nullability, D->getLocation(),
+ isa<ParmVarDecl>(D),
+ /*overrideExisting=*/true);
+ if (Type.getTypePtr() == OrigType.getTypePtr())
+ return;
+
+ if (auto Function = dyn_cast<FunctionDecl>(D)) {
+ const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
+ if (const FunctionProtoType *Proto = dyn_cast<FunctionProtoType>(FnType))
+ Function->setType(S.Context.getFunctionType(Type, Proto->getParamTypes(),
+ Proto->getExtProtoInfo()));
+ else
+ Function->setType(
+ S.Context.getFunctionNoProtoType(Type, FnType->getExtInfo()));
+ } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
+ Method->setReturnType(Type);
+
+ // Make it a context-sensitive keyword if we can.
+ if (!isMultiLevelPointerType(Type))
+ Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+ Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+
+ } else if (auto Value = dyn_cast<ValueDecl>(D)) {
+ Value->setType(Type);
+
+ // Make it a context-sensitive keyword if we can.
+ if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
+ if (Parm->isObjCMethodParameter() && !isMultiLevelPointerType(Type))
+ Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+ Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+ }
+ } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
+ Property->setType(Type, Property->getTypeSourceInfo());
+
+ // Make it a property attribute if we can.
+ if (!isMultiLevelPointerType(Type))
+ Property->setPropertyAttributes(
+ ObjCPropertyAttribute::kind_null_resettable);
+
+ } else
+ llvm_unreachable("cannot handle nullability here");
+}
+
+/// Copy a string into ASTContext-allocated memory.
+static StringRef CopyString(ASTContext &Ctx, StringRef String) {
+ void *mem = Ctx.Allocate(String.size(), alignof(char));
+ memcpy(mem, String.data(), String.size());
+ return StringRef(static_cast<char *>(mem), String.size());
+}
+
+static AttributeCommonInfo getDummyAttrInfo() {
+ return AttributeCommonInfo(SourceRange(),
+ AttributeCommonInfo::UnknownAttribute,
+ {AttributeCommonInfo::AS_GNU,
+ /*Spelling*/ 0, /*IsAlignas*/ false,
+ /*IsRegularKeywordAttribute*/ false});
+}
+
+namespace {
+template <typename A> struct AttrKindFor {};
+
+#define ATTR(X) \
+ template <> struct AttrKindFor<X##Attr> { \
+ static const attr::Kind value = attr::X; \
+ };
+#include "clang/Basic/AttrList.inc"
+
+/// Handle an attribute introduced by API notes.
+///
+/// \param ShouldAddAttribute Whether we should add a new attribute
+/// (otherwise, we might remove an existing attribute).
+/// \param CreateAttr Create the new attribute to be added.
+template <typename A>
+void handleAPINotedAttribute(
+ Sema &S, Decl *D, bool ShouldAddAttribute, VersionedInfoMetadata Metadata,
+ llvm::function_ref<A *()> CreateAttr,
+ llvm::function_ref<Decl::attr_iterator(const Decl *)> GetExistingAttr) {
+ if (Metadata.IsActive) {
+ auto Existing = GetExistingAttr(D);
+ if (Existing != D->attr_end()) {
+ // Remove the existing attribute, and treat it as a superseded
+ // non-versioned attribute.
+ auto *Versioned = SwiftVersionedAttr::CreateImplicit(
+ S.Context, Metadata.Version, *Existing, /*IsReplacedByActive*/ true);
+
+ D->getAttrs().erase(Existing);
+ D->addAttr(Versioned);
+ }
+
+ // If we're supposed to add a new attribute, do so.
+ if (ShouldAddAttribute) {
+ if (auto Attr = CreateAttr())
+ D->addAttr(Attr);
+ }
+
+ } else {
+ if (ShouldAddAttribute) {
+ if (auto Attr = CreateAttr()) {
+ auto *Versioned = SwiftVersionedAttr::CreateImplicit(
+ S.Context, Metadata.Version, Attr,
+ /*IsReplacedByActive*/ Metadata.IsReplacement);
+ D->addAttr(Versioned);
+ }
+ } else {
+ // FIXME: This isn't preserving enough information for things like
+ // availability, where we're trying to remove a /specific/ kind of
+ // attribute.
+ auto *Versioned = SwiftVersionedRemovalAttr::CreateImplicit(
+ S.Context, Metadata.Version, AttrKindFor<A>::value,
+ /*IsReplacedByActive*/ Metadata.IsReplacement);
+ D->addAttr(Versioned);
+ }
+ }
+}
+
+template <typename A>
+void handleAPINotedAttribute(Sema &S, Decl *D, bool ShouldAddAttribute,
+ VersionedInfoMeta...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/73017
More information about the cfe-commits
mailing list