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 <[email protected]> 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 <[email protected]> Date: Wed, 12 Nov 2025 11:50:43 -0800 Subject: [PATCH 2/4] Update clang/lib/Sema/SemaDeclCXX.cpp Co-authored-by: Petr Hosek <[email protected]> --- 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 <[email protected]> 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 <[email protected]> --- 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 <[email protected]> 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()) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
