Author: Chuanqi Xu
Date: 2026-09-04T16:07:43+08:00
New Revision: dcdd1bc13db6af3d2fbed6ebe3d296d28107e77f

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

LOG: [C++20] [Modules] Identify the tag redecl through Using (#221147)

Close https://github.com/llvm/llvm-project/issues/96423

We've already have some logics to merge the redecls. But if the previous
decls are found through using decl, we can't merge them right now. As
the reproducer shows. The using decl is common in modules as a common
method to wrap headers into a module.

The fix is simple too. If the existing check fails, we'll check if the
found one is a using decl, if yes, we will reuse the existing check with
the target decl for using decl.

Added: 
    clang/test/Modules/pr96423.cppm

Modified: 
    clang/include/clang/Sema/Sema.h
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaTemplate.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9b07b591b8c07..2bfe58f02bc3a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4523,6 +4523,13 @@ class Sema final : public SemaBase {
   bool isDeclInScope(NamedDecl *D, DeclContext *Ctx, Scope *S = nullptr,
                      bool AllowInlineNamespace = false) const;
 
+  /// Determine whether a tag-like declaration found by lookup can be
+  /// redeclared in the given scope. If lookup found a using-shadow, also
+  /// consider the scope of the declaration named by the using-shadow.
+  bool isTagRedeclarationInScope(NamedDecl *D, DeclContext *Ctx,
+                                 Scope *S = nullptr,
+                                 bool AllowInlineNamespace = false) const;
+
   /// Finds the scope corresponding to the given decl context, if it
   /// happens to be an enclosing scope.  Otherwise return NULL.
   static Scope *getScopeForDeclContext(Scope *S, DeclContext *DC);

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d6114ccbae7fe..ccdc33d6fa39b 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -1658,6 +1658,17 @@ bool Sema::isDeclInScope(NamedDecl *D, DeclContext *Ctx, 
Scope *S,
   return IdResolver.isDeclInScope(D, Ctx, S, AllowInlineNamespace);
 }
 
+bool Sema::isTagRedeclarationInScope(NamedDecl *D, DeclContext *Ctx, Scope *S,
+                                     bool AllowInlineNamespace) const {
+  if (isDeclInScope(D, Ctx, S, AllowInlineNamespace))
+    return true;
+
+  if (auto *Shadow = dyn_cast<UsingShadowDecl>(D))
+    return isDeclInScope(Shadow->getTargetDecl(), Ctx, S, 
AllowInlineNamespace);
+
+  return false;
+}
+
 Scope *Sema::getScopeForDeclContext(Scope *S, DeclContext *DC) {
   DeclContext *TargetDC = DC->getPrimaryContext();
   do {
@@ -18647,8 +18658,9 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind 
TUK, SourceLocation KWLoc,
       // in the same scope (so that the definition/declaration completes or
       // rementions the tag), reuse the decl.
       if (TUK == TagUseKind::Reference || TUK == TagUseKind::Friend ||
-          isDeclInScope(DirectPrevDecl, SearchDC, S,
-                        SS.isNotEmpty() || isMemberSpecialization)) {
+          isTagRedeclarationInScope(DirectPrevDecl, SearchDC, S,
+                                    SS.isNotEmpty() ||
+                                        isMemberSpecialization)) {
 
         if (auto *RD = dyn_cast<CXXRecordDecl>(PrevDecl);
             RD && RD->isInjectedClassName()) {

diff  --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 4f7e59ea43ef1..b8b0c71894daa 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -2110,7 +2110,8 @@ DeclResult Sema::CheckClassTemplate(
           PrevDecl = (*Previous.begin())->getUnderlyingDecl();
       }
     }
-  } else if (PrevDecl && !isDeclInScope(Previous.getRepresentativeDecl(),
+  } else if (PrevDecl &&
+             !isTagRedeclarationInScope(Previous.getRepresentativeDecl(),
                                         SemanticContext, S, SS.isValid()))
     PrevDecl = PrevClassTemplate = nullptr;
 

diff  --git a/clang/test/Modules/pr96423.cppm b/clang/test/Modules/pr96423.cppm
new file mode 100644
index 0000000000000..a5dc3a484cfc0
--- /dev/null
+++ b/clang/test/Modules/pr96423.cppm
@@ -0,0 +1,41 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify 
-fsyntax-only
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify 
-fsyntax-only
+
+//--- header.h
+#ifndef HEADER_H
+#define HEADER_H
+
+struct X;
+template <class> struct Y;
+void f(struct X *, Y<int> *);
+
+#endif
+
+//--- M.cppm
+module;
+#include "header.h"
+
+namespace N {
+using ::X;
+using ::Y;
+}
+
+export module M;
+
+export namespace N {
+using N::X;
+using N::Y;
+}
+
+//--- use.cpp
+// expected-no-diagnostics
+import M;
+using namespace N;
+
+#include "header.h"


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to