diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 2c65fc4667562..dac8b383804c1 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -245,6 +245,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 `_ + 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 { 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>; + /// 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 different " "translation units (%1 vs. %2)">, InGroup; +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, DefaultError; +def note_odr_attr_here : Note<"attribute %0 here">; def err_odr_tag_type_inconsistent - : Error<"type %0 has incompatible definitions in different translation " - "units">; + : Error<"type %0 has incompatible definitions%select{| in different " + "translation units}1">; def warn_odr_tag_type_inconsistent - : Warning<"type %0 has incompatible definitions in different translation " - "units">, + : Warning<"type %0 has incompatible definitions%select{| in different " + "translation units}1">, InGroup; 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 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; different attribute syntax|spellings for + // 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) { + const auto *DiagnoseDecl = cast(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 differences 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(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 different context (namespace), these should be // different. This is specially important if the definition of one or both - // records is missing. - if (!IsRecordContextStructurallyEquivalent(Context, D1, D2)) + // records is missing. In C23, different contexts do not make for a different + // 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 D1Enums, D2Enums; + auto CopyEnumerators = + [](auto &&Range, llvm::SmallVectorImpl &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 d7421934032cf..9e6b867789e38 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -18048,7 +18048,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 @@ -18061,6 +18062,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; @@ -20044,7 +20047,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(theEnumDecl); EnumConstantDecl *LastEnumConst = cast_or_null(lastEnumConst); @@ -20081,7 +20085,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(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 different. 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 different 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 different 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 different 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 different 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 different 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 different 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 different 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 different 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 different 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(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(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(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(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( 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(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( 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( 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 @@

C23 implementation status

Improved tag compatibility N3037 - No + Clang 21 #embed