Skip to content

Commit a4ceac7

Browse files
authored
[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.
1 parent 543f112 commit a4ceac7

20 files changed

+790
-116
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ C23 Feature Support
249249
scope.
250250
- Fixed a bug where you could not cast a null pointer constant to type
251251
``nullptr_t``. Fixes #GH133644.
252+
- Implemented `WG14 N3037 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf>`_
253+
which allows tag types to be redefined within the same translation unit so
254+
long as both definitions are structurally equivalent (same tag types, same
255+
tag names, same tag members, etc).
252256
- Fixed a failed assertion with an invalid parameter to the ``#embed``
253257
directive. Fixes #GH126940.
254258

clang/include/clang/AST/ASTContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,6 +3147,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
31473147
QualType mergeTransparentUnionType(QualType, QualType,
31483148
bool OfBlockPointer=false,
31493149
bool Unqualified = false);
3150+
QualType mergeTagDefinitions(QualType, QualType);
31503151

31513152
QualType mergeObjCGCQualifiers(QualType, QualType);
31523153

clang/include/clang/AST/ASTStructuralEquivalence.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ struct StructuralEquivalenceContext {
4343
/// key: (from, to, IgnoreTemplateParmDepth)
4444
using NonEquivalentDeclSet = llvm::DenseSet<std::tuple<Decl *, Decl *, int>>;
4545

46+
/// The language options to use for making a structural equivalence check.
47+
const LangOptions &LangOpts;
48+
4649
/// AST contexts for which we are checking structural equivalence.
4750
ASTContext &FromCtx, &ToCtx;
4851

@@ -76,15 +79,17 @@ struct StructuralEquivalenceContext {
7679
/// Whether to ignore comparing the depth of template param(TemplateTypeParm)
7780
bool IgnoreTemplateParmDepth;
7881

79-
StructuralEquivalenceContext(ASTContext &FromCtx, ASTContext &ToCtx,
82+
StructuralEquivalenceContext(const LangOptions &LangOpts, ASTContext &FromCtx,
83+
ASTContext &ToCtx,
8084
NonEquivalentDeclSet &NonEquivalentDecls,
8185
StructuralEquivalenceKind EqKind,
8286
bool StrictTypeSpelling = false,
8387
bool Complain = true,
8488
bool ErrorOnTagTypeMismatch = false,
8589
bool IgnoreTemplateParmDepth = false)
86-
: FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls),
87-
EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling),
90+
: LangOpts(LangOpts), FromCtx(FromCtx), ToCtx(ToCtx),
91+
NonEquivalentDecls(NonEquivalentDecls), EqKind(EqKind),
92+
StrictTypeSpelling(StrictTypeSpelling),
8893
ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain),
8994
IgnoreTemplateParmDepth(IgnoreTemplateParmDepth) {}
9095

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,16 +478,23 @@ def warn_odr_function_type_inconsistent : Warning<
478478
"external function %0 declared with incompatible types in different "
479479
"translation units (%1 vs. %2)">,
480480
InGroup<ODR>;
481+
def warn_odr_tag_type_with_attributes : Warning<
482+
"type %0 has %select{an attribute|a member with an attribute}1 which "
483+
"currently causes the types to be treated as though they are incompatible">,
484+
InGroup<ODR>, DefaultError;
485+
def note_odr_attr_here : Note<"attribute %0 here">;
481486
def err_odr_tag_type_inconsistent
482-
: Error<"type %0 has incompatible definitions in different translation "
483-
"units">;
487+
: Error<"type %0 has incompatible definitions%select{| in different "
488+
"translation units}1">;
484489
def warn_odr_tag_type_inconsistent
485-
: Warning<"type %0 has incompatible definitions in different translation "
486-
"units">,
490+
: Warning<"type %0 has incompatible definitions%select{| in different "
491+
"translation units}1">,
487492
InGroup<ODR>;
488493
def note_odr_tag_kind_here: Note<
489494
"%0 is a %select{struct|interface|union|class|enum}1 here">;
490495
def note_odr_field : Note<"field %0 has type %1 here">;
496+
def note_odr_field_bit_width : Note<"bit-field %0 has bit-width %1 here">;
497+
def note_odr_field_not_bit_field : Note<"field %0 is not a bit-field">;
491498
def note_odr_field_name : Note<"field has name %0 here">;
492499
def note_odr_missing_field : Note<"no corresponding field here">;
493500
def note_odr_base : Note<"class has base type %0">;

clang/include/clang/Parse/Parser.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2579,7 +2579,8 @@ class Parser : public CodeCompletionHandler {
25792579
void ParseEnumSpecifier(SourceLocation TagLoc, DeclSpec &DS,
25802580
const ParsedTemplateInfo &TemplateInfo,
25812581
AccessSpecifier AS, DeclSpecContext DSC);
2582-
void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl);
2582+
void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl,
2583+
SkipBodyInfo *SkipBody = nullptr);
25832584
void ParseStructUnionBody(SourceLocation StartLoc, DeclSpec::TST TagType,
25842585
RecordDecl *TagDecl);
25852586

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4346,7 +4346,8 @@ class Sema final : public SemaBase {
43464346
Decl *ActOnEnumConstant(Scope *S, Decl *EnumDecl, Decl *LastEnumConstant,
43474347
SourceLocation IdLoc, IdentifierInfo *Id,
43484348
const ParsedAttributesView &Attrs,
4349-
SourceLocation EqualLoc, Expr *Val);
4349+
SourceLocation EqualLoc, Expr *Val,
4350+
SkipBodyInfo *SkipBody = nullptr);
43504351
void ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
43514352
Decl *EnumDecl, ArrayRef<Decl *> Elements, Scope *S,
43524353
const ParsedAttributesView &Attr);

