[clang] a4ceac7 - [C23] Implement WG14 N3037 (#132939)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 2 06:19:25 PDT 2025
Author: Aaron Ballman
Date: 2025-05-02T09:19:21-04:00
New Revision: a4ceac7e3e04c32cb3e334eb89b54d8e99a298f8
URL: https://github.com/llvm/llvm-project/commit/a4ceac7e3e04c32cb3e334eb89b54d8e99a298f8
DIFF: https://github.com/llvm/llvm-project/commit/a4ceac7e3e04c32cb3e334eb89b54d8e99a298f8.diff
LOG: [C23] Implement WG14 N3037 (#132939)
This changes the type compatibility rules so that it is permitted to
redefine tag types within the same TU so long as they are equivalent
definitions.
It is intentionally not being exposed as an extension in older C
language modes. GCC does not do so and the feature doesn't seem
compelling enough to warrant it.
Added:
clang/test/C/C23/n3037.c
clang/test/C/C23/n3037_1.c
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/ASTContext.h
clang/include/clang/AST/ASTStructuralEquivalence.h
clang/include/clang/Basic/DiagnosticASTKinds.td
clang/include/clang/Parse/Parser.h
clang/include/clang/Sema/Sema.h
clang/lib/AST/ASTContext.cpp
clang/lib/AST/ASTImporter.cpp
clang/lib/AST/ASTStructuralEquivalence.cpp
clang/lib/Parse/ParseDecl.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaType.cpp
clang/lib/Serialization/ASTReader.cpp
clang/lib/Serialization/ASTReaderDecl.cpp
clang/test/ASTMerge/struct/test.c
clang/test/C/drs/dr1xx.c
clang/unittests/AST/StructuralEquivalenceTest.cpp
clang/www/c_status.html
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 6652ee9b8ed4b..5d6c986bbcb1a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -249,6 +249,10 @@ C23 Feature Support
scope.
- Fixed a bug where you could not cast a null pointer constant to type
``nullptr_t``. Fixes #GH133644.
+- Implemented `WG14 N3037 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf>`_
+ which allows tag types to be redefined within the same translation unit so
+ long as both definitions are structurally equivalent (same tag types, same
+ tag names, same tag members, etc).
- Fixed a failed assertion with an invalid parameter to the ``#embed``
directive. Fixes #GH126940.
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 3c78833a3f069..50083b055199e 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3147,6 +3147,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
QualType mergeTransparentUnionType(QualType, QualType,
bool OfBlockPointer=false,
bool Unqualified = false);
+ QualType mergeTagDefinitions(QualType, QualType);
QualType mergeObjCGCQualifiers(QualType, QualType);
diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h b/clang/include/clang/AST/ASTStructuralEquivalence.h
index b0caded2f49a6..5e431a14f1756 100644
--- a/clang/include/clang/AST/ASTStructuralEquivalence.h
+++ b/clang/include/clang/AST/ASTStructuralEquivalence.h
@@ -43,6 +43,9 @@ struct StructuralEquivalenceContext {
/// key: (from, to, IgnoreTemplateParmDepth)
using NonEquivalentDeclSet = llvm::DenseSet<std::tuple<Decl *, Decl *, int>>;
+ /// The language options to use for making a structural equivalence check.
+ const LangOptions &LangOpts;
+
/// AST contexts for which we are checking structural equivalence.
ASTContext &FromCtx, &ToCtx;
@@ -76,15 +79,17 @@ struct StructuralEquivalenceContext {
/// Whether to ignore comparing the depth of template param(TemplateTypeParm)
bool IgnoreTemplateParmDepth;
- StructuralEquivalenceContext(ASTContext &FromCtx, ASTContext &ToCtx,
+ StructuralEquivalenceContext(const LangOptions &LangOpts, ASTContext &FromCtx,
+ ASTContext &ToCtx,
NonEquivalentDeclSet &NonEquivalentDecls,
StructuralEquivalenceKind EqKind,
bool StrictTypeSpelling = false,
bool Complain = true,
bool ErrorOnTagTypeMismatch = false,
bool IgnoreTemplateParmDepth = false)
- : FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls),
- EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling),
+ : LangOpts(LangOpts), FromCtx(FromCtx), ToCtx(ToCtx),
+ NonEquivalentDecls(NonEquivalentDecls), EqKind(EqKind),
+ StrictTypeSpelling(StrictTypeSpelling),
ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain),
IgnoreTemplateParmDepth(IgnoreTemplateParmDepth) {}
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index f73963752bb67..d2cd86d05d55a 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -478,16 +478,23 @@ def warn_odr_function_type_inconsistent : Warning<
"external function %0 declared with incompatible types in
diff erent "
"translation units (%1 vs. %2)">,
InGroup<ODR>;
+def warn_odr_tag_type_with_attributes : Warning<
+ "type %0 has %select{an attribute|a member with an attribute}1 which "
+ "currently causes the types to be treated as though they are incompatible">,
+ InGroup<ODR>, DefaultError;
+def note_odr_attr_here : Note<"attribute %0 here">;
def err_odr_tag_type_inconsistent
- : Error<"type %0 has incompatible definitions in
diff erent translation "
- "units">;
+ : Error<"type %0 has incompatible definitions%select{| in
diff erent "
+ "translation units}1">;
def warn_odr_tag_type_inconsistent
- : Warning<"type %0 has incompatible definitions in
diff erent translation "
- "units">,
+ : Warning<"type %0 has incompatible definitions%select{| in
diff erent "
+ "translation units}1">,
InGroup<ODR>;
def note_odr_tag_kind_here: Note<
"%0 is a %select{struct|interface|union|class|enum}1 here">;
def note_odr_field : Note<"field %0 has type %1 here">;
+def note_odr_field_bit_width : Note<"bit-field %0 has bit-width %1 here">;
+def note_odr_field_not_bit_field : Note<"field %0 is not a bit-field">;
def note_odr_field_name : Note<"field has name %0 here">;
def note_odr_missing_field : Note<"no corresponding field here">;
def note_odr_base : Note<"class has base type %0">;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 84d46136ff574..00e4b980bf44a 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2579,7 +2579,8 @@ class Parser : public CodeCompletionHandler {
void ParseEnumSpecifier(SourceLocation TagLoc, DeclSpec &DS,
const ParsedTemplateInfo &TemplateInfo,
AccessSpecifier AS, DeclSpecContext DSC);
- void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl);
+ void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl,
+ SkipBodyInfo *SkipBody = nullptr);
void ParseStructUnionBody(SourceLocation StartLoc, DeclSpec::TST TagType,
RecordDecl *TagDecl);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index dbb4a954cfb2a..19343eb0af092 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4346,7 +4346,8 @@ class Sema final : public SemaBase {
Decl *ActOnEnumConstant(Scope *S, Decl *EnumDecl, Decl *LastEnumConstant,
SourceLocation IdLoc, IdentifierInfo *Id,
const ParsedAttributesView &Attrs,
- SourceLocation EqualLoc, Expr *Val);
+ SourceLocation EqualLoc, Expr *Val,
+ SkipBodyInfo *SkipBody = nullptr);
void ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
Decl *EnumDecl, ArrayRef<Decl *> Elements, Scope *S,
const ParsedAttributesView &Attr);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c95e733f30494..ae136ae271882 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16,6 +16,7 @@
#include "clang/AST/APValue.h"
#include "clang/AST/ASTConcept.h"
#include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Attr.h"
#include "clang/AST/AttrIterator.h"
@@ -11443,6 +11444,22 @@ static QualType mergeEnumWithInteger(ASTContext &Context, const EnumType *ET,
return {};
}
+QualType ASTContext::mergeTagDefinitions(QualType LHS, QualType RHS) {
+ // C17 and earlier and C++ disallow two tag definitions within the same TU
+ // from being compatible.
+ if (LangOpts.CPlusPlus || !LangOpts.C23)
+ return {};
+
+ // C23, on the other hand, requires the members to be "the same enough", so
+ // we use a structural equivalence check.
+ StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
+ StructuralEquivalenceContext Ctx(
+ getLangOpts(), *this, *this, NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false, /*ErrorOnTagTypeMismatch=*/true);
+ return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
+}
+
QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
bool Unqualified, bool BlockReturnType,
bool IsConditionalOperator) {
@@ -11740,7 +11757,7 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
/*AllowCXX=*/false, IsConditionalOperator);
case Type::Record:
case Type::Enum:
- return {};
+ return mergeTagDefinitions(LHS, RHS);
case Type::Builtin:
// Only exactly equal builtin types are compatible, which is tested above.
return {};
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 00628602e61fa..eb0e6866e367b 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -2482,8 +2482,9 @@ bool ASTNodeImporter::IsStructuralMatch(Decl *From, Decl *To, bool Complain,
}
StructuralEquivalenceContext Ctx(
- Importer.getFromContext(), Importer.getToContext(),
- Importer.getNonEquivalentDecls(), getStructuralEquivalenceKind(Importer),
+ Importer.getToContext().getLangOpts(), Importer.getFromContext(),
+ Importer.getToContext(), Importer.getNonEquivalentDecls(),
+ getStructuralEquivalenceKind(Importer),
/*StrictTypeSpelling=*/false, Complain, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
return Ctx.IsEquivalent(From, To);
@@ -4341,7 +4342,8 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- FD1->getASTContext(), FD2->getASTContext(), NonEquivalentDecls,
+ Importer.getToContext().getLangOpts(), FD1->getASTContext(),
+ FD2->getASTContext(), NonEquivalentDecls,
StructuralEquivalenceKind::Default,
/* StrictTypeSpelling = */ false, /* Complain = */ false);
return Ctx.IsEquivalent(FD1, FD2);
@@ -10540,8 +10542,8 @@ bool ASTImporter::IsStructurallyEquivalent(QualType From, QualType To,
}
}
- StructuralEquivalenceContext Ctx(FromContext, ToContext, NonEquivalentDecls,
- getStructuralEquivalenceKind(*this), false,
- Complain);
+ StructuralEquivalenceContext Ctx(
+ getToContext().getLangOpts(), FromContext, ToContext, NonEquivalentDecls,
+ getStructuralEquivalenceKind(*this), false, Complain);
return Ctx.IsEquivalent(From, To);
}
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index b8db635a2b890..8d9960b456278 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -61,6 +61,7 @@
#include "clang/AST/ASTStructuralEquivalence.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
@@ -111,6 +112,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
NestedNameSpecifier *NNS1,
NestedNameSpecifier *NNS2);
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+ const Attr *Attr1, const Attr *Attr2);
static bool IsStructurallyEquivalent(const IdentifierInfo *Name1,
const IdentifierInfo *Name2);
@@ -450,6 +453,41 @@ class StmtComparer {
};
} // namespace
+static bool
+CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
+ const Decl *D1, const Decl *D2,
+ const Decl *PrimaryDecl = nullptr) {
+ // If either declaration has an attribute on it, we treat the declarations
+ // as not being structurally equivalent.
+ // FIXME: this should be handled on a case-by-case basis via tablegen in
+ // Attr.td. There are multiple cases to consider: one declation with the
+ // attribute, another without it;
diff erent attribute syntax|spellings for
+ // the same semantic attribute,
diff erences 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) {
+ 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;
+ }
+
+ // The above diagnostic is a warning which defaults to an error. If treated
+ // as a warning, we'll go ahead and allow any attribute
diff erences to be
+ // undefined behavior and the user gets what they get in terms of behavior.
+ return true;
+}
+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const UnaryOperator *E1,
const CXXOperatorCallExpr *E2) {
@@ -1459,6 +1497,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
QualType Owner2Type) {
const auto *Owner2 = cast<Decl>(Field2->getDeclContext());
+ // In C23 mode, check for structural equivalence of attributes on the fields.
+ // FIXME: Should this happen in C++ as well?
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(Context, Field1, Field2, Owner2))
+ return false;
+
// For anonymous structs/unions, match up the anonymous struct/union type
// declarations directly, so that we don't go off searching for anonymous
// types
@@ -1477,7 +1521,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(
Owner2->getLocation(),
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << Owner2Type;
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field_name)
<< Field2->getDeclName();
Context.Diag1(Field1->getLocation(), diag::note_odr_field_name)
@@ -1492,7 +1536,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(
Owner2->getLocation(),
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << Owner2Type;
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field)
<< Field2->getDeclName() << Field2->getType();
Context.Diag1(Field1->getLocation(), diag::note_odr_field)
@@ -1501,9 +1545,39 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return false;
}
- if (Field1->isBitField())
- return IsStructurallyEquivalent(Context, Field1->getBitWidth(),
- Field2->getBitWidth());
+ if ((Field1->isBitField() || Field2->isBitField()) &&
+ !IsStructurallyEquivalent(Context, Field1->getBitWidth(),
+ Field2->getBitWidth())) {
+ // Two bit-fields can be structurally unequivalent but still be okay for
+ // the purposes of C where they simply need to have the same values, not
+ // the same token sequences.
+ bool Diagnose = true;
+ if (Context.LangOpts.C23 && Field1->isBitField() && Field2->isBitField())
+ Diagnose = Field1->getBitWidthValue() != Field2->getBitWidthValue();
+
+ if (Diagnose && Context.Complain) {
+ auto DiagNote = [&](const FieldDecl *FD,
+ DiagnosticBuilder (
+ StructuralEquivalenceContext::*Diag)(
+ SourceLocation, unsigned)) {
+ if (FD->isBitField()) {
+ (Context.*Diag)(FD->getLocation(), diag::note_odr_field_bit_width)
+ << FD->getDeclName() << FD->getBitWidthValue();
+ } else {
+ (Context.*Diag)(FD->getLocation(), diag::note_odr_field_not_bit_field)
+ << FD->getDeclName();
+ }
+ };
+
+ Context.Diag2(
+ Owner2->getLocation(),
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
+ DiagNote(Field2, &StructuralEquivalenceContext::Diag2);
+ DiagNote(Field1, &StructuralEquivalenceContext::Diag1);
+ }
+ return false;
+ }
return true;
}
@@ -1640,6 +1714,28 @@ static bool NameIsStructurallyEquivalent(const TagDecl &D1, const TagDecl &D2) {
/// Determine structural equivalence of two records.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
RecordDecl *D1, RecordDecl *D2) {
+ // C23 6.2.7p1:
+ // ... Moreover, two complete structure, union, or enumerated types declared
+ // with the same tag are compatible if members satisfy the following
+ // requirements:
+ // - there shall be a one-to-one correspondence between their members such
+ // that each pair of corresponding members are declared with compatible
+ // types;
+ // - if one member of the pair is declared with an alignment specifier, the
+ // other is declared with an equivalent alignment specifier;
+ // - and, if one member of the pair is declared with a name, the other is
+ // declared with the same name.
+ // For two structures, corresponding members shall be declared in the same
+ // order. For two unions declared in the same translation unit, corresponding
+ // members shall be declared in the same order. For two structures or unions,
+ // corresponding bit-fields shall have the same widths. ... For determining
+ // type compatibility, anonymous structures and unions are considered a
+ // regular member of the containing structure or union type, and the type of
+ // an anonymous structure or union is considered compatible to the type of
+ // another anonymous structure or union, respectively, if their members
+ // fulfill the preceding requirements. ... Otherwise, the structure, union,
+ // or enumerated types are incompatible.
+
if (!NameIsStructurallyEquivalent(*D1, *D2)) {
return false;
}
@@ -1648,7 +1744,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(D1->getLocation(), diag::note_odr_tag_kind_here)
<< D1->getDeclName() << (unsigned)D1->getTagKind();
}
@@ -1669,10 +1766,19 @@ 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
diff erent context (namespace), these should be
//
diff erent. This is specially important if the definition of one or both
- // records is missing.
- if (!IsRecordContextStructurallyEquivalent(Context, D1, D2))
+ // records is missing. In C23,
diff erent contexts do not make for a
diff erent
+ // structural type (a local struct definition can be a valid redefinition of
+ // a file scope struct definition).
+ if (!Context.LangOpts.C23 &&
+ !IsRecordContextStructurallyEquivalent(Context, D1, D2))
return false;
// If both declarations are class template specializations, we know
@@ -1701,11 +1807,11 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// Compare the definitions of these two records. If either or both are
// incomplete (i.e. it is a forward decl), we assume that they are
- // equivalent.
+ // equivalent. except in C23 mode.
D1 = D1->getDefinition();
D2 = D2->getDefinition();
if (!D1 || !D2)
- return true;
+ return !Context.LangOpts.C23;
// 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
@@ -1740,7 +1846,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(D2->getLocation(), diag::note_odr_number_of_bases)
<< D2CXX->getNumBases();
Context.Diag1(D1->getLocation(), diag::note_odr_number_of_bases)
@@ -1760,7 +1867,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Base2->getBeginLoc(), diag::note_odr_base)
<< Base2->getType() << Base2->getSourceRange();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
@@ -1775,7 +1883,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Base2->getBeginLoc(), diag::note_odr_virtual_base)
<< Base2->isVirtual() << Base2->getSourceRange();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
@@ -1796,7 +1905,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2CXX);
+ << Context.ToCtx.getTypeDeclType(D2CXX)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*Friend1)->getFriendLoc(), diag::note_odr_friend);
Context.Diag2(D2->getLocation(), diag::note_odr_missing_friend);
}
@@ -1808,7 +1918,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2CXX);
+ << Context.ToCtx.getTypeDeclType(D2CXX)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*Friend1)->getFriendLoc(), diag::note_odr_friend);
Context.Diag2((*Friend2)->getFriendLoc(), diag::note_odr_friend);
}
@@ -1821,7 +1932,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2((*Friend2)->getFriendLoc(), diag::note_odr_friend);
Context.Diag1(D1->getLocation(), diag::note_odr_missing_friend);
}
@@ -1832,7 +1944,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
const CXXBaseSpecifier *Base1 = D1CXX->bases_begin();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
<< Base1->getType() << Base1->getSourceRange();
@@ -1854,7 +1967,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(Field1->getLocation(), diag::note_odr_field)
<< Field1->getDeclName() << Field1->getType();
Context.Diag2(D2->getLocation(), diag::note_odr_missing_field);
@@ -1870,7 +1984,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field)
<< Field2->getDeclName() << Field2->getType();
Context.Diag1(D1->getLocation(), diag::note_odr_missing_field);
@@ -1910,54 +2025,84 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// Compare the definitions of these two enums. If either or both are
// incomplete (i.e. forward declared), we assume that they are equivalent.
+ // In C23, the order of the enumerations does not matter, only the names and
+ // values do.
D1 = D1->getDefinition();
D2 = D2->getDefinition();
if (!D1 || !D2)
return true;
- EnumDecl::enumerator_iterator EC2 = D2->enumerator_begin(),
- EC2End = D2->enumerator_end();
- for (EnumDecl::enumerator_iterator EC1 = D1->enumerator_begin(),
- EC1End = D1->enumerator_end();
- EC1 != EC1End; ++EC1, ++EC2) {
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(Context, D1, D2))
+ return false;
+
+ llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
+ auto CopyEnumerators =
+ [](auto &&Range, llvm::SmallVectorImpl<const EnumConstantDecl *> &Cont) {
+ for (const EnumConstantDecl *ECD : Range)
+ Cont.push_back(ECD);
+ };
+ CopyEnumerators(D1->enumerators(), D1Enums);
+ CopyEnumerators(D2->enumerators(), D2Enums);
+
+ // In C23 mode, the order of the enumerations does not matter, so sort them
+ // by name to get them both into a consistent ordering.
+ if (Context.LangOpts.C23) {
+ auto Sorter = [](const EnumConstantDecl *LHS, const EnumConstantDecl *RHS) {
+ return LHS->getName() < RHS->getName();
+ };
+ llvm::sort(D1Enums, Sorter);
+ llvm::sort(D2Enums, Sorter);
+ }
+
+ auto EC2 = D2Enums.begin(), EC2End = D2Enums.end();
+ for (auto EC1 = D1Enums.begin(), EC1End = D1Enums.end(); EC1 != EC1End;
+ ++EC1, ++EC2) {
if (EC2 == EC2End) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
- Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
- << EC1->getDeclName() << toString(EC1->getInitVal(), 10);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag1((*EC1)->getLocation(), diag::note_odr_enumerator)
+ << (*EC1)->getDeclName() << toString((*EC1)->getInitVal(), 10);
Context.Diag2(D2->getLocation(), diag::note_odr_missing_enumerator);
}
return false;
}
- llvm::APSInt Val1 = EC1->getInitVal();
- llvm::APSInt Val2 = EC2->getInitVal();
+ llvm::APSInt Val1 = (*EC1)->getInitVal();
+ llvm::APSInt Val2 = (*EC2)->getInitVal();
if (!llvm::APSInt::isSameValue(Val1, Val2) ||
- !IsStructurallyEquivalent(EC1->getIdentifier(), EC2->getIdentifier())) {
+ !IsStructurallyEquivalent((*EC1)->getIdentifier(),
+ (*EC2)->getIdentifier())) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
- Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
- << EC2->getDeclName() << toString(EC2->getInitVal(), 10);
- Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
- << EC1->getDeclName() << toString(EC1->getInitVal(), 10);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag2((*EC2)->getLocation(), diag::note_odr_enumerator)
+ << (*EC2)->getDeclName() << toString((*EC2)->getInitVal(), 10);
+ Context.Diag1((*EC1)->getLocation(), diag::note_odr_enumerator)
+ << (*EC1)->getDeclName() << toString((*EC1)->getInitVal(), 10);
}
return false;
}
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(Context, *EC1, *EC2, D2))
+ return false;
}
if (EC2 != EC2End) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
- Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
- << EC2->getDeclName() << toString(EC2->getInitVal(), 10);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag2((*EC2)->getLocation(), diag::note_odr_enumerator)
+ << (*EC2)->getDeclName() << toString((*EC2)->getInitVal(), 10);
Context.Diag1(D1->getLocation(), diag::note_odr_missing_enumerator);
}
return false;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index cd6464678c4b5..4cfb8de615f05 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5697,7 +5697,7 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
if (Tok.is(tok::l_brace) && TUK == TagUseKind::Definition) {
Decl *D = SkipBody.CheckSameAsPrevious ? SkipBody.New : TagDecl;
- ParseEnumBody(StartLoc, D);
+ ParseEnumBody(StartLoc, D, &SkipBody);
if (SkipBody.CheckSameAsPrevious &&
!Actions.ActOnDuplicateDefinition(getCurScope(), TagDecl, SkipBody)) {
DS.SetTypeSpecError();
@@ -5722,7 +5722,8 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
/// enumeration-constant:
/// identifier
///
-void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl) {
+void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl,
+ SkipBodyInfo *SkipBody) {
// Enter the scope of the enum body and start the definition.
ParseScope EnumScope(this, Scope::DeclScope | Scope::EnumScope);
Actions.ActOnTagStartDefinition(getCurScope(), EnumDecl);
@@ -5780,7 +5781,7 @@ void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl) {
// Install the enumerator constant into EnumDecl.
Decl *EnumConstDecl = Actions.ActOnEnumConstant(
getCurScope(), EnumDecl, LastEnumConstDecl, IdentLoc, Ident, attrs,
- EqualLoc, AssignedVal.get());
+ EqualLoc, AssignedVal.get(), SkipBody);
EnumAvailabilityDiags.back().done();
EnumConstantDecls.push_back(EnumConstDecl);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8cabebfe81c73..50139625b7897 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -18050,7 +18050,8 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
// ensure the decl passes the structural compatibility check in
// C11 6.2.7/1 (or 6.1.2.6/1 in C89).
NamedDecl *Hidden = nullptr;
- if (SkipBody && !hasVisibleDefinition(Def, &Hidden)) {
+ if (SkipBody &&
+ (!hasVisibleDefinition(Def, &Hidden) || getLangOpts().C23)) {
// There is a definition of this tag, but it is not visible. We
// explicitly make use of C++'s one definition rule here, and
// assume that this definition is identical to the hidden one
@@ -18063,6 +18064,8 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
SkipBody->CheckSameAsPrevious = true;
SkipBody->New = createTagFromNewDecl();
SkipBody->Previous = Def;
+
+ ProcessDeclAttributeList(S, SkipBody->New, Attrs);
return Def;
} else {
SkipBody->ShouldSkip = true;
@@ -20046,7 +20049,8 @@ SkipBodyInfo Sema::shouldSkipAnonEnumBody(Scope *S, IdentifierInfo *II,
Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
SourceLocation IdLoc, IdentifierInfo *Id,
const ParsedAttributesView &Attrs,
- SourceLocation EqualLoc, Expr *Val) {
+ SourceLocation EqualLoc, Expr *Val,
+ SkipBodyInfo *SkipBody) {
EnumDecl *TheEnumDecl = cast<EnumDecl>(theEnumDecl);
EnumConstantDecl *LastEnumConst =
cast_or_null<EnumConstantDecl>(lastEnumConst);
@@ -20083,7 +20087,7 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
if (!New)
return nullptr;
- if (PrevDecl) {
+ if (PrevDecl && (!SkipBody || !SkipBody->CheckSameAsPrevious)) {
if (!TheEnumDecl->isScoped() && isa<ValueDecl>(PrevDecl)) {
// Check for other kinds of shadowing not already handled.
CheckShadow(New, PrevDecl, R);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c1adf35a22270..294daef70c339 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9157,10 +9157,10 @@ bool Sema::hasStructuralCompatLayout(Decl *D, Decl *Suggested) {
// FIXME: Add a specific mode for C11 6.2.7/1 in StructuralEquivalenceContext
// and isolate from other C++ specific checks.
StructuralEquivalenceContext Ctx(
- D->getASTContext(), Suggested->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default,
- false /*StrictTypeSpelling*/, true /*Complain*/,
- true /*ErrorOnTagTypeMismatch*/);
+ getLangOpts(), D->getASTContext(), Suggested->getASTContext(),
+ NonEquivalentDecls, StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false, /*Complain=*/true,
+ /*ErrorOnTagTypeMismatch=*/true);
return Ctx.IsEquivalent(D, Suggested);
}
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index f1ed67afaab7a..cda521808d1de 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -10359,7 +10359,7 @@ void ASTReader::finishPendingActions() {
PendingObjCExtensionIvarRedeclarations.back().second;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- ExtensionsPair.first->getASTContext(),
+ ContextObj->getLangOpts(), ExtensionsPair.first->getASTContext(),
ExtensionsPair.second->getASTContext(), NonEquivalentDecls,
StructuralEquivalenceKind::Default, /*StrictTypeSpelling =*/false,
/*Complain =*/false,
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index b1bec20b40390..e84932c765663 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4574,11 +4574,12 @@ namespace {
Reader.getOwningModuleFile(Cat)) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- Cat->getASTContext(), Existing->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default,
- /*StrictTypeSpelling =*/false,
- /*Complain =*/false,
- /*ErrorOnTagTypeMismatch =*/true);
+ Reader.getContext().getLangOpts(), Cat->getASTContext(),
+ Existing->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false,
+ /*ErrorOnTagTypeMismatch=*/true);
if (!Ctx.IsEquivalent(Cat, Existing)) {
// Warn only if the categories with the same name are
diff erent.
Reader.Diag(Cat->getLocation(), diag::warn_dup_category_def)
diff --git a/clang/test/ASTMerge/struct/test.c b/clang/test/ASTMerge/struct/test.c
index 4dfa74737eed8..10ea753b142bd 100644
--- a/clang/test/ASTMerge/struct/test.c
+++ b/clang/test/ASTMerge/struct/test.c
@@ -21,6 +21,16 @@
// CHECK: struct1.c:27:8: note: no corresponding field here
// CHECK: struct2.c:24:31: warning: external variable 'x4' declared with incompatible types in
diff erent translation units ('struct S4' vs. 'struct S4')
// CHECK: struct1.c:27:22: note: declared here with type 'struct S4'
+// CHECK: struct1.c:33:8: warning: type 'struct S6' has incompatible definitions in
diff erent translation units
+// CHECK: struct1.c:33:33: note: bit-field 'j' has bit-width 8 here
+// CHECK: struct2.c:30:33: note: field 'j' is not a bit-field
+// CHECK: struct2.c:30:38: warning: external variable 'x6' declared with incompatible types in
diff erent translation units ('struct S6' vs. 'struct S6')
+// CHECK: struct1.c:33:42: note: declared here with type 'struct S6'
+// CHECK: struct1.c:36:8: warning: type 'struct S7' has incompatible definitions in
diff erent translation units
+// CHECK: struct1.c:36:33: note: bit-field 'j' has bit-width 8 here
+// CHECK: struct2.c:33:33: note: bit-field 'j' has bit-width 16 here
+// CHECK: struct2.c:33:43: warning: external variable 'x7' declared with incompatible types in
diff erent translation units ('struct S7' vs. 'struct S7')
+// CHECK: struct1.c:36:42: note: declared here with type 'struct S7'
// CHECK: struct1.c:56:10: warning: type 'struct DeeperError' has incompatible definitions in
diff erent translation units
// CHECK: struct1.c:56:35: note: field 'f' has type 'int' here
// CHECK: struct2.c:53:37: note: field 'f' has type 'float' here
@@ -42,4 +52,4 @@
// CHECK: struct2.c:129:9: note: field 'S' has type 'struct (unnamed struct at [[PATH_TO_INPUTS]]struct2.c:127:7)' here
// CHECK: struct2.c:138:3: warning: external variable 'x16' declared with incompatible types in
diff erent translation units ('struct DeepUnnamedError' vs. 'struct DeepUnnamedError')
// CHECK: struct1.c:141:3: note: declared here with type 'struct DeepUnnamedError'
-// CHECK: 17 warnings generated
+// CHECK: 20 warnings generated
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
new file mode 100644
index 0000000000000..0f70d38583eb1
--- /dev/null
+++ b/clang/test/C/C23/n3037.c
@@ -0,0 +1,361 @@
+// 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
+
+/* WG14 N3037: Clang 21
+ * Improved tag compatibility
+ *
+ * Identical tag types have always been compatible across TU boundaries. This
+ * paper made identical tag types compatible within the same TU.
+ */
+
+struct foo { int a; } p;
+
+void baz(struct foo f); // c17-note {{passing argument to parameter 'f' here}}
+
+void bar(void) {
+ struct foo { int a; } q = {};
+ baz(q); // c17-error {{passing 'struct foo' to parameter of incompatible type 'struct foo'}}
+}
+
+#define PRODUCT(A ,B) struct prod { A a; B b; } // expected-note 2 {{expanded from macro 'PRODUCT'}}
+#define SUM(A, B) struct sum { _Bool flag; union { A a; B b; }; } // expected-note 2 {{expanded from macro 'SUM'}}
+
+void func1(PRODUCT(int, SUM(float, double)) x); // both-warning {{declaration of 'struct prod' will not be visible outside of this function}} \
+ both-warning {{declaration of 'struct sum' will not be visible outside of this function}} \
+ c17-note {{passing argument to parameter 'x' here}}
+void func2(PRODUCT(int, SUM(float, double)) y) { // both-warning {{declaration of 'struct prod' will not be visible outside of this function}} \
+ both-warning {{declaration of 'struct sum' will not be visible outside of this function}}
+ func1(y); // c17-error {{passing 'struct prod' to parameter of incompatible type 'struct prod'}}
+}
+
+struct foop { struct { int x; }; }; // c17-note {{previous definition is here}}
+struct foop { struct { int x; }; }; // c17-error {{redefinition of 'foop'}}
+union barp { int x; float y; }; // c17-note {{previous definition is here}}
+union barp { int x; float y; }; // c17-error {{redefinition of 'barp'}}
+typedef struct q { int x; } q_t; // c17-note 2 {{previous definition is here}}
+typedef struct q { int x; } q_t; // c17-error {{redefinition of 'q'}} \
+ c17-error-re {{typedef redefinition with
diff erent types ('struct (unnamed struct at {{.*}})' vs 'struct q')}}
+void func3(void) {
+ struct S { int x; }; // c17-note {{previous definition is here}}
+ struct T { struct S s; }; // c17-note {{previous definition is here}}
+ struct S { int x; }; // c17-error {{redefinition of 'S'}}
+ struct T { struct S s; }; // c17-error {{redefinition of 'T'}}
+}
+
+struct food { int (*p)[3]; }; // c23-note {{field 'p' has type 'int (*)[3]' here}} \
+ c17-note {{previous definition is here}}
+struct food { int (*p)[]; }; // c23-error {{type 'struct food' has incompatible definitions}} \
+ c23-note {{field 'p' has type 'int (*)[]' here}} \
+ c17-error {{redefinition of 'food'}}
+union bard { int x; float y; }; // c23-note {{field has name 'x' here}} \
+ c17-note {{previous definition is here}}
+union bard { int z; float y; }; // c23-error {{type 'union bard' has incompatible definitions}} \
+ c23-note {{field has name 'z' here}} \
+ c17-error {{redefinition of 'bard'}}
+union purr { int x; float y; }; // c23-note {{field has name 'x' here}} \
+ c17-note {{previous definition is here}}
+union purr { float y; int x; }; // c23-error {{type 'union purr' has incompatible definitions}} \
+ c23-note {{field has name 'y' here}} \
+ c17-error {{redefinition of 'purr'}}
+
+// The presence of an attribute makes two types not compatible.
+struct [[gnu::packed]] attr_test { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'packed' here}}
+ int x;
+};
+
+struct attr_test { // c17-error {{redefinition of 'attr_test'}} \
+ c23-error {{type 'struct attr_test' has an attribute which currently causes the types to be treated as though they are incompatible}}
+ int x;
+};
+
+struct attr_test_2 { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct [[gnu::packed]] attr_test_2 { // c17-error {{redefinition of 'attr_test_2'}} \
+ c23-error {{type 'struct attr_test_2' has an attribute which currently causes the types to be treated as though they are incompatible}} \
+ c23-note {{attribute 'packed' here}}
+ int x;
+};
+
+// This includes the same attribute on both types.
+struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'packed' 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 'packed' here}}
+ int x;
+};
+
+// Everything which applies to the tag itself also applies to fields.
+struct field_attr_test_1 { // c17-note {{previous definition is here}}
+ int x;
+ [[gnu::packed]] int y; // c23-note {{attribute 'packed' here}}
+};
+
+struct field_attr_test_1 { // c17-error {{redefinition of 'field_attr_test_1'}} \
+ c23-error {{type 'struct field_attr_test_1' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
+ int x;
+ int y;
+};
+
+struct field_attr_test_2 { // c17-note {{previous definition is here}}
+ [[gnu::packed]] int x; // c23-note {{attribute 'packed' here}}
+ int y;
+};
+
+struct field_attr_test_2 { // c17-error {{redefinition of 'field_attr_test_2'}} \
+ c23-error {{type 'struct field_attr_test_2' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
+ int x;
+ int y;
+};
+
+struct field_attr_test_3 { // c17-note {{previous definition is here}}
+ [[gnu::packed]] int x; // c23-note {{attribute 'packed' here}}
+ 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 'packed' here}}
+ int y;
+};
+
+// Show that equivalent field types are not an issue.
+typedef int typedef_of_type_int;
+struct equivalent_field_types { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct equivalent_field_types { // c17-error {{redefinition of 'equivalent_field_types'}}
+ typedef_of_type_int x;
+};
+
+struct quals_matter { // c17-note {{previous definition is here}}
+ int x; // c23-note {{field 'x' has type 'int' here}}
+};
+
+struct quals_matter { // c17-error {{redefinition of 'quals_matter'}} \
+ c23-error {{type 'struct quals_matter' has incompatible definitions}}
+ const int x; // c23-note {{field 'x' has type 'const int' here}}
+};
+
+struct qual_order_does_not_matter { // c17-note {{previous definition is here}}
+ const volatile int x;
+};
+
+struct qual_order_does_not_matter { // c17-error {{redefinition of 'qual_order_does_not_matter'}}
+ volatile const int x;
+};
+
+struct nested { // both-note {{previous definition is here}}
+ int x;
+ struct nested { // both-error {{nested redefinition of 'nested'}}
+ int x;
+ };
+};
+
+// Show that bit-field order does matter, including anonymous bit-fields.
+struct bit_field_1 { // c17-note 2 {{previous definition is here}}
+ int a : 1;
+ int : 0; // c23-note {{field has name '' here}}
+ int b : 1;
+};
+
+struct bit_field_1 { // c17-error {{redefinition of 'bit_field_1'}}
+ int a : 1;
+ int : 0;
+ int b : 1;
+};
+
+struct bit_field_1 { // c17-error {{redefinition of 'bit_field_1'}} \
+ c23-error {{type 'struct bit_field_1' has incompatible definitions}}
+ int a : 1;
+ int b : 1; // c23-note {{field has name 'b' here}}
+};
+
+struct bit_field_2 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
+struct bit_field_2 { // c17-error {{redefinition of 'bit_field_2'}} \
+ c23-error {{type 'struct bit_field_2' has incompatible definitions}}
+ int a : 1;
+ int b : 2; // c23-note {{bit-field 'b' has bit-width 2 here}}
+};
+
+// Test a bit-field with an attribute.
+struct bit_field_3 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1;
+};
+
+struct bit_field_3 { // c17-error {{redefinition of 'bit_field_3'}} \
+ c23-error {{type 'struct bit_field_3' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
+ int a : 1;
+ [[deprecated]] int b : 1; // c23-note {{attribute 'deprecated' here}}
+};
+
+struct bit_field_4 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
+struct bit_field_4 { // c17-error {{redefinition of 'bit_field_4'}} \
+ c23-error {{type 'struct bit_field_4' has incompatible definitions}}
+ int a : 1;
+ int b; // c23-note {{field 'b' is not a bit-field}}
+};
+
+struct bit_field_5 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b; // c23-note {{field 'b' is not a bit-field}}
+};
+
+struct bit_field_5 { // c17-error {{redefinition of 'bit_field_5'}} \
+ c23-error {{type 'struct bit_field_5' has incompatible definitions}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
+struct bit_field_6 { // c17-note {{previous definition is here}}
+ int a : 2;
+};
+
+struct bit_field_6 { // c17-error {{redefinition of 'bit_field_6'}}
+ int a : 1 + 1;
+};
+
+enum E { A }; // c17-note 2 {{previous definition is here}}
+enum E { A }; // c17-error {{redefinition of 'E'}} \
+ c17-error {{redefinition of enumerator 'A'}}
+
+enum Q { D = 1 }; // c17-note 2 {{previous definition is here}}
+enum Q { D = D }; // c17-error {{redefinition of 'Q'}} \
+ c17-error {{redefinition of enumerator 'D'}}
+
+// The order of the enumeration constants does not matter, only the values do.
+enum X { B = 1, C = 1 + 1 }; // c17-note 3 {{previous definition is here}}
+enum X { C = 2, B = 1 }; // c17-error {{redefinition of 'X'}} \
+ c17-error {{redefinition of enumerator 'C'}} \
+ c17-error {{redefinition of enumerator 'B'}}
+
+// Different enumeration constants.
+enum Y { YA = 1, YB = 2 }; // c23-note {{enumerator 'YB' with value 2 here}} \
+ c17-note 3 {{previous definition is here}}
+enum Y { YA = 1, YB = 3 }; // c23-error {{type 'enum Y' has incompatible definitions}} \
+ c23-note {{enumerator 'YB' with value 3 here}} \
+ c17-error {{redefinition of 'Y'}} \
+ c17-error {{redefinition of enumerator 'YA'}} \
+ c17-error {{redefinition of enumerator 'YB'}}
+
+// Different enumeration names, same named constants.
+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-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_2 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'deprecated' here}}
+ EAT2 // c17-note {{previous definition is here}}
+};
+
+enum enum_attr_test_2 { // c17-error {{redefinition of 'enum_attr_test_2'}} \
+ c23-error {{type 'enum enum_attr_test_2' has an attribute which currently causes the types to be treated as though they are incompatible}}
+ EAT2 // c17-error {{redefinition of enumerator 'EAT2'}}
+};
+
+enum enum_attr_test_3 { // c17-note {{previous definition is here}}
+ EAT3 // c17-note {{previous definition is here}}
+};
+
+enum [[deprecated]] enum_attr_test_3 { // c17-error {{redefinition of 'enum_attr_test_3'}} \
+ c23-error {{type 'enum enum_attr_test_3' has an attribute which currently causes the types to be treated as though they are incompatible}} \
+ c23-note {{attribute 'deprecated' here}}
+ EAT3 // c17-error {{redefinition of enumerator 'EAT3'}}
+};
+
+// You cannot declare one with a fixed underlying type and the other without a
+// fixed underlying type, or a
diff erent underlying type. However, it's worth
+// showing that the underlying type doesn't change the redefinition behavior.
+enum fixed_test_1 : int { FT1 }; // c17-note 2 {{previous definition is here}}
+enum fixed_test_1 : int { FT1 }; // c17-error {{redefinition of 'fixed_test_1'}} \
+ c17-error {{redefinition of enumerator 'FT1'}}
+
+enum fixed_test_2 : int { FT2 }; // c17-note 2 {{previous definition is here}}
+enum fixed_test_2 : typedef_of_type_int { FT2 }; // c17-error {{redefinition of 'fixed_test_2'}} \
+ c17-error {{redefinition of enumerator 'FT2'}}
+
+// Test more bizarre situations in terms of where the type is declared. This
+// has always been allowed.
+struct declared_funny_1 { int x; }
+declared_funny_func(struct declared_funny_1 { int x; } arg) { // both-warning {{declaration of 'struct declared_funny_1' will not be visible outside of this function}}
+ return declared_funny_func((__typeof__(arg)){ 0 });
+}
+
+// However, this is new.
+struct Outer {
+ struct Inner { // c17-note {{previous definition is here}}
+ int x;
+ } i;
+
+ enum InnerEnum { // c17-note {{previous definition is here}}
+ IE1 // c17-note {{previous definition is here}}
+ } j;
+};
+
+struct Inner { // c17-error {{redefinition of 'Inner'}}
+ int x;
+};
+
+enum InnerEnum { // c17-error {{redefinition of 'InnerEnum'}}
+ IE1 // c17-error {{redefinition of enumerator 'IE1'}}
+};
+
+void hidden(void) {
+ struct hidden_struct { int x; };
+}
+
+struct hidden_struct { // This is fine because the previous declaration is not visible.
+ int y;
+ int z;
+};
+
+struct array { int y; int x[]; }; // c17-note {{previous definition is here}} \
+ c23-note {{field 'x' has type 'int[]' here}}
+struct array { int y; int x[0]; }; // c17-error {{redefinition of 'array'}} \
+ c23-error {{type 'struct array' has incompatible definitions}} \
+ c23-note {{field 'x' has type 'int[0]' here}} \
+ both-warning {{zero size arrays are an extension}}
+
+// So long as the bounds are the same value, everything is fine. They do not
+// have to be token equivalent.
+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-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}}
+ int x;
+};
diff --git a/clang/test/C/C23/n3037_1.c b/clang/test/C/C23/n3037_1.c
new file mode 100644
index 0000000000000..e494eaf5828aa
--- /dev/null
+++ b/clang/test/C/C23/n3037_1.c
@@ -0,0 +1,88 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -std=c23 -Wno-error=odr -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
+
+// This tests the codegen behavior of redefined tag types to ensure the
+// generated code looks reasonable.
+
+enum E {
+ One = 1,
+ Zero = 0,
+ Two = 2
+};
+
+struct S {
+ int x;
+ int y;
+};
+
+struct S func(struct S s, enum E e);
+
+struct S {
+ int x, y;
+} what();
+
+// CHECK-LABEL: define dso_local i64 @func(
+// CHECK-SAME: i64 [[S_COERCE:%.*]], i32 noundef [[E:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_S:%.*]], align 4
+// CHECK-NEXT: [[S:%.*]] = alloca [[STRUCT_S]], align 4
+// CHECK-NEXT: [[E_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i64 [[S_COERCE]], ptr [[S]], align 4
+// CHECK-NEXT: store i32 [[E]], ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_S]], ptr [[S]], i32 0, i32 0
+// CHECK-NEXT: store i32 [[TMP0]], ptr [[X]], align 4
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[RETVAL]], ptr align 4 [[S]], i64 8, i1 false)
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[RETVAL]], align 4
+// CHECK-NEXT: ret i64 [[TMP1]]
+//
+struct S func(struct S s, enum E e) {
+ s.x = (int)e;
+ return s;
+}
+
+enum E {
+ Zero,
+ One,
+ Two
+};
+
+// Ensure that ignoring the incompatibility due to attributes does not cause a
+// crash. Note, this is undefined behavior in Clang until we implement
+// attribute structural compatibility logic, so this is not intended to verify
+// any particular behavior beyond "don't crash."
+struct T {
+ _Alignas(double) int x;
+};
+
+// CHECK-LABEL: define dso_local i32 @foo(
+// CHECK-SAME: i32 [[T_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[T:%.*]] = alloca [[STRUCT_T:%.*]], align 8
+// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT: store i32 [[T_COERCE]], ptr [[COERCE_DIVE]], align 8
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X]], align 8
+// CHECK-NEXT: ret i32 [[TMP0]]
+//
+int foo(struct T t) {
+ return t.x;
+}
+
+struct T {
+ int x;
+};
+
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK-SAME: i32 [[T_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[T:%.*]] = alloca [[STRUCT_T:%.*]], align 8
+// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT: store i32 [[T_COERCE]], ptr [[COERCE_DIVE]], align 8
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X]], align 8
+// CHECK-NEXT: ret i32 [[TMP0]]
+//
+int bar(struct T t) {
+ return t.x;
+}
diff --git a/clang/test/C/drs/dr1xx.c b/clang/test/C/drs/dr1xx.c
index 3e4c39cca62e4..ada58f1d6ad87 100644
--- a/clang/test/C/drs/dr1xx.c
+++ b/clang/test/C/drs/dr1xx.c
@@ -1,8 +1,8 @@
-/* RUN: %clang_cc1 -std=c89 -fsyntax-only -verify=expected,c89only -pedantic -Wno-c11-extensions %s
- RUN: %clang_cc1 -std=c99 -fsyntax-only -verify=expected,c99untilc2x -pedantic -Wno-c11-extensions %s
- RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected,c99untilc2x -pedantic %s
- RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=expected,c99untilc2x -pedantic %s
- RUN: %clang_cc1 -std=c2x -fsyntax-only -verify=expected,c2xandup -pedantic %s
+/* RUN: %clang_cc1 -std=c89 -fsyntax-only -verify=expected,c89only,untilc23 -pedantic -Wno-c11-extensions %s
+ RUN: %clang_cc1 -std=c99 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic -Wno-c11-extensions %s
+ RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic %s
+ RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic %s
+ RUN: %clang_cc1 -std=c23 -fsyntax-only -verify=expected,c2xandup -pedantic %s
*/
/* The following are DRs which do not require tests to demonstrate
@@ -87,15 +87,15 @@ void dr101_caller(void) {
* Tag redeclaration constraints
*/
void dr102(void) {
- struct S { int member; }; /* expected-note {{previous definition is here}} */
- struct S { int member; }; /* expected-error {{redefinition of 'S'}} */
+ struct S { int member; }; /* untilc23-note {{previous definition is here}} */
+ struct S { int member; }; /* untilc23-error {{redefinition of 'S'}} */
- union U { int member; }; /* expected-note {{previous definition is here}} */
- union U { int member; }; /* expected-error {{redefinition of 'U'}} */
+ union U { int member; }; /* untilc23-note {{previous definition is here}} */
+ union U { int member; }; /* untilc23-error {{redefinition of 'U'}} */
- enum E { member }; /* expected-note 2{{previous definition is here}} */
- enum E { member }; /* expected-error {{redefinition of 'E'}}
- expected-error {{redefinition of enumerator 'member'}} */
+ enum E { member }; /* untilc23-note 2{{previous definition is here}} */
+ enum E { member }; /* untilc23-error {{redefinition of 'E'}}
+ untilc23-error {{redefinition of enumerator 'member'}} */
}
/* WG14 DR103: yes
diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp
index 7cf52df9b14d0..ef82afaf3f8dc 100644
--- a/clang/unittests/AST/StructuralEquivalenceTest.cpp
+++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp
@@ -137,13 +137,15 @@ struct StructuralEquivalenceTest : ::testing::Test {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
StructuralEquivalenceContext Ctx01(
- D0->getASTContext(), D1->getASTContext(), NonEquivalentDecls01,
- StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ D0->getLangOpts(), D0->getASTContext(), D1->getASTContext(),
+ NonEquivalentDecls01, StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
/*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
StructuralEquivalenceContext Ctx10(
- D1->getASTContext(), D0->getASTContext(), NonEquivalentDecls10,
- StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ D1->getLangOpts(), D1->getASTContext(), D0->getASTContext(),
+ NonEquivalentDecls10, StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
/*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
bool Eq01 = Ctx01.IsEquivalent(D0, D1);
@@ -155,12 +157,16 @@ struct StructuralEquivalenceTest : ::testing::Test {
bool testStructuralMatch(StmtWithASTContext S0, StmtWithASTContext S1) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
- StructuralEquivalenceContext Ctx01(
- *S0.Context, *S1.Context, NonEquivalentDecls01,
- StructuralEquivalenceKind::Default, false, false);
- StructuralEquivalenceContext Ctx10(
- *S1.Context, *S0.Context, NonEquivalentDecls10,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceContext Ctx01(S0.Context->getLangOpts(), *S0.Context,
+ *S1.Context, NonEquivalentDecls01,
+ StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
+ StructuralEquivalenceContext Ctx10(S1.Context->getLangOpts(), *S1.Context,
+ *S0.Context, NonEquivalentDecls10,
+ StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
bool Eq01 = Ctx01.IsEquivalent(S0.S, S1.S);
bool Eq10 = Ctx10.IsEquivalent(S1.S, S0.S);
EXPECT_EQ(Eq01, Eq10);
@@ -1826,8 +1832,10 @@ TEST_F(StructuralEquivalenceCacheTest, SimpleNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first, X.second));
@@ -1849,8 +1857,10 @@ TEST_F(StructuralEquivalenceCacheTest, ReturnStmtNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first->getBody(), X.second->getBody()));
@@ -1868,8 +1878,10 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1886,8 +1898,10 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithDifferentStorageClassNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1913,8 +1927,10 @@ TEST_F(StructuralEquivalenceCacheTest,
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto NTTP = findDeclPair<NonTypeTemplateParmDecl>(
TU, nonTypeTemplateParmDecl(hasName("T")));
@@ -1932,8 +1948,10 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithInitNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1964,8 +1982,10 @@ TEST_F(StructuralEquivalenceCacheTest, SpecialNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2003,8 +2023,10 @@ TEST_F(StructuralEquivalenceCacheTest, Cycle) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2066,9 +2088,11 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
ASSERT_EQ(D1->getTemplateDepth(), 0u);
StructuralEquivalenceContext Ctx_NoIgnoreTemplateParmDepth(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
- false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
+ /*IgnoreTemplateParmDepth=*/false);
EXPECT_FALSE(Ctx_NoIgnoreTemplateParmDepth.IsEquivalent(D0, D1));
@@ -2080,9 +2104,11 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
EXPECT_FALSE(isInNonEqCache(std::make_pair(NonEqDecl0, NonEqDecl1), true));
StructuralEquivalenceContext Ctx_IgnoreTemplateParmDepth(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
- false, true);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
+ /*IgnoreTemplateParmDepth=*/true);
EXPECT_TRUE(Ctx_IgnoreTemplateParmDepth.IsEquivalent(D0, D1));
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index af1b61afdaec8..e47466e3273f2 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -899,7 +899,7 @@ <h2 id="c2x">C23 implementation status</h2>
<tr>
<td>Improved tag compatibility</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf">N3037</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 21</td>
</tr>
<tr>
<td>#embed</td>
More information about the cfe-commits
mailing list