Author: Volodymyr Sapsai
Date: 2022-04-04T18:48:30-07:00
New Revision: 29444f0444c6d3586969fd6fbe49c8188c02c244

URL: 
https://github.com/llvm/llvm-project/commit/29444f0444c6d3586969fd6fbe49c8188c02c244
DIFF: 
https://github.com/llvm/llvm-project/commit/29444f0444c6d3586969fd6fbe49c8188c02c244.diff

LOG: [modules] Merge ObjC interface ivars with anonymous types.

Without the fix ivars with anonymous types can trigger errors like

> error: 'TestClass::structIvar' from module 'Target' is not present in 
> definition of 'TestClass' provided earlier
> [...]
> note: declaration of 'structIvar' does not match

It happens because types of ivars from different modules are considered
to be different. And it is caused by not merging anonymous `TagDecl`
from different modules.

To fix that I've changed `serialization::needsAnonymousDeclarationNumber`
to handle anonymous `TagDecl` inside `ObjCInterfaceDecl`. But that's not
sufficient as C code inside `ObjCInterfaceDecl` doesn't use interface
decl as a decl context but switches to its parent (TranslationUnit in
most cases).  I'm changing that to make `ObjCContainerDecl` the lexical
decl context but keeping the semantic decl context intact.

Test "check-dup-decls-inside-objc.m" doesn't reflect a change in
functionality but captures the existing behavior to prevent regressions.

rdar://85563013

Differential Revision: https://reviews.llvm.org/D118525

Added: 
    clang/test/Modules/merge-anon-record-definition-in-objc.m
    clang/test/SemaObjC/check-dup-decls-inside-objc.m

Modified: 
    clang/lib/Parse/ParseObjc.cpp
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Serialization/ASTCommon.cpp
    clang/lib/Serialization/ASTReaderDecl.cpp
    clang/test/AST/ast-dump-decl.mm

Removed: 
    