clang/lib/AST/ASTContext.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/AST/APValue.h"
1717
#include "clang/AST/ASTConcept.h"
1818
#include "clang/AST/ASTMutationListener.h"
19+
#include "clang/AST/ASTStructuralEquivalence.h"
1920
#include "clang/AST/ASTTypeTraits.h"
2021
#include "clang/AST/Attr.h"
2122
#include "clang/AST/AttrIterator.h"
@@ -11443,6 +11444,22 @@ static QualType mergeEnumWithInteger(ASTContext &Context, const EnumType *ET,
1144311444
return {};
1144411445
}
1144511446

11447+
QualType ASTContext::mergeTagDefinitions(QualType LHS, QualType RHS) {
11448+
// C17 and earlier and C++ disallow two tag definitions within the same TU
11449+
// from being compatible.
11450+
if (LangOpts.CPlusPlus || !LangOpts.C23)
11451+
return {};
11452+
11453+
// C23, on the other hand, requires the members to be "the same enough", so
11454+
// we use a structural equivalence check.
11455+
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
11456+
StructuralEquivalenceContext Ctx(
11457+
getLangOpts(), *this, *this, NonEquivalentDecls,
11458+
StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
11459+
/*Complain=*/false, /*ErrorOnTagTypeMismatch=*/true);
11460+
return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
11461+
}
11462+
1144611463
QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
1144711464
bool Unqualified, bool BlockReturnType,
1144811465
bool IsConditionalOperator) {
@@ -11740,7 +11757,7 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
1174011757
/*AllowCXX=*/false, IsConditionalOperator);
1174111758
case Type::Record:
1174211759
case Type::Enum:
11743-
return {};
11760+
return mergeTagDefinitions(LHS, RHS);
1174411761
case Type::Builtin:
1174511762
// Only exactly equal builtin types are compatible, which is tested above.
1174611763
return {};

clang/lib/AST/ASTImporter.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,8 +2482,9 @@ bool ASTNodeImporter::IsStructuralMatch(Decl *From, Decl *To, bool Complain,
24822482
}
24832483

24842484
StructuralEquivalenceContext Ctx(
2485-
Importer.getFromContext(), Importer.getToContext(),
2486-
Importer.getNonEquivalentDecls(), getStructuralEquivalenceKind(Importer),
2485+
Importer.getToContext().getLangOpts(), Importer.getFromContext(),
2486+
Importer.getToContext(), Importer.getNonEquivalentDecls(),
2487+
getStructuralEquivalenceKind(Importer),
24872488
/*StrictTypeSpelling=*/false, Complain, /*ErrorOnTagTypeMismatch=*/false,
24882489
IgnoreTemplateParmDepth);
24892490
return Ctx.IsEquivalent(From, To);
@@ -4341,7 +4342,8 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
43414342

43424343
ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
43434344
StructuralEquivalenceContext Ctx(
4344-
FD1->getASTContext(), FD2->getASTContext(), NonEquivalentDecls,
4345+
Importer.getToContext().getLangOpts(), FD1->getASTContext(),
4346+
FD2->getASTContext(), NonEquivalentDecls,
43454347
StructuralEquivalenceKind::Default,
43464348
/* StrictTypeSpelling = */ false, /* Complain = */ false);
43474349
return Ctx.IsEquivalent(FD1, FD2);
@@ -10540,8 +10542,8 @@ bool ASTImporter::IsStructurallyEquivalent(QualType From, QualType To,
1054010542
}
1054110543
}
1054210544

10543-
StructuralEquivalenceContext Ctx(FromContext, ToContext, NonEquivalentDecls,
10544-
getStructuralEquivalenceKind(*this), false,
10545-
Complain);
10545+
StructuralEquivalenceContext Ctx(
10546+
getToContext().getLangOpts(), FromContext, ToContext, NonEquivalentDecls,
10547+
getStructuralEquivalenceKind(*this), false, Complain);
1054610548
return Ctx.IsEquivalent(From, To);
1054710549
}

0 commit comments

Comments
 (0)