diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1137,9 +1137,10 @@ // Warnings for Plan 9 extensions. def Plan9AnonTag : DiagGroup<"plan9-anon-tag">; +def Plan9DuplicateMember : DiagGroup<"plan9-duplicate-member">; // Warnings group for warnings about Plan 9 extensions. -def Plan9 : DiagGroup<"plan9", [Plan9AnonTag]>; +def Plan9 : DiagGroup<"plan9", [Plan9AnonTag, Plan9DuplicateMember]>; // Warnings for Microsoft extensions. def MicrosoftCharize : DiagGroup<"microsoft-charize">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5815,6 +5815,9 @@ def err_redefinition_of_enumerator : Error<"redefinition of enumerator %0">; def err_duplicate_member : Error<"duplicate member %0">; +def ext_plan9_duplicate_member : ExtWarn< + "duplicate member %0 is a Plan 9 extension">, + InGroup; def err_misplaced_ivar : Error< "instance variables may not be placed in %select{categories|class extension}0">; def warn_ivars_in_interface : Warning< diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -1617,8 +1617,6 @@ NonOdrUseReason NOUR) : Expr(MemberExprClass, T, VK, OK), Base(Base), MemberDecl(MemberDecl), MemberDNLoc(NameInfo.getInfo()), MemberLoc(NameInfo.getLoc()) { - assert(!NameInfo.getName() || - MemberDecl->getDeclName() == NameInfo.getName()); MemberExprBits.IsArrow = IsArrow; MemberExprBits.HasQualifierOrFoundDecl = false; MemberExprBits.HasTemplateKWAndArgsInfo = false; @@ -1643,6 +1641,12 @@ HasQualOrFound ? 1 : 0, HasTemplateKWAndArgsInfo ? 1 : 0, TemplateArgs ? TemplateArgs->size() : 0); + // In Plan 9 C unnamed record fields can be accessed by their typedef'd name. + if (!C.getLangOpts().Plan9) { + assert(!NameInfo.getName() || + MemberDecl->getDeclName() == NameInfo.getName()); + } + void *Mem = C.Allocate(Size, alignof(MemberExpr)); MemberExpr *E = new (Mem) MemberExpr(Base, IsArrow, OperatorLoc, MemberDecl, NameInfo, T, VK, OK, NOUR); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -5133,6 +5133,13 @@ if (!SemaRef.isDeclInScope(PrevDecl, Owner, S)) return false; + // Plan 9 C allows duplicate record member names. + if (SemaRef.getLangOpts().Plan9) { + SemaRef.Diag(NameLoc, diag::ext_plan9_duplicate_member) << Name; + SemaRef.Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + return false; + } + SemaRef.Diag(NameLoc, diag::err_anonymous_record_member_redecl) << IsUnion << Name; SemaRef.Diag(PrevDecl->getLocation(), diag::note_previous_declaration); @@ -17451,9 +17458,15 @@ NewFD->setInvalidDecl(); if (PrevDecl && !isa(PrevDecl)) { - Diag(Loc, diag::err_duplicate_member) << II; - Diag(PrevDecl->getLocation(), diag::note_previous_declaration); - NewFD->setInvalidDecl(); + // Plan 9 C allows duplicate record member names. + if (getLangOpts().Plan9 && !getLangOpts().CPlusPlus) { + Diag(Loc, diag::ext_plan9_duplicate_member) << II; + Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + } else { + Diag(Loc, diag::err_duplicate_member) << II; + Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + NewFD->setInvalidDecl(); + } } if (!InvalidDecl && getLangOpts().CPlusPlus) { diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -1085,6 +1085,99 @@ } } +// Resolves fields in a record like a Plan 9 compiler. The resolution +// is performed in 3 passes: +// +// Pass 1: Look for field names that match the lookup name. +// Pass 2: Look for unnamed embedded records who's typedef'd name +// match the lookup name. +// Pass 3: Look for candidate fields in unnamed embedded records. +// +// Returns true if any matches were found. +static bool LookupPlan9(Sema &S, LookupResult &R, const RecordDecl *RD, + const DeclContext *Owner) { + bool Found = false; + + // Pass 1 & 2 are fused: conflicts are an error. + for (FieldDecl *Field : RD->fields()) { + // Pass 1 + if (Field->getDeclName() == R.getLookupName()) { + R.addDecl(Field); + Found = true; + } + + // Pass 2 + if (isa(Field->getType()) && Field->getDeclName().isEmpty()) { + if (const auto *Typedef = + Field->getTypeSourceInfo()->getType()->getAs()) { + if (Typedef->getDecl()->getDeclName() == R.getLookupName()) { + R.addDecl(Field); + Found = true; + } + } + } + } + + if (Found) + return true; + + LookupResult LocalR(LookupResult::Temporary, R); + + // Pass 3 + for (FieldDecl *Field : RD->fields()) { + const auto *EmbeddedRecord = Field->getType()->getAs(); + if (EmbeddedRecord && Field->getDeclName().isEmpty()) { + if (LookupPlan9(S, LocalR, EmbeddedRecord->getDecl(), Owner)) { + for (auto D = LocalR.begin(); D != LocalR.end(); ++D) { + IndirectFieldDecl *IF; + NamedDecl *ND = *D; + + // If we resolve to an indirect field: prepend our field to the chain. + if (auto *OldField = dyn_cast(ND)) { + auto OldChain = OldField->chain(); + unsigned OldChainSize = OldChain.size(); + + NamedDecl **NamedChain = + new (S.Context) NamedDecl *[OldChainSize + 1]; + NamedChain[0] = Field; + for (unsigned I = 0; I < OldChainSize; I++) + NamedChain[I + 1] = OldChain[I]; + + IF = IndirectFieldDecl::Create( + S.Context, const_cast(Owner), + OldField->getLocation(), OldField->getIdentifier(), + OldField->getType(), {NamedChain, OldChainSize + 1}); + } else { + // If the field is not indirect, make it so. This lets us perform + // nested lookup. + auto *FD = cast(ND); + + NamedDecl **NamedChain = new (S.Context) NamedDecl *[2]; + NamedChain[0] = Field; + NamedChain[1] = FD; + + IF = IndirectFieldDecl::Create( + S.Context, const_cast(Owner), FD->getLocation(), + FD->getIdentifier(), FD->getType(), {NamedChain, 2}); + } + + for (const auto *Attr : ND->attrs()) + IF->addAttr(Attr->clone(S.Context)); + + IF->setImplicit(); + + R.addDecl(IF); + Found = true; + } + + LocalR.clear(); + } + } + } + + return Found; +} + // Adds all qualifying matches for a name within a decl context to the // given lookup result. Returns true if any matches were found. static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) { @@ -1095,12 +1188,18 @@ DeclareImplicitMemberFunctionsWithName(S, R.getLookupName(), R.getNameLoc(), DC); - // Perform lookup into this declaration context. - DeclContext::lookup_result DR = DC->lookup(R.getLookupName()); - for (NamedDecl *D : DR) { - if ((D = R.getAcceptableDecl(D))) { - R.addDecl(D); - Found = true; + // If in Plan 9 C mode, resolve fields in a record like a Plan 9 compiler. + if (DC->isRecord() && S.getLangOpts().Plan9 && !S.getLangOpts().CPlusPlus) { + const RecordDecl *RD = cast(DC); + Found = LookupPlan9(S, R, RD, DC); + } else { + // Perform lookup into this declaration context. + DeclContext::lookup_result DR = DC->lookup(R.getLookupName()); + for (NamedDecl *D : DR) { + if ((D = R.getAcceptableDecl(D))) { + R.addDecl(D); + Found = true; + } } } diff --git a/clang/test/Sema/2c-anon-record.c b/clang/test/Sema/2c-anon-record.c --- a/clang/test/Sema/2c-anon-record.c +++ b/clang/test/Sema/2c-anon-record.c @@ -1,9 +1,95 @@ // RUN: %clang_cc1 %s -fsyntax-only -Wplan9 -verify -fplan9-extensions -struct A { +typedef struct { + int x; // expected-note {{previous declaration is here}} +} A; + +struct B { + A; // expected-warning {{anonymous structs are a Plan 9 extension}} + char *x; // expected-warning {{duplicate member 'x' is a Plan 9 extension}} +}; + +char *f0(struct B *b) { + return b->x; +} + +typedef struct { int x; +} C; + +struct D { + // expected-warning@+2 {{anonymous structs are a Plan 9 extension}} + // expected-note@+1 {{candidate found by name lookup is 'D::(anonymous)'}} + C; + int C; // expected-note {{candidate found by name lookup is 'D::C'}} }; -struct B { - struct A; // expected-warning {{anonymous structs are a Plan 9 extension}} +int f1(struct D *d) { + int x = d->x; + return d->C; // expected-error {{reference to 'C' is ambiguous}} +} + +typedef struct { + int x; // expected-note {{previous declaration is here}} +} E; + +struct F { + E; // expected-warning {{anonymous structs are a Plan 9 extension}} + int x; // expected-warning {{duplicate member 'x' is a Plan 9 extension}} +}; + +E f2(struct F *f) { + int x = f->x; + return f->E; +} + +typedef struct { + // expected-note@+3 {{previous declaration is here}} + // expected-note@+2 {{previous declaration is here}} + // expected-note@+1 {{candidate found by name lookup is 'H::a'}} + int a; + // expected-warning@+3 {{duplicate member 'a' is a Plan 9 extension}} + // expected-warning@+2 {{duplicate member 'a' is a Plan 9 extension}} + // expected-note@+1 {{candidate found by name lookup is 'H::a'}} + int a; +} G; + +struct H { + G; // expected-warning {{anonymous structs are a Plan 9 extension}} + int b; }; + +int f3(struct H *h) { + int b = h->b; + return h->a; // expected-error {{reference to 'a' is ambiguous}} +} + +typedef union { + int a; +} I; + +union J { + I; // expected-warning {{anonymous unions are a Plan 9 extension}} + int b : 4; +}; + +int f4(union J *j) { + int b = j->b; + return j->a; +} + +typedef struct K TK; + +struct K { + int x; +}; + +struct L { + TK; // expected-warning {{anonymous structs are a Plan 9 extension}} + int l; +}; + +TK f5(struct L *l) { + struct K k = l->K; // expected-error {{no member named 'K' in 'struct L'}} + return l->TK; +}