################################################################################
diff  --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 719513e7bda09..c56fcb8cc3cfe 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -1871,9 +1871,9 @@ void Parser::HelperActionsForIvarDeclarations(Decl 
*interfaceDecl, SourceLocatio
   if (!RBraceMissing)
     T.consumeClose();
 
-  Actions.ActOnObjCContainerStartDefinition(interfaceDecl);
+  assert(getObjCDeclContext() == interfaceDecl &&
+         "Ivars should have interfaceDecl as their decl context");
   Actions.ActOnLastBitfield(T.getCloseLocation(), AllIvarDecls);
-  Actions.ActOnObjCContainerFinishDefinition();
   // Call ActOnFields() even if we don't have any decls. This is useful
   // for code rewriting tools that need to be aware of the empty list.
   Actions.ActOnFields(getCurScope(), atLoc, interfaceDecl, AllIvarDecls,
@@ -1908,8 +1908,7 @@ void Parser::ParseObjCClassInstanceVariables(Decl 
*interfaceDecl,
   assert(Tok.is(tok::l_brace) && "expected {");
   SmallVector<Decl *, 32> AllIvarDecls;
 
-  ParseScope ClassScope(this, Scope::DeclScope|Scope::ClassScope);
-  ObjCDeclContextSwitch ObjCDC(*this);
+  ParseScope ClassScope(this, Scope::DeclScope | Scope::ClassScope);
 
   BalancedDelimiterTracker T(*this, tok::l_brace);
   T.consumeOpen();
@@ -1973,13 +1972,13 @@ void Parser::ParseObjCClassInstanceVariables(Decl 
*interfaceDecl,
     }
 
     auto ObjCIvarCallback = [&](ParsingFieldDeclarator &FD) {
-      Actions.ActOnObjCContainerStartDefinition(interfaceDecl);
+      assert(getObjCDeclContext() == interfaceDecl &&
+             "Ivar should have interfaceDecl as its decl context");
       // Install the declarator into the interface decl.
       FD.D.setObjCIvar(true);
       Decl *Field = Actions.ActOnIvar(
           getCurScope(), FD.D.getDeclSpec().getSourceRange().getBegin(), FD.D,
           FD.BitfieldSize, visibility);
-      Actions.ActOnObjCContainerFinishDefinition();
       if (Field)
         AllIvarDecls.push_back(Field);
       FD.complete(Field);

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1e25346fde6f6..0b5b530bc756c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16059,9 +16059,20 @@ Decl *Sema::ActOnTag(Scope *S, unsigned TagSpec, 
TagUseKind TUK,
       // with C structs, unions, and enums when looking for a matching
       // tag declaration or definition. See the similar lookup tweak
       // in Sema::LookupName; is there a better way to deal with this?
-      while (isa<RecordDecl>(SearchDC) || isa<EnumDecl>(SearchDC))
+      while (isa<RecordDecl, EnumDecl, ObjCContainerDecl>(SearchDC))
+        SearchDC = SearchDC->getParent();
+    } else if (getLangOpts().CPlusPlus) {
+      // Inside ObjCContainer want to keep it as a lexical decl context but go
+      // past it (most often to TranslationUnit) to find the semantic decl
+      // context.
+      while (isa<ObjCContainerDecl>(SearchDC))
         SearchDC = SearchDC->getParent();
     }
+  } else if (getLangOpts().CPlusPlus) {
+    // Don't use ObjCContainerDecl as the semantic decl context for anonymous
+    // TagDecl the same way as we skip it for named TagDecl.
+    while (isa<ObjCContainerDecl>(SearchDC))
+      SearchDC = SearchDC->getParent();
   }
 
   if (Previous.isSingleResult() &&

diff  --git a/clang/lib/Serialization/ASTCommon.cpp 
b/clang/lib/Serialization/ASTCommon.cpp
index 673c56674f616..26b722b6b14a4 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -478,7 +478,9 @@ bool serialization::needsAnonymousDeclarationNumber(const 
NamedDecl *D) {
   // Otherwise, we only care about anonymous class members / block-scope decls.
   // FIXME: We need to handle lambdas and blocks within inline / templated
   // variables too.
-  if (D->getDeclName() || !isa<RecordDecl>(D->getLexicalDeclContext()))
+  if (D->getDeclName())
+    return false;
+  if (!isa<RecordDecl, ObjCInterfaceDecl>(D->getLexicalDeclContext()))
     return false;
   return isa<TagDecl>(D) || isa<FieldDecl>(D);
 }

diff  --git a/clang/lib/Serialization/ASTReaderDecl.cpp 
b/clang/lib/Serialization/ASTReaderDecl.cpp
index 40208eb00c153..43aacdeda9987 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -3065,6 +3065,8 @@ ASTDeclReader::getPrimaryDCForAnonymousDecl(DeclContext 
*LexicalDC) {
   if (auto *RD = dyn_cast<CXXRecordDecl>(LexicalDC)) {
     auto *DD = RD->getCanonicalDecl()->DefinitionData;
     return DD ? DD->Definition : nullptr;
+  } else if (auto *OID = dyn_cast<ObjCInterfaceDecl>(LexicalDC)) {
+    return OID->getCanonicalDecl()->getDefinition();
   }
 
   // For anything else, walk its merged redeclarations looking for a 
definition.

diff  --git a/clang/test/AST/ast-dump-decl.mm b/clang/test/AST/ast-dump-decl.mm
index 69ff46101da1e..c9282e9537e28 100644
--- a/clang/test/AST/ast-dump-decl.mm
+++ b/clang/test/AST/ast-dump-decl.mm
@@ -30,6 +30,23 @@ - (void) foo {
 // CHECK-NEXT:   ObjCInterface{{.*}} 'TestObjCImplementation'
 // CHECK-NEXT:   CXXCtorInitializer{{.*}} 'X'
 // CHECK-NEXT:     CXXConstructExpr
+// CHECK-NEXT:   CXXRecordDecl{{.*}} struct X definition
+// CHECK-NEXT:     DefinitionData
+// CHECK-NEXT:       DefaultConstructor
+// CHECK-NEXT:       CopyConstructor
+// CHECK-NEXT:       MoveConstructor
+// CHECK-NEXT:       CopyAssignment
+// CHECK-NEXT:       MoveAssignment
+// CHECK-NEXT:       Destructor
+// CHECK-NEXT:     CXXRecordDecl{{.*}} struct X
+// CHECK-NEXT:     FieldDecl{{.*}} i 'int'
+// CHECK-NEXT:     CXXConstructorDecl{{.*}} 'void ()
+// CHECK-NEXT:       CompoundStmt
+// CHECK-NEXT:     CXXConstructorDecl{{.*}} 'void (const X &)
+// CHECK-NEXT:       ParmVarDecl{{.*}} 'const X &'
+// CHECK-NEXT:     CXXConstructorDecl{{.*}} 'void (X &&)
+// CHECK-NEXT:       ParmVarDecl{{.*}} 'X &&'
+// CHECK-NEXT:     CXXDestructorDecl
 // CHECK-NEXT:   ObjCIvarDecl{{.*}} X
 // CHECK-NEXT:   ObjCMethodDecl{{.*}} foo
 

diff  --git a/clang/test/Modules/merge-anon-record-definition-in-objc.m 
b/clang/test/Modules/merge-anon-record-definition-in-objc.m
new file mode 100644
index 0000000000000..415ed97491339
--- /dev/null
+++ b/clang/test/Modules/merge-anon-record-definition-in-objc.m
@@ -0,0 +1,84 @@
+// UNSUPPORTED: -zos, -aix
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -fsyntax-only -F%t/Frameworks %t/test.m 
-Wno-objc-property-implementation -Wno-incomplete-implementation \
+// RUN:            -fmodules -fmodule-name=Target -fimplicit-module-maps 
-fmodules-cache-path=%t/modules.cache
+
+// RUN: %clang_cc1 -fsyntax-only -F%t/Frameworks -x objective-c++ %t/test.m 
-Wno-objc-property-implementation -Wno-incomplete-implementation \
+// RUN:            -fmodules -fmodule-name=Target -fimplicit-module-maps 
-fmodules-cache-path=%t/modules.cache
+
+// Test anonymous TagDecl inside Objective-C interfaces are merged and ivars 
with these anonymous types are merged too.
+
+//--- Frameworks/Foundation.framework/Headers/Foundation.h
+@interface NSObject
+@end
+
+//--- Frameworks/Foundation.framework/Modules/module.modulemap
+framework module Foundation {
+  header "Foundation.h"
+  export *
+}
+
+//--- Frameworks/Target.framework/Headers/Target.h
+#import <Foundation/Foundation.h>
+@interface TestClass : NSObject {
+@public
+  struct {
+    struct { int x; int y; } left;
+    struct { int w; int z; } right;
+  } structIvar;
+  union { int x; float y; } *unionIvar;
+  enum { kX = 0, } enumIvar;
+}
+@property struct { int u; } prop;
+#ifndef __cplusplus
+- (struct { int v; })method;
+#endif
+@end
+
+@interface TestClass() {
+@public
+  struct { int y; } extensionIvar;
+}
+@end
+
+@implementation TestClass {
+@public
+  struct { int z; } implementationIvar;
+}
+@end
+
+//--- Frameworks/Target.framework/Modules/module.modulemap
+framework module Target {
+  header "Target.h"
+  export *
+}
+
+//--- Frameworks/Redirect.framework/Headers/Redirect.h
+#import <Target/Target.h>
+
+//--- Frameworks/Redirect.framework/Modules/module.modulemap
+framework module Redirect {
+  header "Redirect.h"
+  export *
+}
+
+//--- test.m
+// At first import everything as non-modular.
+#import <Target/Target.h>
+// And now as modular to merge same entities obtained through 
diff erent sources.
+#import <Redirect/Redirect.h>
+// Non-modular import is achieved through using the same name (-fmodule-name) 
as the imported framework module.
+
+void test(TestClass *obj) {
+  obj->structIvar.left.x = 0;
+  obj->unionIvar->y = 1.0f;
+  obj->enumIvar = kX;
+  int tmp = obj.prop.u;
+#ifndef __cplusplus
+  tmp += [obj method].v;
+#endif
+
+  obj->extensionIvar.y = 0;
+  obj->implementationIvar.z = 0;
+}

diff  --git a/clang/test/SemaObjC/check-dup-decls-inside-objc.m 
b/clang/test/SemaObjC/check-dup-decls-inside-objc.m
new file mode 100644
index 0000000000000..b8245a5126e97
--- /dev/null
+++ b/clang/test/SemaObjC/check-dup-decls-inside-objc.m
@@ -0,0 +1,67 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wno-objc-root-class %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wno-objc-root-class -x objective-c++ 
%s
+
+// Test decls inside Objective-C entities are considered to be duplicates of 
same-name decls outside of these entities.
+
+@protocol SomeProtocol
+struct InProtocol {}; // expected-note {{previous definition is here}}
+- (union MethodReturnType { int x; float y; })returningMethod; // 
expected-note {{previous definition is here}}
+#ifdef __cplusplus
+// expected-error@-2 {{'MethodReturnType' cannot be defined in a parameter 
type}}
+#endif
+@end
+
+@interface Container {
+  struct InInterfaceCurliesWithField {} field; // expected-note {{previous 
definition is here}}
+  union InInterfaceCurlies { int x; float y; }; // expected-note {{previous 
definition is here}}
+}
+enum InInterface { kX = 0, }; // expected-note {{previous definition is here}}
+#ifdef __cplusplus
+enum class InInterfaceScoped { kXScoped = 0, }; // expected-note {{previous 
definition is here}}
+#endif
+@end
+
+@interface Container(Category)
+union InCategory { int x; float y; }; // expected-note {{previous definition 
is here}}
+@end
+
+@interface Container() {
+  enum InExtensionCurliesWithField: int { kY = 1, } extensionField; // 
expected-note {{previous definition is here}}
+  struct InExtensionCurlies {}; // expected-note {{previous definition is 
here}}
+}
+union InExtension { int x; float y; }; // expected-note {{previous definition 
is here}}
+@end
+
+@implementation Container {
+  union InImplementationCurliesWithField { int x; float y; } implField; // 
expected-note {{previous definition is here}}
+  enum InImplementationCurlies { kZ = 2, }; // expected-note {{previous 
definition is here}}
+}
+struct InImplementation {}; // expected-note {{previous definition is here}}
+@end
+
+@implementation Container(Category)
+enum InCategoryImplementation { kW = 3, }; // expected-note {{previous 
definition is here}}
+@end
+
+
+struct InProtocol { int a; }; // expected-error {{redefinition of 
'InProtocol'}}
+union MethodReturnType { int a; long b; }; // expected-error {{redefinition of 
'MethodReturnType'}}
+
+struct InInterfaceCurliesWithField { int a; }; // expected-error 
{{redefinition of 'InInterfaceCurliesWithField'}}
+union InInterfaceCurlies { int a; long b; }; // expected-error {{redefinition 
of 'InInterfaceCurlies'}}
+enum InInterface { kA = 10, }; // expected-error {{redefinition of 
'InInterface'}}
+#ifdef __cplusplus
+enum class InInterfaceScoped { kAScoped = 10, }; // expected-error 
{{redefinition of 'InInterfaceScoped'}}
+#endif
+
+union InCategory { int a; long b; }; // expected-error {{redefinition of 
'InCategory'}}
+
+enum InExtensionCurliesWithField: int { kB = 11, }; // expected-error 
{{redefinition of 'InExtensionCurliesWithField'}}
+struct InExtensionCurlies { int a; }; // expected-error {{redefinition of 
'InExtensionCurlies'}}
+union InExtension { int a; long b; }; // expected-error {{redefinition of 
'InExtension'}}
+
+union InImplementationCurliesWithField { int a; long b; }; // expected-error 
{{redefinition of 'InImplementationCurliesWithField'}}
+enum InImplementationCurlies { kC = 12, }; // expected-error {{redefinition of 
'InImplementationCurlies'}}
+struct InImplementation { int a; }; // expected-error {{redefinition of 
'InImplementation'}}
+
+enum InCategoryImplementation { kD = 13, }; // expected-error {{redefinition 
of 'InCategoryImplementation'}}


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to