ksaunders created this revision.
ksaunders added a reviewer: rsmith.
ksaunders added a project: clang.
Herald added a project: All.
ksaunders requested review of this revision.
Herald added a subscriber: cfe-commits.

This patch implements the Plan 9 member resolution algorithm which is described 
referenced in the original revision: https://reviews.llvm.org/D127462. The test 
code is illustrative as to what is considered valid, as it was derived from 
samples compiled using the Plan 9 C compiler.

It is not clear what the policy on issuing diagnostics is for extensions usage, 
so there is a warning emitted for usage of duplicated members (which is a Plan 
9 extension). However this might not be a good idea, as there could be 
potentially a large number of warnings emitted, as this is used throughout 
headers Plan 9 C.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D130857

Files:
  clang/include/clang/Basic/DiagnosticGroups.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/AST/Expr.cpp
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaLookup.cpp
  clang/test/Sema/2c-anon-record.c

Index: clang/test/Sema/2c-anon-record.c
===================================================================
--- clang/test/Sema/2c-anon-record.c
+++ 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;
+}
Index: clang/lib/Sema/SemaLookup.cpp
===================================================================
--- clang/lib/Sema/SemaLookup.cpp
+++ 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<RecordType>(Field->getType()) && Field->getDeclName().isEmpty()) {
+      if (const auto *Typedef =
+              Field->getTypeSourceInfo()->getType()->getAs<TypedefType>()) {
+        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<RecordType>();
+    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<IndirectFieldDecl>(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<DeclContext *>(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<FieldDecl>(ND);
+
+            NamedDecl **NamedChain = new (S.Context) NamedDecl *[2];
+            NamedChain[0] = Field;
+            NamedChain[1] = FD;
+
+            IF = IndirectFieldDecl::Create(
+                S.Context, const_cast<DeclContext *>(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<RecordDecl>(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;
+      }
     }
   }
 
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ 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<TagDecl>(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) {
Index: clang/lib/AST/Expr.cpp
===================================================================
--- clang/lib/AST/Expr.cpp
+++ 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);
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ 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<Plan9DuplicateMember>;
 def err_misplaced_ivar : Error<
   "instance variables may not be placed in %select{categories|class extension}0">;
 def warn_ivars_in_interface : Warning<
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ 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">;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to