[clang] [clang] Fix conflicting declaration error with using_if_exists (PR #167646)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 1 10:49:55 PDT 2026
https://github.com/PiJoules updated https://github.com/llvm/llvm-project/pull/167646
>From 1cb2ea477f69a68f3d32a845b2ace087b22248d9 Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardchan at google.com>
Date: Tue, 11 Nov 2025 21:23:43 -0800
Subject: [PATCH 1/4] [clang] Fix conflicting declaration error with
using_if_exists
This fixes an issue with using_if_exists where we would hit
`conflicts with target of using declaration already in scope` with a
using_if_exists attribute referring to a declaration which did not
exist. That is, if we have `using ::bar
__attribute__((using_if_exists))` but `bar` is not in the global
namespace, then nothing should actually be declared here.
This PR contains the following changes:
1. Ensure we only diagnose this error if the target decl and [Non]Tag
decl can be substitutes for each other.
2. Prevent LookupResult from considering UnresolvedUsingIfExistsDecls in
the event of ambiguous results.
3. Update tests. This includes the minimal repo for a regression test,
and changes to existing tests which also seem to exhibit this bug.
Fixes #85335
---
clang/lib/Sema/SemaDeclCXX.cpp | 10 ++++
clang/lib/Sema/SemaLookup.cpp | 19 ++++--
clang/test/SemaCXX/using-if-exists.cpp | 81 +++++++++++++++++---------
3 files changed, 78 insertions(+), 32 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a33842ca56536..c1eb80cffb444 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -23,6 +23,7 @@
#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/GlobalDecl.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/TypeLoc.h"
@@ -12936,6 +12937,15 @@ bool Sema::CheckUsingShadowDecl(BaseUsingDecl *BUD, NamedDecl *Orig,
(isa_and_nonnull<UnresolvedUsingIfExistsDecl>(NonTag))) {
if (!NonTag && !Tag)
return false;
+
+ // Only check report the error if this using_if_exists decl can be a
+ // substitute for the original decl. LookupResult will find things with
+ // the same name but we also want to take into account namespaces and
+ // other scopes. GlobalDecl helps take care of that.
+ NamedDecl *UsedTag = NonTag ? NonTag : Tag;
+ if (GlobalDecl(Target) != GlobalDecl(UsedTag))
+ return false;
+
Diag(BUD->getLocation(), diag::err_using_decl_conflict);
Diag(Target->getLocation(), diag::note_using_decl_target);
Diag((NonTag ? NonTag : Tag)->getLocation(),
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index de53f6010a1b6..e2953eb6f0543 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -521,11 +521,15 @@ void LookupResult::resolveKind() {
llvm::SmallVector<const NamedDecl *, 4> EquivalentNonFunctions;
llvm::BitVector RemovedDecls(N);
+ llvm::BitVector UnresolvedUsingDecls(N);
for (unsigned I = 0; I < N; I++) {
const NamedDecl *D = Decls[I]->getUnderlyingDecl();
D = cast<NamedDecl>(D->getCanonicalDecl());
+ if (isa<UnresolvedUsingIfExistsDecl>(D))
+ UnresolvedUsingDecls.set(I);
+
// Ignore an invalid declaration unless it's the only one left.
// Also ignore HLSLBufferDecl which not have name conflict with other Decls.
if ((D->isInvalidDecl() || isa<HLSLBufferDecl>(D)) &&
@@ -633,6 +637,17 @@ void LookupResult::resolveKind() {
getSema().diagnoseEquivalentInternalLinkageDeclarations(
getNameLoc(), HasNonFunction, EquivalentNonFunctions);
+ if ((HasNonFunction && (HasFunction || HasUnresolved)) ||
+ (HideTags && HasTag && (HasFunction || HasNonFunction || HasUnresolved)))
+ Ambiguous = true;
+
+ if (Ambiguous && UnresolvedUsingDecls.count()) {
+ // If we would have an ambiguous reference but any of them are
+ // using_if_exist decls, ignore them since they are unresolved.
+ RemovedDecls |= UnresolvedUsingDecls;
+ Ambiguous = false;
+ }
+
// Remove decls by replacing them with decls from the end (which
// means that we need to iterate from the end) and then truncating
// to the new size.
@@ -640,10 +655,6 @@ void LookupResult::resolveKind() {
Decls[I] = Decls[--N];
Decls.truncate(N);
- if ((HasNonFunction && (HasFunction || HasUnresolved)) ||
- (HideTags && HasTag && (HasFunction || HasNonFunction || HasUnresolved)))
- Ambiguous = true;
-
if (Ambiguous && ReferenceToPlaceHolderVariable)
setAmbiguous(LookupAmbiguityKind::AmbiguousReferenceToPlaceholderVariable);
else if (Ambiguous)
diff --git a/clang/test/SemaCXX/using-if-exists.cpp b/clang/test/SemaCXX/using-if-exists.cpp
index 36fbbb171fb9a..7b1caca53e8da 100644
--- a/clang/test/SemaCXX/using-if-exists.cpp
+++ b/clang/test/SemaCXX/using-if-exists.cpp
@@ -22,28 +22,28 @@ using NS::x UIE;
namespace NS1 {}
namespace NS2 {}
namespace NS3 {
-int A(); // expected-note{{target of using declaration}}
-struct B {}; // expected-note{{target of using declaration}}
-int C(); // expected-note{{conflicting declaration}}
-struct D {}; // expected-note{{conflicting declaration}}
+int A();
+struct B {};
+int C();
+struct D {};
} // namespace NS3
-using NS1::A UIE;
-using NS2::A UIE; // expected-note{{using declaration annotated with 'using_if_exists' here}} expected-note{{conflicting declaration}}
-using NS3::A UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}}
-int i = A(); // expected-error{{reference to unresolved using declaration}}
+using NS1::A UIE; // OK since this declaration shouldn't exist since `A` is not in `NS1`
+using NS2::A UIE; // OK since this declaration shouldn't exist since `A` is not in `NS2`
+using NS3::A UIE; // OK since prior UIEs of `A` shouldn't have declare anything since they don't exist
+int i = A(); // OK since `A` resolved to the single UIE in the previous line
-using NS1::B UIE;
-using NS2::B UIE; // expected-note{{conflicting declaration}} expected-note{{using declaration annotated with 'using_if_exists' here}}
-using NS3::B UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}}
-B myB; // expected-error{{reference to unresolved using declaration}}
+using NS1::B UIE; // OK since this declaration shouldn't exist since `B` is not in `NS1`
+using NS2::B UIE; // OK since this declaration shouldn't exist since `B` is not in `NS2
+using NS3::B UIE; // OK since prior UIEs of `B` shouldn't have declare anything since they don't exist
+B myB; // OK since `B` resolved to the single UIE in the previous lin
using NS3::C UIE;
-using NS2::C UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}} expected-note{{target of using declaration}}
+using NS2::C UIE; // OK since NS2::C doesn't exist
int j = C();
using NS3::D UIE;
-using NS2::D UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}} expected-note{{target of using declaration}}
+using NS2::D UIE; // OK since NS2::D doesn't exist
D myD;
} // namespace test_redecl
@@ -113,7 +113,12 @@ struct NonDep : BaseEmpty {
namespace test_using_pack {
template <class... Ts>
struct S : Ts... {
- using typename Ts::x... UIE; // expected-error 2 {{target of using declaration conflicts with declaration already in scope}} expected-note{{conflicting declaration}} expected-note{{target of using declaration}}
+ // We don't expect any errors with conflicting targets for variables `a`, `b`, `c`,
+ // and `d` below this. For `a`, `x` will not be declared because neither E1 nor E2
+ // defines it. For `b`, `x` is the same type so there won't be any conflicts. For
+ // `c` and `d`, only one of the template parameters has a class that defines it,
+ // so there's no conflict.
+ using typename Ts::x... UIE;
};
struct E1 {};
@@ -121,21 +126,23 @@ struct E2 {};
S<E1, E2> a;
struct F1 {
- typedef int x; // expected-note 2 {{conflicting declaration}}
+ typedef int x;
};
struct F2 {
- typedef int x; // expected-note 2 {{target of using declaration}}
+ typedef int x;
};
S<F1, F2> b;
-S<E1, F2> c; // expected-note{{in instantiation of template class}}
-S<F1, E2> d; // expected-note{{in instantiation of template class}}
+S<E1, F2> c;
+S<F1, E2> d;
template <class... Ts>
struct S2 : Ts... {
- using typename Ts::x... UIE; // expected-error 2 {{target of using declaration conflicts with declaration already in scope}} expected-note 3 {{using declaration annotated with 'using_if_exists' here}} expected-note{{conflicting declaration}} expected-note{{target of using declaration}}
+ // OK for the same reasons listed in `struct S` above. We don't expect any conflicts w.r.t
+ // redefinitions of `x` but we still expect errors when using `x` for cases it's not available.
+ using typename Ts::x... UIE; // expected-note 4 {{using declaration annotated with 'using_if_exists' here}}
- x mem(); // expected-error 3 {{reference to unresolved using declaration}}
+ x mem(); // expected-error 4 {{reference to unresolved using declaration}}
};
S2<E1, E2> e; // expected-note{{in instantiation of template class}}
@@ -145,14 +152,15 @@ S2<F1, E2> h; // expected-note{{in instantiation of template class}}
template <class... Ts>
struct S3 : protected Ts... {
- using Ts::m... UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}} expected-note{{target of using declaration}}
+ // No errors for conflicting declarations because only one of the parent classes declares `m`.
+ using Ts::m... UIE;
};
struct B1 {
- enum { m }; // expected-note{{conflicting declaration}}
+ enum { m };
};
struct B2 {};
-S3<B1, B2> i; // expected-note{{in instantiation of template}}
+S3<B1, B2> i;
S<B2, B1> j;
} // namespace test_using_pack
@@ -170,9 +178,9 @@ NS2::x y; // expected-error {{reference to unresolved using declaration}}
} // namespace test_nested
namespace test_scope {
-int x; // expected-note{{conflicting declaration}}
+int x;
void f() {
- int x; // expected-note{{conflicting declaration}}
+ int x;
{
using ::x UIE; // expected-note {{using declaration annotated with 'using_if_exists' here}}
(void)x; // expected-error {{reference to unresolved using declaration}}
@@ -180,13 +188,13 @@ void f() {
{
using test_scope::x;
- using ::x UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}} expected-note{{target of using declaration}}
+ using ::x UIE; // OK since there's no `x` in the global namespace, so this wouldn't be any declaration
(void)x;
}
(void)x;
- using ::x UIE; // expected-error{{target of using declaration conflicts with declaration already in scope}} expected-note{{target of using declaration}}
+ using ::x UIE; // OK since there's no `x` in the global namespace, so this wouldn't be any declaration
(void)x;
}
} // namespace test_scope
@@ -224,3 +232,20 @@ int main() {
size = fake_printf();
size = std::fake_printf();
}
+
+// Regression test for https://github.com/llvm/llvm-project/issues/85335.
+// No errors should be reported here.
+namespace PR85335 {
+void foo();
+
+namespace N {
+ void bar();
+
+ using ::foo __attribute__((__using_if_exists__));
+ using ::bar __attribute__((__using_if_exists__));
+}
+
+void baz() {
+ N::bar();
+}
+} // namespace PR85335
>From 576bb9dd8951975546442a76f59d4075769ae621 Mon Sep 17 00:00:00 2001
From: PiJoules <6019989+PiJoules at users.noreply.github.com>
Date: Wed, 12 Nov 2025 11:50:43 -0800
Subject: [PATCH 2/4] Update clang/lib/Sema/SemaDeclCXX.cpp
Co-authored-by: Petr Hosek <phosek at google.com>
---
clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c1eb80cffb444..7a6463e616c41 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12938,7 +12938,7 @@ bool Sema::CheckUsingShadowDecl(BaseUsingDecl *BUD, NamedDecl *Orig,
if (!NonTag && !Tag)
return false;
- // Only check report the error if this using_if_exists decl can be a
+ // Only report the error if this using_if_exists decl can be a
// substitute for the original decl. LookupResult will find things with
// the same name but we also want to take into account namespaces and
// other scopes. GlobalDecl helps take care of that.
>From 181dab2c3a5b6b7c0753362472d29b19e50c8267 Mon Sep 17 00:00:00 2001
From: PiJoules <6019989+PiJoules at users.noreply.github.com>
Date: Wed, 12 Nov 2025 11:50:53 -0800
Subject: [PATCH 3/4] Update clang/test/SemaCXX/using-if-exists.cpp
Co-authored-by: Petr Hosek <phosek at google.com>
---
clang/lib/Sema/SemaDeclCXX.cpp | 26 ++++----------------------
clang/test/SemaCXX/using-if-exists.cpp | 2 +-
2 files changed, 5 insertions(+), 23 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 7a6463e616c41..43ba26cb8da0e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -23,7 +23,6 @@
#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
-#include "clang/AST/GlobalDecl.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/TypeLoc.h"
@@ -12931,28 +12930,11 @@ bool Sema::CheckUsingShadowDecl(BaseUsingDecl *BUD, NamedDecl *Orig,
if (FoundEquivalentDecl)
return false;
- // Always emit a diagnostic for a mismatch between an unresolved
- // using_if_exists and a resolved using declaration in either direction.
+ // This using_if_exists decl cannot be a subsitute for the original decl,
+ // so do not create a shadow decl for this case.
if (isa<UnresolvedUsingIfExistsDecl>(Target) !=
- (isa_and_nonnull<UnresolvedUsingIfExistsDecl>(NonTag))) {
- if (!NonTag && !Tag)
- return false;
-
- // Only report the error if this using_if_exists decl can be a
- // substitute for the original decl. LookupResult will find things with
- // the same name but we also want to take into account namespaces and
- // other scopes. GlobalDecl helps take care of that.
- NamedDecl *UsedTag = NonTag ? NonTag : Tag;
- if (GlobalDecl(Target) != GlobalDecl(UsedTag))
- return false;
-
- Diag(BUD->getLocation(), diag::err_using_decl_conflict);
- Diag(Target->getLocation(), diag::note_using_decl_target);
- Diag((NonTag ? NonTag : Tag)->getLocation(),
- diag::note_using_decl_conflict);
- BUD->setInvalidDecl();
- return true;
- }
+ (isa_and_nonnull<UnresolvedUsingIfExistsDecl>(NonTag)))
+ return false;
if (FunctionDecl *FD = Target->getAsFunction()) {
NamedDecl *OldDecl = nullptr;
diff --git a/clang/test/SemaCXX/using-if-exists.cpp b/clang/test/SemaCXX/using-if-exists.cpp
index 7b1caca53e8da..5c2ccd997048c 100644
--- a/clang/test/SemaCXX/using-if-exists.cpp
+++ b/clang/test/SemaCXX/using-if-exists.cpp
@@ -36,7 +36,7 @@ int i = A(); // OK since `A` resolved to the single UIE in the previous lin
using NS1::B UIE; // OK since this declaration shouldn't exist since `B` is not in `NS1`
using NS2::B UIE; // OK since this declaration shouldn't exist since `B` is not in `NS2
using NS3::B UIE; // OK since prior UIEs of `B` shouldn't have declare anything since they don't exist
-B myB; // OK since `B` resolved to the single UIE in the previous lin
+B myB; // OK since `B` resolved to the single UIE in the previous line
using NS3::C UIE;
using NS2::C UIE; // OK since NS2::C doesn't exist
>From cd912d7b07287e745f1fcb55f352fc27d3eaac06 Mon Sep 17 00:00:00 2001
From: Leonard Chan <leonardchan at google.com>
Date: Wed, 1 Apr 2026 17:49:30 +0000
Subject: [PATCH 4/4] Add comments
---
clang/lib/Sema/SemaLookup.cpp | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index e2953eb6f0543..cccc3cf3e2c31 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -637,8 +637,22 @@ void LookupResult::resolveKind() {
getSema().diagnoseEquivalentInternalLinkageDeclarations(
getNameLoc(), HasNonFunction, EquivalentNonFunctions);
- if ((HasNonFunction && (HasFunction || HasUnresolved)) ||
- (HideTags && HasTag && (HasFunction || HasNonFunction || HasUnresolved)))
+ // A lookup can be ambiguous if we find multiple declarations that cannot
+ // coexist. This occurs if:
+ //
+ // 1. We have a non-function (like a variable or namespace), which cannot
+ // be overloaded, and either a function or an unresolved using declaration.
+ bool ConflictWithNonFunction =
+ HasNonFunction && (HasFunction || HasUnresolved);
+
+ // 2. We have a hidden tag (struct or enum) and another declaration, and
+ // Because they both remain in the results, they must be from different
+ // scopes. If they were in the same scope, the tag would have been hidden
+ // and removed prior.
+ bool HiddenTagConflict =
+ HideTags && HasTag && (HasFunction || HasNonFunction || HasUnresolved);
+
+ if (ConflictWithNonFunction || HiddenTagConflict)
Ambiguous = true;
if (Ambiguous && UnresolvedUsingDecls.count()) {
More information about the cfe-commits
mailing list