https://github.com/ahatanak updated https://github.com/llvm/llvm-project/pull/168769
>From 516b90f080fe28c93e9127686773d3f72c96517b Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Wed, 19 Nov 2025 12:09:26 -0800 Subject: [PATCH 1/7] [AST] Support structural equivalence checking of attributes on Decls The structural equivalence checker currently treats any explicit attributes on a declaration as a reason to consider the declarations non-equivalent in C23 mode, even when both declarations carry the same attributes. This is unnecessarily strict and causes two otherwise equivalent declarations to be rejected just because they carry explicitly annotated attributes. This patch enables structural equivalence checking to accept selected attributes as long as the attributes on both definitions are equivalent and appear in the same order. The initial implementation adds support for three attributes: Availability, EnumExtensibility, and Unused. Additional attribute kinds can be added incrementally. This design also allows these utilities to be generated automatically by tablegen in the future. Inherited attributes that are supported are ignored when determining structural equivalence, because inherited attributes should not affect whether two definitions are structurally compatible. This patch also moves the call to CheckStructurallyEquivalentAttributes so that attribute comparison is performed between two definitions of record decls rather than between a declaration and a definition. rdar://163304242 --- clang/lib/AST/ASTStructuralEquivalence.cpp | 146 ++++++++++++++++++--- clang/test/C/C23/n3037.c | 87 ++++++++++++ 2 files changed, 217 insertions(+), 16 deletions(-) diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index da64c92221837..fc7f44c6ba563 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -93,6 +93,7 @@ #include "llvm/Support/ErrorHandling.h" #include <cassert> #include <optional> +#include <set> #include <utility> using namespace clang; @@ -451,6 +452,123 @@ class StmtComparer { }; } // namespace +namespace { +enum class AttrComparisonKind { Equal, NotEqual }; + +/// Represents the result of comparing the attribute sets on two decls. If the +/// sets are incompatible, A1/A2 point to the offending attributes. +struct AttrComparisonResult { + AttrComparisonKind Kind = AttrComparisonKind::Equal; + const Attr *A1 = nullptr, *A2 = nullptr; +}; +} // namespace + +static AttrComparisonResult +areAvailabilityAttrsEqual(const AvailabilityAttr *A1, + const AvailabilityAttr *A2) { + if (A1->getPlatform() == A2->getPlatform() && + A1->getIntroduced() == A2->getIntroduced() && + A1->getDeprecated() == A2->getDeprecated() && + A1->getObsoleted() == A2->getObsoleted() && + A1->getUnavailable() == A2->getUnavailable() && + A1->getMessage() == A2->getMessage() && + A1->getReplacement() == A2->getReplacement() && + A1->getStrict() == A2->getStrict() && + A1->getPriority() == A2->getPriority() && + A1->getEnvironment() == A2->getEnvironment()) + return {AttrComparisonKind::Equal}; + return {AttrComparisonKind::NotEqual, A1, A2}; +} + +static AttrComparisonResult +areEnumExtensibilityAttrsEqual(const EnumExtensibilityAttr *A1, + const EnumExtensibilityAttr *A2) { + if (A1->getExtensibility() == A2->getExtensibility()) + return {AttrComparisonKind::Equal}; + return {AttrComparisonKind::NotEqual, A1, A2}; +} + +static AttrComparisonResult areAttrsEqual(const Attr *A1, const Attr *A2) { + auto Kind1 = A1->getKind(), Kind2 = A2->getKind(); + if (Kind1 != Kind2) + return {AttrComparisonKind::NotEqual, A1, A2}; + + switch (Kind1) { + case attr::Availability: + return areAvailabilityAttrsEqual(cast<AvailabilityAttr>(A1), + cast<AvailabilityAttr>(A2)); + case attr::EnumExtensibility: + return areEnumExtensibilityAttrsEqual(cast<EnumExtensibilityAttr>(A1), + cast<EnumExtensibilityAttr>(A2)); + case attr::Unused: + return {AttrComparisonKind::Equal}; + default: + llvm_unreachable("unexpected attr kind"); + } +} + +static bool compareAttrKind(const Attr *A1, const Attr *A2) { + return A1->getKind() < A2->getKind(); +} + +namespace { +using AttrSet = std::multiset<const Attr *, decltype(&compareAttrKind)>; +} + +/// Collects all supported, non-inherited attributes from the given decl. +/// Returns true on success. If the decl contains any unsupported attribute, +/// returns false and sets UnsupportedAttr to point to that attribute. +static bool collectComparableAttrs(const Decl *D, AttrSet &Attrs, + const Attr *&UnsupportedAttr) { + for (const Attr *A : D->attrs()) { + switch (A->getKind()) { + case attr::Availability: + case attr::EnumExtensibility: + case attr::Unused: + if (!A->isInherited()) + Attrs.insert(A); + break; + + default: + UnsupportedAttr = A; + return false; // unsupported attribute + } + } + + return true; +} + +/// Determines whether D1 and D2 have compatible sets of attributes for the +/// purposes of structural equivalence checking. +static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1, + const Decl *D2) { + if (D1->isImplicit() || D2->isImplicit()) + return {AttrComparisonKind::Equal}; + + AttrSet A1(&compareAttrKind), A2(&compareAttrKind); + + const Attr *UnsupportedAttr1 = nullptr, *UnsupportedAttr2 = nullptr; + bool HasUnsupportedAttr1 = collectComparableAttrs(D1, A1, UnsupportedAttr1); + bool HasUnsupportedAttr2 = collectComparableAttrs(D2, A2, UnsupportedAttr2); + + if (!HasUnsupportedAttr1 || !HasUnsupportedAttr2) + return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2}; + + auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end(); + for (; I1 != E1 && I2 != E2; ++I1, ++I2) { + AttrComparisonResult R = areAttrsEqual(*I1, *I2); + if (R.Kind != AttrComparisonKind::Equal) + return R; + } + + if (I1 != E1) + return {AttrComparisonKind::NotEqual, *I1}; + if (I2 != E2) + return {AttrComparisonKind::NotEqual, nullptr, *I2}; + + return {AttrComparisonKind::Equal}; +} + static bool CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context, const Decl *D1, const Decl *D2, @@ -465,21 +583,17 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context, // the same semantic attribute, differences in attribute arguments, order // in which attributes are applied, how to merge attributes if the types are // structurally equivalent, etc. - const Attr *D1Attr = nullptr, *D2Attr = nullptr; - if (D1->hasAttrs()) - D1Attr = *D1->getAttrs().begin(); - if (D2->hasAttrs()) - D2Attr = *D2->getAttrs().begin(); - if ((D1Attr || D2Attr) && !D1->isImplicit() && !D2->isImplicit()) { + AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2); + if (R.Kind != AttrComparisonKind::Equal) { const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2); Context.Diag2(DiagnoseDecl->getLocation(), diag::warn_odr_tag_type_with_attributes) << Context.ToCtx.getTypeDeclType(DiagnoseDecl) << (PrimaryDecl != nullptr); - if (D1Attr) - Context.Diag1(D1Attr->getLoc(), diag::note_odr_attr_here) << D1Attr; - if (D2Attr) - Context.Diag1(D2Attr->getLoc(), diag::note_odr_attr_here) << D2Attr; + if (R.A1) + Context.Diag1(R.A1->getLoc(), diag::note_odr_attr_here) << R.A1; + if (R.A2) + Context.Diag1(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2; } // The above diagnostic is a warning which defaults to an error. If treated @@ -1791,12 +1905,6 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, } } - // In C23 mode, check for structural equivalence of attributes on the record - // itself. FIXME: Should this happen in C++ as well? - if (Context.LangOpts.C23 && - !CheckStructurallyEquivalentAttributes(Context, D1, D2)) - return false; - // If the records occur in different context (namespace), these should be // different. This is specially important if the definition of one or both // records is missing. In C23, different contexts do not make for a different @@ -1838,6 +1946,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, if (!D1 || !D2) return !Context.LangOpts.C23; + // In C23 mode, check for structural equivalence of attributes on the record + // itself. FIXME: Should this happen in C++ as well? + if (Context.LangOpts.C23 && + !CheckStructurallyEquivalentAttributes(Context, D1, D2)) + return false; + // If any of the records has external storage and we do a minimal check (or // AST import) we assume they are equivalent. (If we didn't have this // assumption then `RecordDecl::LoadFieldsFromExternalStorage` could trigger diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index 113ecf74d8bef..0a82e8846d92a 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -515,3 +515,90 @@ void gh149965(void) { enum E2 *eptr2; [[maybe_unused]] __typeof__(x2.h) *ptr2 = eptr2; } + +struct __attribute__((availability(ios, introduced = 14), availability(macos, introduced = 12))) AvailS0 { + // c17-note@-1 4 {{previous definition is here}} + // c23-note@-2 2 {{attribute 'availability' here}} + int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16))); + // c23-note@-1 {{attribute 'availability' here}} +}; + +struct __attribute__((availability(ios, introduced = 14), availability(macos, introduced = 12))) AvailS0 { + // c17-error@-1 {{redefinition of 'AvailS0'}} + int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16))); +}; + +// The order of the attributes matters. +struct __attribute__((availability(macos, introduced = 12), availability(ios, introduced = 14))) AvailS0 { + // c17-error@-1 {{redefinition of 'AvailS0'}} + // c23-error@-2 {{type 'struct AvailS0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'availability' here}} + int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16))); +}; + +struct __attribute__((availability(ios, introduced = 14))) [[__maybe_unused__]] AvailS0 { + // c17-error@-1 {{redefinition of 'AvailS0'}} + // c23-error@-2 {{type 'struct AvailS0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'maybe_unused' here}} + int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16))); +}; + +struct __attribute__((availability(ios, introduced = 14), availability(macos, introduced = 12))) AvailS0 { + // c17-error@-1 {{redefinition of 'AvailS0'}} + // c23-error@-2 {{type 'struct AvailS0' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + int f0 __attribute__((availability(macos, introduced = 13))); + // c23-note@-1 {{attribute 'availability' here}} +}; + +enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) AvailE0 { + // c17-note@-1 {{previous definition is here}} + // c23-note@-2 {{attribute 'warn_unused_result' here}} + A_E0 + // c17-note@-1 {{previous definition is here}} +}; + +enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) AvailE0 { + // c17-error@-1 {{redefinition of 'AvailE0'}} + // c23-error@-2 {{type 'enum AvailE0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'warn_unused_result' here}} + A_E0 + // c17-error@-1 {{redefinition of enumerator 'A_E0'}} +}; + +enum __attribute__((enum_extensibility(closed))) AvailE1 { + // c17-note@-1 {{previous definition is here}} + A_E1 + // c17-note@-1 {{previous definition is here}} +}; + +enum __attribute__((enum_extensibility(closed))) AvailE1 { + // c17-error@-1 {{redefinition of 'AvailE1'}} + A_E1 + // c17-error@-1 {{redefinition of enumerator 'A_E1'}} +}; + +struct [[__maybe_unused__]] AvailS1; + +struct __attribute__((availability(macos, introduced = 12))) AvailS1 { + // c17-note@-1 {{previous definition is here}} + int a; +}; + +struct __attribute__((availability(macos, introduced = 12))) AvailS1 { + // c17-error@-1 {{redefinition of 'AvailS1'}} + int a; +}; + +struct __attribute__((warn_unused_result)) AvailS2; +// c23-note@-1 {{attribute 'warn_unused_result' here}} + +struct __attribute__((availability(macos, introduced = 12))) AvailS2 { + // c17-note@-1 {{previous definition is here}} + int a; +}; + +struct __attribute__((availability(macos, introduced = 12))) AvailS2 { + // c17-error@-1 {{redefinition of 'AvailS2'}} + // c23-error@-2 {{type 'struct AvailS2' has an attribute which currently causes the types to be treated as though they are incompatible}} + int a; +}; >From 669be8d167b465e9ed2980e687068251a4d69024 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Wed, 19 Nov 2025 16:06:47 -0800 Subject: [PATCH 2/7] Simplify function and rename confusing variables --- clang/lib/AST/ASTStructuralEquivalence.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index fc7f44c6ba563..b2e7cea3a8180 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -516,10 +516,9 @@ using AttrSet = std::multiset<const Attr *, decltype(&compareAttrKind)>; } /// Collects all supported, non-inherited attributes from the given decl. -/// Returns true on success. If the decl contains any unsupported attribute, -/// returns false and sets UnsupportedAttr to point to that attribute. -static bool collectComparableAttrs(const Decl *D, AttrSet &Attrs, - const Attr *&UnsupportedAttr) { +/// If the decl doesn't contain any unsupported attributes, returns a nullptr, +/// otherwise returns the first unsupported attribute. +static const Attr *collectComparableAttrs(const Decl *D, AttrSet &Attrs) { for (const Attr *A : D->attrs()) { switch (A->getKind()) { case attr::Availability: @@ -528,14 +527,12 @@ static bool collectComparableAttrs(const Decl *D, AttrSet &Attrs, if (!A->isInherited()) Attrs.insert(A); break; - default: - UnsupportedAttr = A; - return false; // unsupported attribute + return A; // unsupported attribute } } - return true; + return nullptr; } /// Determines whether D1 and D2 have compatible sets of attributes for the @@ -547,11 +544,10 @@ static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1, AttrSet A1(&compareAttrKind), A2(&compareAttrKind); - const Attr *UnsupportedAttr1 = nullptr, *UnsupportedAttr2 = nullptr; - bool HasUnsupportedAttr1 = collectComparableAttrs(D1, A1, UnsupportedAttr1); - bool HasUnsupportedAttr2 = collectComparableAttrs(D2, A2, UnsupportedAttr2); + const Attr *UnsupportedAttr1 = collectComparableAttrs(D1, A1); + const Attr *UnsupportedAttr2 = collectComparableAttrs(D2, A2); - if (!HasUnsupportedAttr1 || !HasUnsupportedAttr2) + if (UnsupportedAttr1 || UnsupportedAttr2) return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2}; auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end(); @@ -563,7 +559,7 @@ static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1, if (I1 != E1) return {AttrComparisonKind::NotEqual, *I1}; - if (I2 != E2) + else if (I2 != E2) return {AttrComparisonKind::NotEqual, nullptr, *I2}; return {AttrComparisonKind::Equal}; >From b9e91d3c4fe42f523d845ff9d5527aa2374f4ca7 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Wed, 19 Nov 2025 19:31:03 -0800 Subject: [PATCH 3/7] =?UTF-8?q?Don=E2=80=99t=20use=20else=20after=20a=20re?= =?UTF-8?q?turn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clang/lib/AST/ASTStructuralEquivalence.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index b2e7cea3a8180..19dfb38a3dab6 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -559,7 +559,7 @@ static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1, if (I1 != E1) return {AttrComparisonKind::NotEqual, *I1}; - else if (I2 != E2) + if (I2 != E2) return {AttrComparisonKind::NotEqual, nullptr, *I2}; return {AttrComparisonKind::Equal}; >From 74efd1b4ed45c09903154c5386a95ec9e7ec50ea Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Mon, 22 Dec 2025 06:34:22 -0800 Subject: [PATCH 4/7] Automatically generate functions for attribute equivalence checking --- .../clang/AST/ASTStructuralEquivalence.h | 49 ++- clang/include/clang/AST/Attr.h | 4 + clang/include/clang/Basic/Attr.td | 4 +- clang/include/clang/Sema/Sema.h | 22 +- clang/lib/AST/ASTStructuralEquivalence.cpp | 178 ++++----- clang/lib/AST/AttrImpl.cpp | 82 ++++ clang/lib/AST/DeclBase.cpp | 2 +- clang/lib/Index/CommentToXML.cpp | 2 +- clang/lib/Sema/SemaAvailability.cpp | 2 +- clang/lib/Sema/SemaDeclAttr.cpp | 18 +- clang/lib/Sema/SemaExprObjC.cpp | 10 +- clang/lib/Sema/SemaHLSL.cpp | 4 +- clang/lib/Sema/SemaObjC.cpp | 2 +- clang/test/C/C23/n3037.c | 358 ++++++++++++++++-- clang/utils/TableGen/ClangAttrEmitter.cpp | 89 ++++- 15 files changed, 652 insertions(+), 174 deletions(-) diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h b/clang/include/clang/AST/ASTStructuralEquivalence.h index 5e431a14f1756..8fa21f8a223a5 100644 --- a/clang/include/clang/AST/ASTStructuralEquivalence.h +++ b/clang/include/clang/AST/ASTStructuralEquivalence.h @@ -61,6 +61,37 @@ struct StructuralEquivalenceContext { /// (which we have already complained about). NonEquivalentDeclSet &NonEquivalentDecls; + /// RAII helper used during attribute equivalence checking. While this object + /// is alive, attribute comparison uses a local queue and visited set in order + /// not to pollute the ones stored in StructuralEquivalenceContext (i.e., + /// DeclsToCheck and VisitedDecls). + struct AttrScopedAttrEquivalenceContext { + AttrScopedAttrEquivalenceContext(StructuralEquivalenceContext &Ctx) + : Ctx(Ctx), OldDeclsToCheck(Ctx.CurDeclsToCheck), + OldVisitedDecls(Ctx.CurVisitedDecls), OldComplain(Ctx.Complain) { + Ctx.CurDeclsToCheck = &LocalDeclsToCheck; + Ctx.CurVisitedDecls = &LocalVisitedDecls; + // Silence diagnostics while trying to determine whether the attributes + // are equivalent. + Ctx.Complain = false; + } + ~AttrScopedAttrEquivalenceContext(); + StructuralEquivalenceContext &Ctx; + std::queue<std::pair<Decl *, Decl *>> LocalDeclsToCheck, *OldDeclsToCheck; + llvm::DenseSet<std::pair<Decl *, Decl *>> LocalVisitedDecls, + *OldVisitedDecls; + bool OldComplain; + }; + + /// Pointers to the current decl queue and visited decl set that are being + /// used. + std::queue<std::pair<Decl *, Decl *>> *CurDeclsToCheck; + llvm::DenseSet<std::pair<Decl *, Decl *>> *CurVisitedDecls; + + bool isInAttrEquivalenceCheck() const { + return CurVisitedDecls != &VisitedDecls; + } + StructuralEquivalenceKind EqKind; /// Whether we're being strict about the spelling of types when @@ -88,7 +119,8 @@ struct StructuralEquivalenceContext { bool ErrorOnTagTypeMismatch = false, bool IgnoreTemplateParmDepth = false) : LangOpts(LangOpts), FromCtx(FromCtx), ToCtx(ToCtx), - NonEquivalentDecls(NonEquivalentDecls), EqKind(EqKind), + NonEquivalentDecls(NonEquivalentDecls), CurDeclsToCheck(&DeclsToCheck), + CurVisitedDecls(&VisitedDecls), EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling), ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain), IgnoreTemplateParmDepth(IgnoreTemplateParmDepth) {} @@ -134,7 +166,12 @@ struct StructuralEquivalenceContext { // relevant warning for the input error diagnostic. unsigned getApplicableDiagnostic(unsigned ErrorDiagnostic); + /// Iterate over the decl pairs in CurDeclsToCheck until either an + /// inequivalent pair is found or the queue is empty. + bool checkDeclQueue(); + private: + /// Finish checking all of the structural equivalences. /// /// \returns true if the equivalence check failed (non-equivalence detected), @@ -152,6 +189,16 @@ struct StructuralEquivalenceContext { bool CheckKindSpecificEquivalence(Decl *D1, Decl *D2); }; +/// Expose these functions so that they can be called by the functions that +/// check equivalence of attribute arguments. +namespace ASTStructuralEquivalence { +bool isEquivalent(StructuralEquivalenceContext &Context, QualType T1, + QualType T2); +bool isEquivalent(StructuralEquivalenceContext &Context, const Stmt *S1, + const Stmt *S2); +bool isEquivalent(const IdentifierInfo *Name1, const IdentifierInfo *Name2); +} + } // namespace clang #endif // LLVM_CLANG_AST_ASTSTRUCTURALEQUIVALENCE_H diff --git a/clang/include/clang/AST/Attr.h b/clang/include/clang/AST/Attr.h index e36184f232f8a..6c38437e88a44 100644 --- a/clang/include/clang/AST/Attr.h +++ b/clang/include/clang/AST/Attr.h @@ -40,6 +40,7 @@ class AttributeCommonInfo; class FunctionDecl; class OMPTraitInfo; class OpenACCClause; +struct StructuralEquivalenceContext; /// Attr - This represents one attribute. class Attr : public AttributeCommonInfo { @@ -112,6 +113,9 @@ class Attr : public AttributeCommonInfo { bool isLateParsed() const { return IsLateParsed; } + bool isEquivalent(const Attr &Other, + StructuralEquivalenceContext &Context) const; + // Pretty print this attribute. void printPretty(raw_ostream &OS, const PrintingPolicy &Policy) const; diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 8e5f7ef0bb82d..c02938b6295ce 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -762,6 +762,7 @@ class Attr { // Any documentation that should be associated with the attribute. Since an // attribute may be documented under multiple categories, more than one // Documentation entry may be listed. + string comparisonFn = ""; list<Documentation> Documentation; } @@ -886,6 +887,7 @@ def Aligned : InheritableAttr { Accessor<"isAlignas", [CustomKeyword<"alignas">, CustomKeyword<"_Alignas">]>, Accessor<"isDeclspec",[Declspec<"align">]>]; + let comparisonFn = "areAlignedAttrsEqual"; let Documentation = [Undocumented]; } @@ -1486,7 +1488,7 @@ def CPUSpecific : InheritableAttr { let Subjects = SubjectList<[Function]>; let Documentation = [CPUSpecificCPUDispatchDocs]; let AdditionalMembers = [{ - IdentifierInfo *getCPUName(unsigned Index) const { + const IdentifierInfo *getCPUName(unsigned Index) const { return *(cpus_begin() + Index); } }]; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index cbfcc9bc0ea99..3551ca5999f6c 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4914,12 +4914,14 @@ class Sema final : public SemaBase { bool CheckAttrTarget(const ParsedAttr &CurrAttr); bool CheckAttrNoArgs(const ParsedAttr &CurrAttr); - AvailabilityAttr *mergeAvailabilityAttr( - NamedDecl *D, const AttributeCommonInfo &CI, IdentifierInfo *Platform, - bool Implicit, VersionTuple Introduced, VersionTuple Deprecated, - VersionTuple Obsoleted, bool IsUnavailable, StringRef Message, - bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK, - int Priority, IdentifierInfo *IIEnvironment); + AvailabilityAttr * + mergeAvailabilityAttr(NamedDecl *D, const AttributeCommonInfo &CI, + const IdentifierInfo *Platform, bool Implicit, + VersionTuple Introduced, VersionTuple Deprecated, + VersionTuple Obsoleted, bool IsUnavailable, + StringRef Message, bool IsStrict, StringRef Replacement, + AvailabilityMergeKind AMK, int Priority, + const IdentifierInfo *IIEnvironment); TypeVisibilityAttr * mergeTypeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI, @@ -4950,11 +4952,11 @@ class Sema final : public SemaBase { ErrorAttr *mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI, StringRef NewUserDiagnostic); FormatAttr *mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *Format, int FormatIdx, + const IdentifierInfo *Format, int FormatIdx, int FirstArg); FormatMatchesAttr *mergeFormatMatchesAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *Format, + const IdentifierInfo *Format, int FormatIdx, StringLiteral *FormatStr); @@ -4980,8 +4982,8 @@ class Sema final : public SemaBase { void CheckAlignasUnderalignment(Decl *D); /// AddModeAttr - Adds a mode attribute to a particular declaration. - void AddModeAttr(Decl *D, const AttributeCommonInfo &CI, IdentifierInfo *Name, - bool InInstantiation = false); + void AddModeAttr(Decl *D, const AttributeCommonInfo &CI, + const IdentifierInfo *Name, bool InInstantiation = false); AlwaysInlineAttr *mergeAlwaysInlineAttr(Decl *D, const AttributeCommonInfo &CI, const IdentifierInfo *Ident); diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index 19dfb38a3dab6..d9a6d14eb02b3 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -88,12 +88,12 @@ #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/APSInt.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include <cassert> #include <optional> -#include <set> #include <utility> using namespace clang; @@ -453,116 +453,58 @@ class StmtComparer { } // namespace namespace { -enum class AttrComparisonKind { Equal, NotEqual }; - /// Represents the result of comparing the attribute sets on two decls. If the /// sets are incompatible, A1/A2 point to the offending attributes. struct AttrComparisonResult { - AttrComparisonKind Kind = AttrComparisonKind::Equal; + bool Kind = false; const Attr *A1 = nullptr, *A2 = nullptr; }; } // namespace -static AttrComparisonResult -areAvailabilityAttrsEqual(const AvailabilityAttr *A1, - const AvailabilityAttr *A2) { - if (A1->getPlatform() == A2->getPlatform() && - A1->getIntroduced() == A2->getIntroduced() && - A1->getDeprecated() == A2->getDeprecated() && - A1->getObsoleted() == A2->getObsoleted() && - A1->getUnavailable() == A2->getUnavailable() && - A1->getMessage() == A2->getMessage() && - A1->getReplacement() == A2->getReplacement() && - A1->getStrict() == A2->getStrict() && - A1->getPriority() == A2->getPriority() && - A1->getEnvironment() == A2->getEnvironment()) - return {AttrComparisonKind::Equal}; - return {AttrComparisonKind::NotEqual, A1, A2}; -} - -static AttrComparisonResult -areEnumExtensibilityAttrsEqual(const EnumExtensibilityAttr *A1, - const EnumExtensibilityAttr *A2) { - if (A1->getExtensibility() == A2->getExtensibility()) - return {AttrComparisonKind::Equal}; - return {AttrComparisonKind::NotEqual, A1, A2}; -} - -static AttrComparisonResult areAttrsEqual(const Attr *A1, const Attr *A2) { - auto Kind1 = A1->getKind(), Kind2 = A2->getKind(); - if (Kind1 != Kind2) - return {AttrComparisonKind::NotEqual, A1, A2}; - - switch (Kind1) { - case attr::Availability: - return areAvailabilityAttrsEqual(cast<AvailabilityAttr>(A1), - cast<AvailabilityAttr>(A2)); - case attr::EnumExtensibility: - return areEnumExtensibilityAttrsEqual(cast<EnumExtensibilityAttr>(A1), - cast<EnumExtensibilityAttr>(A2)); - case attr::Unused: - return {AttrComparisonKind::Equal}; - default: - llvm_unreachable("unexpected attr kind"); - } -} - -static bool compareAttrKind(const Attr *A1, const Attr *A2) { - return A1->getKind() < A2->getKind(); -} - namespace { -using AttrSet = std::multiset<const Attr *, decltype(&compareAttrKind)>; +using AttrSet = llvm::SmallVector<const Attr *, 2>; } -/// Collects all supported, non-inherited attributes from the given decl. -/// If the decl doesn't contain any unsupported attributes, returns a nullptr, -/// otherwise returns the first unsupported attribute. -static const Attr *collectComparableAttrs(const Decl *D, AttrSet &Attrs) { - for (const Attr *A : D->attrs()) { - switch (A->getKind()) { - case attr::Availability: - case attr::EnumExtensibility: - case attr::Unused: - if (!A->isInherited()) - Attrs.insert(A); - break; - default: - return A; // unsupported attribute - } - } - - return nullptr; +StructuralEquivalenceContext::AttrScopedAttrEquivalenceContext:: + ~AttrScopedAttrEquivalenceContext() { + Ctx.CurDeclsToCheck = OldDeclsToCheck; + Ctx.CurVisitedDecls = OldVisitedDecls; + Ctx.Complain = OldComplain; } /// Determines whether D1 and D2 have compatible sets of attributes for the /// purposes of structural equivalence checking. -static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1, - const Decl *D2) { +static AttrComparisonResult +areDeclAttrsEquivalent(const Decl *D1, const Decl *D2, + StructuralEquivalenceContext &Context) { if (D1->isImplicit() || D2->isImplicit()) - return {AttrComparisonKind::Equal}; + return {true}; - AttrSet A1(&compareAttrKind), A2(&compareAttrKind); + AttrSet A1, A2; - const Attr *UnsupportedAttr1 = collectComparableAttrs(D1, A1); - const Attr *UnsupportedAttr2 = collectComparableAttrs(D2, A2); + // Ignore inherited attributes. + auto RemoveInherited = [](const Attr *A) { return !A->isInherited(); }; - if (UnsupportedAttr1 || UnsupportedAttr2) - return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2}; + llvm::copy_if(D1->attrs(), std::back_inserter(A1), RemoveInherited); + llvm::copy_if(D2->attrs(), std::back_inserter(A2), RemoveInherited); auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end(); for (; I1 != E1 && I2 != E2; ++I1, ++I2) { - AttrComparisonResult R = areAttrsEqual(*I1, *I2); - if (R.Kind != AttrComparisonKind::Equal) - return R; + StructuralEquivalenceContext::AttrScopedAttrEquivalenceContext AttrCtx( + Context); + bool R = (*I1)->isEquivalent(**I2, Context); + if (R) + R = !Context.checkDeclQueue(); + if (!R) + return {false, *I1, *I2}; } if (I1 != E1) - return {AttrComparisonKind::NotEqual, *I1}; + return {false, *I1}; if (I2 != E2) - return {AttrComparisonKind::NotEqual, nullptr, *I2}; + return {false, nullptr, *I2}; - return {AttrComparisonKind::Equal}; + return {true}; } static bool @@ -579,8 +521,8 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context, // the same semantic attribute, differences in attribute arguments, order // in which attributes are applied, how to merge attributes if the types are // structurally equivalent, etc. - AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2); - if (R.Kind != AttrComparisonKind::Equal) { + AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2, Context); + if (!R.Kind) { const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2); Context.Diag2(DiagnoseDecl->getLocation(), diag::warn_odr_tag_type_with_attributes) @@ -589,7 +531,7 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context, if (R.A1) Context.Diag1(R.A1->getLoc(), diag::note_odr_attr_here) << R.A1; if (R.A2) - Context.Diag1(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2; + Context.Diag2(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2; } // The above diagnostic is a warning which defaults to an error. If treated @@ -633,8 +575,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, } /// Determine structural equivalence of two statements. -static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, - const Stmt *S1, const Stmt *S2) { +bool ASTStructuralEquivalence::isEquivalent( + StructuralEquivalenceContext &Context, const Stmt *S1, const Stmt *S2) { if (!S1 || !S2) return S1 == S2; @@ -678,15 +620,25 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return true; } +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + const Stmt *S1, const Stmt *S2) { + return ASTStructuralEquivalence::isEquivalent(Context, S1, S2); +} + /// Determine whether two identifiers are equivalent. -static bool IsStructurallyEquivalent(const IdentifierInfo *Name1, - const IdentifierInfo *Name2) { +bool ASTStructuralEquivalence::isEquivalent(const IdentifierInfo *Name1, + const IdentifierInfo *Name2) { if (!Name1 || !Name2) return Name1 == Name2; return Name1->getName() == Name2->getName(); } +static bool IsStructurallyEquivalent(const IdentifierInfo *Name1, + const IdentifierInfo *Name2) { + return ASTStructuralEquivalence::isEquivalent(Name1, Name2); +} + /// Determine whether two nested-name-specifiers are equivalent. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, NestedNameSpecifier NNS1, @@ -946,8 +898,8 @@ static bool IsEquivalentExceptionSpec(StructuralEquivalenceContext &Context, } /// Determine structural equivalence of two types. -static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, - QualType T1, QualType T2) { +bool ASTStructuralEquivalence::isEquivalent( + StructuralEquivalenceContext &Context, QualType T1, QualType T2) { if (T1.isNull() || T2.isNull()) return T1.isNull() && T2.isNull(); @@ -1602,6 +1554,11 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return true; } +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + QualType T1, QualType T2) { + return ASTStructuralEquivalence::isEquivalent(Context, T1, T2); +} + static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, VarDecl *D1, VarDecl *D2) { IdentifierInfo *Name1 = D1->getIdentifier(); @@ -1722,6 +1679,14 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, Context.ToCtx.getCanonicalTagType(Owner2)); } +/// Determine structural equivalence of two IndirectFields. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + IndirectFieldDecl *ID1, + IndirectFieldDecl *ID2) { + return IsStructurallyEquivalent(Context, ID1->getAnonField(), + ID2->getAnonField()); +} + /// Determine structural equivalence of two methods. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, CXXMethodDecl *Method1, @@ -2632,6 +2597,10 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, D1 = D1->getCanonicalDecl(); D2 = D2->getCanonicalDecl(); + + if (D1 == D2) + return true; + std::pair<Decl *, Decl *> P{D1, D2}; // Check whether we already know that these two declarations are not @@ -2641,13 +2610,18 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, return false; // Check if a check for these declarations is already pending. - // If yes D1 and D2 will be checked later (from DeclsToCheck), + // If yes D1 and D2 will be checked later (from CurDeclsToCheck), // or these are already checked (and equivalent). - bool Inserted = Context.VisitedDecls.insert(P).second; + bool Inserted = Context.CurVisitedDecls->insert(P).second; if (!Inserted) return true; - Context.DeclsToCheck.push(P); + // We can also check if the pair is in VisitedDecls if we are currently + // checking attribute equivalence. + if (Context.isInAttrEquivalenceCheck() && Context.VisitedDecls.count(P)) + return true; + + Context.CurDeclsToCheck->push(P); return true; } @@ -2826,11 +2800,11 @@ bool StructuralEquivalenceContext::CheckKindSpecificEquivalence( return true; } -bool StructuralEquivalenceContext::Finish() { - while (!DeclsToCheck.empty()) { +bool StructuralEquivalenceContext::checkDeclQueue() { + while (!CurDeclsToCheck->empty()) { // Check the next declaration. - std::pair<Decl *, Decl *> P = DeclsToCheck.front(); - DeclsToCheck.pop(); + std::pair<Decl *, Decl *> P = CurDeclsToCheck->front(); + CurDeclsToCheck->pop(); Decl *D1 = P.first; Decl *D2 = P.second; @@ -2850,3 +2824,5 @@ bool StructuralEquivalenceContext::Finish() { return false; } + +bool StructuralEquivalenceContext::Finish() { return checkDeclQueue(); } diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp index 5875a925d3fb0..3d994e06d2342 100644 --- a/clang/lib/AST/AttrImpl.cpp +++ b/clang/lib/AST/AttrImpl.cpp @@ -11,10 +11,12 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" +#include "clang/AST/ASTStructuralEquivalence.h" #include "clang/AST/Attr.h" #include "clang/AST/Expr.h" #include "clang/AST/Type.h" #include <optional> +#include <type_traits> using namespace clang; void LoopHintAttr::printPrettyPragma(raw_ostream &OS, @@ -280,4 +282,84 @@ StringLiteral *FormatMatchesAttr::getFormatString() const { return cast<StringLiteral>(getExpectedFormat()); } +namespace { +// Arguments whose types fail this test never compare equal unless there's a +// specialization of equalAttrArgs for the type. Specilization for the following +// arguments haven't been implemented yet: +// - DeclArgument +// - OMPTraitInfoArgument +// - VariadicOMPInteropInfoArgument +template <class T> constexpr bool useDefaultEquality() { + return std::is_same_v<T, StringRef> || std::is_same_v<T, VersionTuple> || + std::is_same_v<T, IdentifierInfo *> || std::is_same_v<T, ParamIdx> || + std::is_same_v<T, Attr *> || std::is_same_v<T, char *> || + std::is_enum_v<T> || std::is_integral_v<T>; +} + +template <class T> +typename std::enable_if_t<!useDefaultEquality<T>(), bool> +equalAttrArgs(T A, T B, StructuralEquivalenceContext &Context) { + return false; +} + +template <class T> +typename std::enable_if_t<useDefaultEquality<T>(), bool> +equalAttrArgs(T A1, T A2, StructuralEquivalenceContext &Context) { + return A1 == A2; +} + +template <class T> +bool equalAttrArgs(T *A1_B, T *A1_E, T *A2_B, T *A2_E, + StructuralEquivalenceContext &Context) { + if (A1_E - A1_B != A2_E - A2_B) + return false; + + for (; A1_B != A1_E; ++A1_B, ++A2_B) + if (!equalAttrArgs(*A1_B, *A2_B, Context)) + return false; + + return true; +} + +template <> +bool equalAttrArgs<Attr *>(Attr *A1, Attr *A2, + StructuralEquivalenceContext &Context) { + return A1->isEquivalent(*A2, Context); +} + +template <> +bool equalAttrArgs<Expr *>(Expr *A1, Expr *A2, + StructuralEquivalenceContext &Context) { + return ASTStructuralEquivalence::isEquivalent(Context, A1, A2); +} + +template <> +bool equalAttrArgs<QualType>(QualType T1, QualType T2, + StructuralEquivalenceContext &Context) { + return ASTStructuralEquivalence::isEquivalent(Context, T1, T2); +} + +template <> +bool equalAttrArgs<const IdentifierInfo *>( + const IdentifierInfo *Name1, const IdentifierInfo *Name2, + StructuralEquivalenceContext &Context) { + return ASTStructuralEquivalence::isEquivalent(Name1, Name2); +} + +bool areAlignedAttrsEqual(const AlignedAttr &A1, const AlignedAttr &A2, + StructuralEquivalenceContext &Context) { + if (A1.getSpelling() != A2.getSpelling()) + return false; + + if (A1.isAlignmentExpr() != A2.isAlignmentExpr()) + return false; + + if (A1.isAlignmentExpr()) + return equalAttrArgs(A1.getAlignmentExpr(), A2.getAlignmentExpr(), Context); + + return equalAttrArgs(A1.getAlignmentType()->getType(), + A2.getAlignmentType()->getType(), Context); +} +} // namespace + #include "clang/AST/AttrImpl.inc" diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index 30c6d3ed91f1e..0a1e442656c35 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -711,7 +711,7 @@ static AvailabilityResult CheckAvailability(ASTContext &Context, // Make sure that this declaration has already been introduced. if (!A->getIntroduced().empty() && EnclosingVersion < A->getIntroduced()) { - IdentifierInfo *IIEnv = A->getEnvironment(); + const IdentifierInfo *IIEnv = A->getEnvironment(); auto &Triple = Context.getTargetInfo().getTriple(); StringRef TargetEnv = Triple.getEnvironmentName(); StringRef EnvName = diff --git a/clang/lib/Index/CommentToXML.cpp b/clang/lib/Index/CommentToXML.cpp index f54d8be790217..f396760126fcc 100644 --- a/clang/lib/Index/CommentToXML.cpp +++ b/clang/lib/Index/CommentToXML.cpp @@ -1065,7 +1065,7 @@ void CommentASTToXMLConverter::visitFullComment(const FullComment *C) { if (AA->getUnavailable()) Result << "<Unavailable/>"; - IdentifierInfo *Environment = AA->getEnvironment(); + const IdentifierInfo *Environment = AA->getEnvironment(); if (Environment) { Result << "<Environment>" << Environment->getName() << "</Environment>"; } diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp index b09e1684e4e64..3624e487a7572 100644 --- a/clang/lib/Sema/SemaAvailability.cpp +++ b/clang/lib/Sema/SemaAvailability.cpp @@ -33,7 +33,7 @@ using namespace sema; static bool hasMatchingEnvironmentOrNone(const ASTContext &Context, const AvailabilityAttr *AA) { - IdentifierInfo *IIEnvironment = AA->getEnvironment(); + const IdentifierInfo *IIEnvironment = AA->getEnvironment(); auto Environment = Context.getTargetInfo().getTriple().getEnvironment(); if (!IIEnvironment || Environment == llvm::Triple::UnknownEnvironment) return true; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index e3af5023c74d0..976b90c0c1bf1 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1938,7 +1938,7 @@ static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (!AL.checkAtLeastNumArgs(S, 1)) return; - SmallVector<IdentifierInfo *, 8> CPUs; + SmallVector<const IdentifierInfo *, 8> CPUs; for (unsigned ArgNo = 0; ArgNo < getNumAttributeArgs(AL); ++ArgNo) { if (!AL.isArgIdent(ArgNo)) { S.Diag(AL.getLoc(), diag::err_attribute_argument_type) @@ -2292,7 +2292,7 @@ static void handleAttrWithMessage(Sema &S, Decl *D, const ParsedAttr &AL) { } static bool checkAvailabilityAttr(Sema &S, SourceRange Range, - IdentifierInfo *Platform, + const IdentifierInfo *Platform, VersionTuple Introduced, VersionTuple Deprecated, VersionTuple Obsoleted) { @@ -2349,11 +2349,11 @@ static bool versionsMatch(const VersionTuple &X, const VersionTuple &Y, } AvailabilityAttr *Sema::mergeAvailabilityAttr( - NamedDecl *D, const AttributeCommonInfo &CI, IdentifierInfo *Platform, + NamedDecl *D, const AttributeCommonInfo &CI, const IdentifierInfo *Platform, bool Implicit, VersionTuple Introduced, VersionTuple Deprecated, VersionTuple Obsoleted, bool IsUnavailable, StringRef Message, bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK, - int Priority, IdentifierInfo *Environment) { + int Priority, const IdentifierInfo *Environment) { VersionTuple MergedIntroduced = Introduced; VersionTuple MergedDeprecated = Deprecated; VersionTuple MergedObsoleted = Obsoleted; @@ -2381,13 +2381,13 @@ AvailabilityAttr *Sema::mergeAvailabilityAttr( continue; } - IdentifierInfo *OldPlatform = OldAA->getPlatform(); + const IdentifierInfo *OldPlatform = OldAA->getPlatform(); if (OldPlatform != Platform) { ++i; continue; } - IdentifierInfo *OldEnvironment = OldAA->getEnvironment(); + const IdentifierInfo *OldEnvironment = OldAA->getEnvironment(); if (OldEnvironment != Environment) { ++i; continue; @@ -3783,7 +3783,7 @@ ErrorAttr *Sema::mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI, } FormatAttr *Sema::mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *Format, int FormatIdx, + const IdentifierInfo *Format, int FormatIdx, int FirstArg) { // Check whether we already have an equivalent format attribute. for (auto *F : D->specific_attrs<FormatAttr>()) { @@ -3803,7 +3803,7 @@ FormatAttr *Sema::mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI, FormatMatchesAttr *Sema::mergeFormatMatchesAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *Format, + const IdentifierInfo *Format, int FormatIdx, StringLiteral *FormatStr) { // Check whether we already have an equivalent FormatMatches attribute. @@ -4757,7 +4757,7 @@ static void handleModeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { } void Sema::AddModeAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *Name, bool InInstantiation) { + const IdentifierInfo *Name, bool InInstantiation) { StringRef Str = Name->getName(); normalizeName(Str); SourceLocation AttrLoc = CI.getLoc(); diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp index 4daf01703d7dd..5dbd64b65c015 100644 --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -4007,7 +4007,7 @@ static bool CheckObjCBridgeNSCast(Sema &S, QualType castType, Expr *castExpr, while (const auto *TD = T->getAs<TypedefType>()) { TypedefNameDecl *TDNDecl = TD->getDecl(); if (TB *ObjCBAttr = getObjCBridgeAttr<TB>(TD)) { - if (IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) { + if (const IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) { HadTheAttribute = true; if (Parm->isStr("id")) return true; @@ -4070,7 +4070,7 @@ static bool CheckObjCBridgeCFCast(Sema &S, QualType castType, Expr *castExpr, while (const auto *TD = T->getAs<TypedefType>()) { TypedefNameDecl *TDNDecl = TD->getDecl(); if (TB *ObjCBAttr = getObjCBridgeAttr<TB>(TD)) { - if (IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) { + if (const IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) { HadTheAttribute = true; if (Parm->isStr("id")) return true; @@ -4228,9 +4228,9 @@ bool SemaObjC::checkObjCBridgeRelatedComponents( if (!ObjCBAttr) return false; - IdentifierInfo *RCId = ObjCBAttr->getRelatedClass(); - IdentifierInfo *CMId = ObjCBAttr->getClassMethod(); - IdentifierInfo *IMId = ObjCBAttr->getInstanceMethod(); + const IdentifierInfo *RCId = ObjCBAttr->getRelatedClass(); + const IdentifierInfo *CMId = ObjCBAttr->getClassMethod(); + const IdentifierInfo *IMId = ObjCBAttr->getInstanceMethod(); if (!RCId) return false; NamedDecl *Target = nullptr; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index e7ee3b1adf941..a17fdf9db4568 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -2579,7 +2579,7 @@ void DiagnoseHLSLAvailability::RunOnFunction(const FunctionDecl *FD) { bool DiagnoseHLSLAvailability::HasMatchingEnvironmentOrNone( const AvailabilityAttr *AA) { - IdentifierInfo *IIEnvironment = AA->getEnvironment(); + const IdentifierInfo *IIEnvironment = AA->getEnvironment(); if (!IIEnvironment) return true; @@ -2623,7 +2623,7 @@ void DiagnoseHLSLAvailability::CheckDeclAvailability(NamedDecl *D, const AvailabilityAttr *AA, SourceRange Range) { - IdentifierInfo *IIEnv = AA->getEnvironment(); + const IdentifierInfo *IIEnv = AA->getEnvironment(); if (!IIEnv) { // The availability attribute does not have environment -> it depends only diff --git a/clang/lib/Sema/SemaObjC.cpp b/clang/lib/Sema/SemaObjC.cpp index 7aaa56e37b3be..dae30b7e941d1 100644 --- a/clang/lib/Sema/SemaObjC.cpp +++ b/clang/lib/Sema/SemaObjC.cpp @@ -1468,7 +1468,7 @@ bool SemaObjC::isCFError(RecordDecl *RD) { // declared with "objc_bridge_mutable", so look for either one of the two // attributes. if (RD->getTagKind() == TagTypeKind::Struct) { - IdentifierInfo *bridgedType = nullptr; + const IdentifierInfo *bridgedType = nullptr; if (auto bridgeAttr = RD->getAttr<ObjCBridgeAttr>()) bridgedType = bridgeAttr->getBridgedType(); else if (auto bridgeAttr = RD->getAttr<ObjCBridgeMutableAttr>()) diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index 0a82e8846d92a..4200c07eba88b 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -Wall -Wno-comment -verify=both,c23 %s -// RUN: %clang_cc1 -fsyntax-only -std=c17 -pedantic -Wall -Wno-comment -Wno-c23-extensions -verify=both,c17 %s +// RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -Wall -Wno-comment -fexperimental-late-parse-attributes -verify=both,c23 %s +// RUN: %clang_cc1 -fsyntax-only -std=c17 -pedantic -Wall -Wno-comment -Wno-c23-extensions -fexperimental-late-parse-attributes -verify=both,c17 %s /* WG14 N3037: Clang 21 * Improved tag compatibility @@ -8,6 +8,8 @@ * paper made identical tag types compatible within the same TU. */ +int g0; + struct foo { int a; } p; void baz(struct foo f); // c17-note {{passing argument to parameter 'f' here}} @@ -93,14 +95,11 @@ struct [[gnu::packed]] attr_test_2 { // c17-error {{redefinition of 'attr_test_2 }; // This includes the same attribute on both types. -struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is here}} \ - c23-note {{attribute 'gnu::packed' here}} +struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is here}} int x; }; -struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 'attr_test_3'}} \ - c23-error {{type 'struct attr_test_3' has an attribute which currently causes the types to be treated as though they are incompatible}} \ - c23-note {{attribute 'gnu::packed' here}} +struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 'attr_test_3'}} int x; }; @@ -128,13 +127,12 @@ struct field_attr_test_2 { // c17-error {{redefinition of 'field_attr_test_2'}} }; struct field_attr_test_3 { // c17-note {{previous definition is here}} - [[gnu::packed]] int x; // c23-note {{attribute 'gnu::packed' here}} + [[gnu::packed]] int x; int y; }; -struct field_attr_test_3 { // c17-error {{redefinition of 'field_attr_test_3'}} \ - c23-error {{type 'struct field_attr_test_3' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} - int x [[gnu::packed]]; // c23-note {{attribute 'gnu::packed' here}} +struct field_attr_test_3 { // c17-error {{redefinition of 'field_attr_test_3'}} + int x [[gnu::packed]]; int y; }; @@ -272,18 +270,12 @@ enum Z1 { ZC = 1 }; // both-note {{previous definition is here}} enum Z2 { ZC = 1 }; // both-error {{redefinition of enumerator 'ZC'}} // Test attributes on the enumeration and enumerators. -enum [[deprecated]] enum_attr_test_1 { // c17-note {{previous definition is here}} \ - c23-note {{attribute 'deprecated' here}} - EAT1 [[deprecated]] // c17-note {{previous definition is here}} \ - c23-note {{attribute 'deprecated' here}} +enum [[deprecated]] enum_attr_test_1 { // c17-note {{previous definition is here}} + EAT1 [[deprecated]] // c17-note {{previous definition is here}} }; -enum [[deprecated]] enum_attr_test_1 { // c17-error {{redefinition of 'enum_attr_test_1'}} \ - c23-error {{type 'enum enum_attr_test_1' has an attribute which currently causes the types to be treated as though they are incompatible}} \ - c23-error {{type 'enum enum_attr_test_1' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} \ - c23-note {{attribute 'deprecated' here}} - EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}} \ - c23-note {{attribute 'deprecated' here}} +enum [[deprecated]] enum_attr_test_1 { // c17-error {{redefinition of 'enum_attr_test_1'}} + EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}} }; enum [[deprecated]] enum_attr_test_2 { // c17-note {{previous definition is here}} \ @@ -364,8 +356,18 @@ struct array { int y; int x[0]; }; // c17-error {{redefinition of 'array'}} \ struct array_2 { int y; int x[3]; }; // c17-note {{previous definition is here}} struct array_2 { int y; int x[1 + 1 + 1]; }; // c17-error {{redefinition of 'array_2'}} -struct alignment { // c17-note {{previous definition is here}} - _Alignas(int) int x; // c23-note {{attribute '_Alignas' here}} +struct alignment { // c17-note 4 {{previous definition is here}} + _Alignas(int) int x; // c23-note 2 {{attribute '_Alignas' here}} +}; + +struct alignment { // c17-error {{redefinition of 'alignment'}} + _Alignas(int) int x; +}; + +typedef int MyInt; + +struct alignment { // c17-error {{redefinition of 'alignment'}} + _Alignas(MyInt) int x; }; struct alignment { // c17-error {{redefinition of 'alignment'}} \ @@ -373,6 +375,29 @@ struct alignment { // c17-error {{redefinition of 'alignment'}} \ int x; }; +struct alignment { // c17-error {{redefinition of 'alignment'}} \ + c23-error {{type 'struct alignment' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + _Alignas(4) int x; // c23-note {{attribute '_Alignas' here}} +}; + +struct alignment2 { // c17-note 3 {{previous definition is here}} + _Alignas(4) int x; // c23-note 2 {{attribute '_Alignas' here}} +}; + +struct alignment2 { // c17-error {{redefinition of 'alignment2'}} + _Alignas(4) int x; +}; + +struct alignment2 { // c17-error {{redefinition of 'alignment2'}} \ + c23-error {{type 'struct alignment2' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + _Alignas(4 * 1) int x; // c23-note {{attribute '_Alignas' here}} +}; + +struct alignment2 { // c17-error {{redefinition of 'alignment2'}} \ + c23-error {{type 'struct alignment2' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + _Alignas(8) int x; // c23-note {{attribute '_Alignas' here}} +}; + // Both structures need to have a tag in order to be compatible within the same // translation unit. struct {int i;} nontag; @@ -552,15 +577,12 @@ struct __attribute__((availability(ios, introduced = 14), availability(macos, in enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) AvailE0 { // c17-note@-1 {{previous definition is here}} - // c23-note@-2 {{attribute 'warn_unused_result' here}} A_E0 // c17-note@-1 {{previous definition is here}} }; enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) AvailE0 { // c17-error@-1 {{redefinition of 'AvailE0'}} - // c23-error@-2 {{type 'enum AvailE0' has an attribute which currently causes the types to be treated as though they are incompatible}} - // c23-note@-3 {{attribute 'warn_unused_result' here}} A_E0 // c17-error@-1 {{redefinition of enumerator 'A_E0'}} }; @@ -577,28 +599,296 @@ enum __attribute__((enum_extensibility(closed))) AvailE1 { // c17-error@-1 {{redefinition of enumerator 'A_E1'}} }; -struct [[__maybe_unused__]] AvailS1; - -struct __attribute__((availability(macos, introduced = 12))) AvailS1 { +#pragma clang attribute push (__attribute__((availability(macos, introduced=12))), apply_to=record) +struct AvailS1 { // c17-note@-1 {{previous definition is here}} + // c23-note@-3 {{attribute 'availability' here}} int a; }; +#pragma clang attribute pop struct __attribute__((availability(macos, introduced = 12))) AvailS1 { // c17-error@-1 {{redefinition of 'AvailS1'}} + // c23-error@-2 {{type 'struct AvailS1' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'availability' here}} int a; }; -struct __attribute__((warn_unused_result)) AvailS2; -// c23-note@-1 {{attribute 'warn_unused_result' here}} - -struct __attribute__((availability(macos, introduced = 12))) AvailS2 { +struct __attribute__((availability(macos, introduced = 12, message = "abc"))) AvailS2 { // c17-note@-1 {{previous definition is here}} + // c23-note@-2 {{attribute 'availability' here}} int a; }; -struct __attribute__((availability(macos, introduced = 12))) AvailS2 { +struct __attribute__((availability(macos, introduced = 12, message = "xyz"))) AvailS2 { // c17-error@-1 {{redefinition of 'AvailS2'}} // c23-error@-2 {{type 'struct AvailS2' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'availability' here}} + int a; +}; + +struct __attribute__((availability(macos, introduced = 12, strict))) AvailS3 { + // c17-note@-1 {{previous definition is here}} + // c23-note@-2 {{attribute 'availability' here}} + int a; +}; + +struct __attribute__((availability(macos, introduced = 12))) AvailS3 { + // c17-error@-1 {{redefinition of 'AvailS3'}} + // c23-error@-2 {{type 'struct AvailS3' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'availability' here}} + int a; +}; + +struct __attribute__((availability(macos, introduced = 12))) AvailS4 { + // c17-note@-1 {{previous definition is here}} + // c23-note@-2 {{attribute 'availability' here}} + int a; +}; + +struct __attribute__((availability(ios, introduced = 12))) AvailS4 { + // c17-error@-1 {{redefinition of 'AvailS4'}} + // c23-error@-2 {{type 'struct AvailS4' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'availability' here}} + int a; +}; + +struct PreferredType0 { + // c17-note@-1 3 {{previous definition is here}} + [[clang::preferred_type(_Bool)]] int f : 1; + // c23-note@-1 {{attribute 'clang::preferred_type' here}} +}; + +struct PreferredType0 { + // c17-error@-1 {{redefinition of 'PreferredType0'}} + [[clang::preferred_type(_Bool)]] int f : 1; +}; + +typedef _Bool MyBool; + +struct PreferredType0 { + // c17-error@-1 {{redefinition of 'PreferredType0'}} + [[clang::preferred_type(MyBool)]] int f : 1; +}; + +struct PreferredType0 { + // c17-error@-1 {{redefinition of 'PreferredType0'}} + // c23-error@-2 {{type 'struct PreferredType0' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + [[clang::preferred_type(char)]] int f : 1; + // c23-note@-1 {{attribute 'clang::preferred_type' here}} +}; + +struct __attribute__((abi_tag("a", "b"))) ABITag0 { + // c17-note@-1 4 {{previous definition is here}} + // c23-note@-2 3 {{attribute 'abi_tag' here}} + int f; +}; + +struct __attribute__((abi_tag("a", "b"))) ABITag0 { + // c17-error@-1 {{redefinition of 'ABITag0'}} + int f; +}; + +struct __attribute__((abi_tag("a"))) ABITag0 { + // c17-error@-1 {{redefinition of 'ABITag0'}} + // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'abi_tag' here}} + int f; +}; + +struct __attribute__((abi_tag("a", "b", "c"))) ABITag0 { + // c17-error@-1 {{redefinition of 'ABITag0'}} + // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'abi_tag' here}} + int f; +}; + +struct __attribute__((abi_tag("a", "d"))) ABITag0 { + // c17-error@-1 {{redefinition of 'ABITag0'}} + // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'abi_tag' here}} + int f; +}; + +struct CountedBy0 { + // c17-note@-1 {{previous definition is here}} + int count; + int * __attribute__((counted_by(count))) p; +}; + +struct CountedBy0 { + // c17-error@-1 {{redefinition of 'CountedBy0'}} + int count; + int * __attribute__((counted_by(count))) p; +}; + +struct CountedBy1 { + // c17-note@-1 {{previous definition is here}} + int count0, count1; + int * __attribute__((counted_by(count0))) p; +}; + +struct CountedBy1 { + // c17-error@-1 {{redefinition of 'CountedBy1'}} + int count0, count1; + // FIXME: This should be rejected in c23. + int * __attribute__((counted_by(count1))) p; +}; + +struct __attribute__ ((lockable)) Lock { + int a; +}; + +struct GuardedBy0 { + // c17-note@-1 {{previous definition is here}} + struct Lock lock; + int b __attribute__((guarded_by(lock))); +}; + +struct GuardedBy0 { + // c17-error@-1 {{redefinition of 'GuardedBy0'}} + struct Lock lock; + int b __attribute__((guarded_by(lock))); +}; + +struct GuardedBy1 { + // c17-note@-1 {{previous definition is here}} + struct Lock lock0, lock1; + int b __attribute__((guarded_by(lock0))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct GuardedBy1 { + // c17-error@-1 {{redefinition of 'GuardedBy1'}} + // c23-error@-2 {{type 'struct GuardedBy1' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + struct Lock lock0, lock1; + int b __attribute__((guarded_by(lock1))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct Lock sharedLock0, sharedLock1; + +struct GuardedBy2 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(sharedLock0))); +}; + +struct GuardedBy2 { + // c17-error@-1 {{redefinition of 'GuardedBy2'}} + int b __attribute__((guarded_by(sharedLock0))); +}; + +struct GuardedBy3 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(sharedLock0))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct GuardedBy3 { + // c17-error@-1 {{redefinition of 'GuardedBy3'}} + // c23-error@-2 {{type 'struct GuardedBy3' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + int b __attribute__((guarded_by(sharedLock1))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct OuterLock { + struct Lock lock; +}; + +struct OuterLock outer0, outer1; + +struct GuardedBy4 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(outer0.lock))); +}; + +struct GuardedBy4 { + // c17-error@-1 {{redefinition of 'GuardedBy4'}} + int b __attribute__((guarded_by(outer0.lock))); +}; + +struct GuardedBy5 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(outer0.lock))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct GuardedBy5 { + // c17-error@-1 {{redefinition of 'GuardedBy5'}} + // c23-error@-2 {{type 'struct GuardedBy5' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + int b __attribute__((guarded_by(outer1.lock))); + // c23-note@-1 {{attribute 'guarded_by' here}} +}; + +struct GuardedBy6 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(lock))); + struct Lock lock; +}; + +struct GuardedBy6 { + // c17-error@-1 {{redefinition of 'GuardedBy6'}} + int b __attribute__((guarded_by(lock))); + struct Lock lock; +}; + +struct GuardedBy7 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(lock0))); + // c23-note@-1 {{attribute 'guarded_by' here}} + struct Lock lock0, lock1; +}; + +struct GuardedBy7 { + // c17-error@-1 {{redefinition of 'GuardedBy7'}} + // c23-error@-2 {{type 'struct GuardedBy7' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} + int b __attribute__((guarded_by(lock1))); + // c23-note@-1 {{attribute 'guarded_by' here}} + struct Lock lock0, lock1; +}; + +struct GuardedBy8 { + // c17-note@-1 {{previous definition is here}} + int b __attribute__((guarded_by(lock))); + struct { + struct Lock lock; + }; +}; + +struct GuardedBy8 { + // c17-error@-1 {{redefinition of 'GuardedBy8'}} + int b __attribute__((guarded_by(lock))); + struct { + struct Lock lock; + }; +}; + +struct __attribute__((annotate("abc", &baz, &g0))) Annotate0 { + // c17-note@-1 {{previous definition is here}} + int a; +}; + +struct __attribute__((annotate("abc", &baz, &g0))) Annotate0 { + // c17-error@-1 {{redefinition of 'Annotate0'}} + int a; +}; + +struct __attribute__((annotate("abc", &baz, &g0))) Annotate1 { + // c17-note@-1 2 {{previous definition is here}} + // c23-note@-2 2 {{attribute 'annotate' here}} + int a; +}; + +struct __attribute__((annotate("abc", &bar, &g0))) Annotate1 { + // c17-error@-1 {{redefinition of 'Annotate1'}} + // c23-error@-2 {{type 'struct Annotate1' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'annotate' here}} + int a; +}; + +struct __attribute__((annotate("abc", &baz, &g0, 2))) Annotate1 { + // c17-error@-1 {{redefinition of 'Annotate1'}} + // c23-error@-2 {{type 'struct Annotate1' has an attribute which currently causes the types to be treated as though they are incompatible}} + // c23-note@-3 {{attribute 'annotate' here}} int a; }; diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index bee9a01a3b01a..36c0b2b1c5b9c 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -130,7 +130,7 @@ static std::string ReadPCHRecord(StringRef type) { .EndsWith("Decl *", "Record.readDeclAs<" + type.drop_back().str() + ">()") .Case("TypeSourceInfo *", "Record.readTypeSourceInfo()") .Case("Expr *", "Record.readExpr()") - .Case("IdentifierInfo *", "Record.readIdentifier()") + .Case("const IdentifierInfo *", "Record.readIdentifier()") .Case("StringRef", "Record.readString()") .Case("ParamIdx", "ParamIdx::deserialize(Record.readInt())") .Case("OMPTraitInfo *", "Record.readOMPTraitInfo()") @@ -152,7 +152,7 @@ static std::string WritePCHRecord(StringRef type, StringRef name) { .Case("TypeSourceInfo *", "AddTypeSourceInfo(" + name.str() + ");\n") .Case("Expr *", "AddStmt(" + name.str() + ");\n") - .Case("IdentifierInfo *", + .Case("const IdentifierInfo *", "AddIdentifierRef(" + name.str() + ");\n") .Case("StringRef", "AddString(" + name.str() + ");\n") .Case("ParamIdx", "push_back(" + name.str() + ".serialize());\n") @@ -280,6 +280,13 @@ namespace { virtual void writeImplicitCtorArgs(raw_ostream &OS) const { OS << getUpperName(); } + + constexpr StringRef getArgEqualityFn() const { return "equalAttrArgs"; } + + virtual std::string emitAttrArgEqualityCheck() const { + std::string S = std::string("get") + std::string(getUpperName()) + "()"; + return getArgEqualityFn().str() + "(" + S + ", Other." + S + ", Context)"; + } }; class SimpleArgument : public Argument { @@ -340,7 +347,7 @@ namespace { return ((subject == list) || ...); }; - if (IsOneOf(type, "IdentifierInfo *", "Expr *")) + if (IsOneOf(type, "const IdentifierInfo *", "Expr *")) return "!get" + getUpperName().str() + "()"; if (IsOneOf(type, "TypeSourceInfo *")) return "!get" + getUpperName().str() + "Loc()"; @@ -356,7 +363,7 @@ namespace { if (type == "FunctionDecl *") OS << "\" << get" << getUpperName() << "()->getNameInfo().getAsString() << \""; - else if (type == "IdentifierInfo *") + else if (type == "const IdentifierInfo *") // Some non-optional (comma required) identifier arguments can be the // empty string but are then recorded as a nullptr. OS << "\" << (get" << getUpperName() << "() ? get" << getUpperName() @@ -375,7 +382,7 @@ namespace { if (StringRef(type).ends_with("Decl *")) { OS << " OS << \" \";\n"; OS << " dumpBareDeclRef(SA->get" << getUpperName() << "());\n"; - } else if (type == "IdentifierInfo *") { + } else if (type == "const IdentifierInfo *") { // Some non-optional (comma required) identifier arguments can be the // empty string but are then recorded as a nullptr. OS << " if (SA->get" << getUpperName() << "())\n" @@ -679,6 +686,17 @@ namespace { void writeHasChildren(raw_ostream &OS) const override { OS << "SA->is" << getUpperName() << "Expr()"; } + + std::string emitAttrArgEqualityCheck() const override { + auto GetStr = [&](bool Other) { + std::string CtxStr = Other ? "Context.ToCtx" : "Context.FromCtx"; + std::string S = std::string("get") + std::string(getUpperName()) + "(" + + CtxStr + ")"; + return S; + }; + return getArgEqualityFn().str() + "(" + GetStr(false) + ", Other." + + GetStr(true) + ", Context)"; + } }; class VariadicArgument : public Argument { @@ -836,6 +854,19 @@ namespace { OS << " for (const auto &Val : SA->" << RangeName << "())\n"; writeDumpImpl(OS); } + + std::string emitAttrArgEqualityCheck() const override { + auto GenIter = [&](bool IsOther, const std::string &Suffix) { + std::string S = IsOther ? "Other." : ""; + std::string LN = getLowerName().str(); + S += LN + "_" + Suffix + "()"; + return S; + }; + + return getArgEqualityFn().str() + "(" + GenIter(false, "begin") + ", " + + GenIter(false, "end") + ", " + GenIter(true, "begin") + ", " + + GenIter(true, "end") + ", Context)"; + } }; class VariadicOMPInteropInfoArgument : public VariadicArgument { @@ -1371,7 +1402,7 @@ namespace { class VariadicIdentifierArgument : public VariadicArgument { public: VariadicIdentifierArgument(const Record &Arg, StringRef Attr) - : VariadicArgument(Arg, Attr, "IdentifierInfo *") + : VariadicArgument(Arg, Attr, "const IdentifierInfo *") {} }; @@ -1481,7 +1512,7 @@ createArgument(const Record &Arg, StringRef Attr, Ptr = std::make_unique<SimpleArgument>( Arg, Attr, (Arg.getValueAsDef("Kind")->getName() + "Decl *").str()); else if (ArgName == "IdentifierArgument") - Ptr = std::make_unique<SimpleArgument>(Arg, Attr, "IdentifierInfo *"); + Ptr = std::make_unique<SimpleArgument>(Arg, Attr, "const IdentifierInfo *"); else if (ArgName == "DefaultBoolArgument") Ptr = std::make_unique<DefaultSimpleArgument>( Arg, Attr, "bool", Arg.getValueAsBit("Default")); @@ -3148,6 +3179,28 @@ static void emitAttributes(const RecordKeeper &Records, raw_ostream &OS, OS, Header); } + std::string FnStr = "isEquivalent(const "; + FnStr += R.getName(); + FnStr += "Attr &Other, StructuralEquivalenceContext &Context) const"; + if (Header) { + OS << " bool " << FnStr << ";\n"; + } else { + OS << "bool " << R.getName() << "Attr::" << FnStr << " {\n"; + std::string CustomFn = R.getValueAsString("comparisonFn").str(); + if (CustomFn.empty()) { + if (!ElideSpelling) + OS << " if (getSpelling() != Other.getSpelling()) return false;\n\n"; + for (const auto &ai : Args) { + OS << " if (!" << ai->emitAttrArgEqualityCheck() << ")\n"; + OS << " return false;\n"; + } + OS << " return true;\n"; + } else { + OS << " return " + CustomFn + "(*this, Other, Context);\n"; + } + OS << "}\n\n"; + } + if (Header) { if (DelayedArgs) { DelayedArgs->writeAccessors(OS); @@ -3200,6 +3253,26 @@ void clang::EmitClangAttrClass(const RecordKeeper &Records, raw_ostream &OS) { OS << "#endif // LLVM_CLANG_ATTR_CLASSES_INC\n"; } +static void emitEquivalenceFunction(const RecordKeeper &Records, + raw_ostream &OS) { + OS << "bool Attr::isEquivalent(const Attr &Other, " + "StructuralEquivalenceContext &Context) const {\n"; + OS << "if (getKind() != Other.getKind()) return false;\n\n"; + OS << " switch (getKind()) {\n"; + for (const auto *Attr : Records.getAllDerivedDefinitions("Attr")) { + const Record &R = *Attr; + if (!R.getValueAsBit("ASTNode")) + continue; + + OS << " case attr::" << R.getName() << ":\n"; + OS << " return cast<" << R.getName() << "Attr>(this)->isEquivalent(cast<" + << R.getName() << "Attr>(Other), Context);\n"; + } + OS << " }\n"; + OS << " llvm_unreachable(\"Unexpected attribute kind!\");\n"; + OS << "}\n\n"; +} + // Emits the class method definitions for attributes. void clang::EmitClangAttrImpl(const RecordKeeper &Records, raw_ostream &OS) { emitSourceFileHeader("Attribute classes' member function definitions", OS, @@ -3234,6 +3307,8 @@ void clang::EmitClangAttrImpl(const RecordKeeper &Records, raw_ostream &OS) { OS << "void Attr::printPretty(raw_ostream &OS, " "const PrintingPolicy &Policy) const {\n"; EmitFunc("printPretty(OS, Policy)"); + + emitEquivalenceFunction(Records, OS); } static void emitAttrList(raw_ostream &OS, StringRef Class, >From d30dd3d14a1eee675b40a517f7a3a411355a747b Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Mon, 22 Dec 2025 06:44:42 -0800 Subject: [PATCH 5/7] Constify IdentifierInfo in more places --- clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Sema/SemaDeclAttr.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index f3837090ac349..c9ad6860dc625 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4960,7 +4960,7 @@ class Sema final : public SemaBase { StringLiteral *FormatStr); ModularFormatAttr *mergeModularFormatAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *ModularImplFn, + const IdentifierInfo *ModularImplFn, StringRef ImplName, MutableArrayRef<StringRef> Aspects); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 71bbc938aa498..263ce2118ba86 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7004,7 +7004,7 @@ static void handleVTablePointerAuthentication(Sema &S, Decl *D, } static bool modularFormatAttrsEquiv(const ModularFormatAttr *Existing, - IdentifierInfo *ModularImplFn, + const IdentifierInfo *ModularImplFn, StringRef ImplName, ArrayRef<StringRef> Aspects) { return Existing->getModularImplFn() == ModularImplFn && @@ -7013,10 +7013,9 @@ static bool modularFormatAttrsEquiv(const ModularFormatAttr *Existing, llvm::equal(Existing->aspects(), Aspects); } -ModularFormatAttr * -Sema::mergeModularFormatAttr(Decl *D, const AttributeCommonInfo &CI, - IdentifierInfo *ModularImplFn, StringRef ImplName, - MutableArrayRef<StringRef> Aspects) { +ModularFormatAttr *Sema::mergeModularFormatAttr( + Decl *D, const AttributeCommonInfo &CI, const IdentifierInfo *ModularImplFn, + StringRef ImplName, MutableArrayRef<StringRef> Aspects) { if (const auto *Existing = D->getAttr<ModularFormatAttr>()) { if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) { Diag(Existing->getLocation(), diag::err_duplicate_attribute) << *Existing; >From aa84c22c1df74520502e126b08f646e149f873c9 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Tue, 23 Dec 2025 09:30:04 -0800 Subject: [PATCH 6/7] Fix indentation --- clang/utils/TableGen/ClangAttrEmitter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 36c0b2b1c5b9c..61fb40e79ea91 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -1402,8 +1402,7 @@ namespace { class VariadicIdentifierArgument : public VariadicArgument { public: VariadicIdentifierArgument(const Record &Arg, StringRef Attr) - : VariadicArgument(Arg, Attr, "const IdentifierInfo *") - {} + : VariadicArgument(Arg, Attr, "const IdentifierInfo *") {} }; class VariadicStringArgument : public VariadicArgument { >From 1c52325d69f6f495fb745b50c2a6af409137fec9 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka <[email protected]> Date: Tue, 23 Dec 2025 09:37:15 -0800 Subject: [PATCH 7/7] Fix clang-format error --- clang/include/clang/AST/ASTStructuralEquivalence.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h b/clang/include/clang/AST/ASTStructuralEquivalence.h index 8fa21f8a223a5..2b56273f20371 100644 --- a/clang/include/clang/AST/ASTStructuralEquivalence.h +++ b/clang/include/clang/AST/ASTStructuralEquivalence.h @@ -171,7 +171,6 @@ struct StructuralEquivalenceContext { bool checkDeclQueue(); private: - /// Finish checking all of the structural equivalences. /// /// \returns true if the equivalence check failed (non-equivalence detected), @@ -197,7 +196,7 @@ bool isEquivalent(StructuralEquivalenceContext &Context, QualType T1, bool isEquivalent(StructuralEquivalenceContext &Context, const Stmt *S1, const Stmt *S2); bool isEquivalent(const IdentifierInfo *Name1, const IdentifierInfo *Name2); -} +} // namespace ASTStructuralEquivalence } // namespace clang _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
