[clang] ASTContext: flesh out implementation of getCommonNNS (#131964)
This properly implements getCommonNNS, for getting the common
NestedNameSpecifier, for which the previous implementation was a bare
minimum placeholder.
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9b0680c..4258d0d 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -271,6 +271,8 @@
as function arguments or return value respectively. Note that
:doc:`ThreadSafetyAnalysis` still does not perform alias analysis. The
feature will be default-enabled with ``-Wthread-safety`` in a future release.
+- Clang will now do a better job producing common nested names, when producing
+ common types for ternary operator, template argument deduction and multiple return auto deduction.
- The ``-Wsign-compare`` warning now treats expressions with bitwise not(~) and minus(-) as signed integers
except for the case where the operand is an unsigned integer
and throws warning if they are compared with unsigned integers (##18878).
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 7ed5b03..68a02f3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13462,13 +13462,155 @@
: ElaboratedTypeKeyword::None;
}
+/// Returns a NestedNameSpecifier which has only the common sugar
+/// present in both NNS1 and NNS2.
+static NestedNameSpecifier *getCommonNNS(ASTContext &Ctx,
+ NestedNameSpecifier *NNS1,
+ NestedNameSpecifier *NNS2,
+ bool IsSame) {
+ // If they are identical, all sugar is common.
+ if (NNS1 == NNS2)
+ return NNS1;
+
+ // IsSame implies both NNSes are equivalent.
+ NestedNameSpecifier *Canon = Ctx.getCanonicalNestedNameSpecifier(NNS1);
+ if (Canon != Ctx.getCanonicalNestedNameSpecifier(NNS2)) {
+ assert(!IsSame && "Should be the same NestedNameSpecifier");
+ // If they are not the same, there is nothing to unify.
+ // FIXME: It would be useful here if we could represent a canonically
+ // empty NNS, which is not identical to an empty-as-written NNS.
+ return nullptr;
+ }
+
+ NestedNameSpecifier *R = nullptr;
+ NestedNameSpecifier::SpecifierKind K1 = NNS1->getKind(), K2 = NNS2->getKind();
+ switch (K1) {
+ case NestedNameSpecifier::SpecifierKind::Identifier: {
+ assert(K2 == NestedNameSpecifier::SpecifierKind::Identifier);
+ IdentifierInfo *II = NNS1->getAsIdentifier();
+ assert(II == NNS2->getAsIdentifier());
+ // For an identifier, the prefixes are significant, so they must be the
+ // same.
+ NestedNameSpecifier *P = ::getCommonNNS(Ctx, NNS1->getPrefix(),
+ NNS2->getPrefix(), /*IsSame=*/true);
+ R = NestedNameSpecifier::Create(Ctx, P, II);
+ break;
+ }
+ case NestedNameSpecifier::SpecifierKind::Namespace:
+ case NestedNameSpecifier::SpecifierKind::NamespaceAlias: {
+ assert(K2 == NestedNameSpecifier::SpecifierKind::Namespace ||
+ K2 == NestedNameSpecifier::SpecifierKind::NamespaceAlias);
+ // The prefixes for namespaces are not significant, its declaration
+ // identifies it uniquely.
+ NestedNameSpecifier *P =
+ ::getCommonNNS(Ctx, NNS1->getPrefix(), NNS2->getPrefix(),
+ /*IsSame=*/false);
+ NamespaceAliasDecl *A1 = NNS1->getAsNamespaceAlias(),
+ *A2 = NNS2->getAsNamespaceAlias();
+ // Are they the same namespace alias?
+ if (declaresSameEntity(A1, A2)) {
+ R = NestedNameSpecifier::Create(Ctx, P, ::getCommonDeclChecked(A1, A2));
+ break;
+ }
+ // Otherwise, look at the namespaces only.
+ NamespaceDecl *N1 = A1 ? A1->getNamespace() : NNS1->getAsNamespace(),
+ *N2 = A2 ? A2->getNamespace() : NNS2->getAsNamespace();
+ R = NestedNameSpecifier::Create(Ctx, P, ::getCommonDeclChecked(N1, N2));
+ break;
+ }
+ case NestedNameSpecifier::SpecifierKind::TypeSpec:
+ case NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate: {
+ // FIXME: See comment below, on Super case.
+ if (K2 == NestedNameSpecifier::SpecifierKind::Super)
+ return Ctx.getCanonicalNestedNameSpecifier(NNS1);
+
+ assert(K2 == NestedNameSpecifier::SpecifierKind::TypeSpec ||
+ K2 == NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate);
+
+ // Only keep the template keyword if both sides have it.
+ bool Template =
+ K1 == NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate &&
+ K2 == NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate;
+
+ const Type *T1 = NNS1->getAsType(), *T2 = NNS2->getAsType();
+ if (T1 == T2) {
+ // If the types are indentical, then only the prefixes differ.
+ // A well-formed NNS never has these types, as they have
+ // special normalized forms.
+ assert((!isa<DependentNameType, ElaboratedType>(T1)));
+ // Only for a DependentTemplateSpecializationType the prefix
+ // is actually significant. A DependentName, which would be another
+ // plausible case, cannot occur here, as explained above.
+ bool IsSame = isa<DependentTemplateSpecializationType>(T1);
+ NestedNameSpecifier *P =
+ ::getCommonNNS(Ctx, NNS1->getPrefix(), NNS2->getPrefix(), IsSame);
+ R = NestedNameSpecifier::Create(Ctx, P, Template, T1);
+ break;
+ }
+ // TODO: Try to salvage the original prefix.
+ // If getCommonSugaredType removed any top level sugar, the original prefix
+ // is not applicable anymore.
+ NestedNameSpecifier *P = nullptr;
+ const Type *T = Ctx.getCommonSugaredType(QualType(T1, 0), QualType(T2, 0),
+ /*Unqualified=*/true)
+ .getTypePtr();
+
+ // A NestedNameSpecifier has special normalization rules for certain types.
+ switch (T->getTypeClass()) {
+ case Type::Elaborated: {
+ // An ElaboratedType is stripped off, it's Qualifier becomes the prefix.
+ auto *ET = cast<ElaboratedType>(T);
+ R = NestedNameSpecifier::Create(Ctx, ET->getQualifier(), Template,
+ ET->getNamedType().getTypePtr());
+ break;
+ }
+ case Type::DependentName: {
+ // A DependentName is turned into an Identifier NNS.
+ auto *DN = cast<DependentNameType>(T);
+ R = NestedNameSpecifier::Create(Ctx, DN->getQualifier(),
+ DN->getIdentifier());
+ break;
+ }
+ case Type::DependentTemplateSpecialization: {
+ // A DependentTemplateSpecializationType loses it's Qualifier, which
+ // is turned into the prefix.
+ auto *DTST = cast<DependentTemplateSpecializationType>(T);
+ T = Ctx.getDependentTemplateSpecializationType(
+ DTST->getKeyword(), /*NNS=*/nullptr, DTST->getIdentifier(),
+ DTST->template_arguments())
+ .getTypePtr();
+ P = DTST->getQualifier();
+ R = NestedNameSpecifier::Create(Ctx, DTST->getQualifier(), Template, T);
+ break;
+ }
+ default:
+ R = NestedNameSpecifier::Create(Ctx, P, Template, T);
+ break;
+ }
+ break;
+ }
+ case NestedNameSpecifier::SpecifierKind::Super:
+ // FIXME: Can __super even be used with data members?
+ // If it's only usable in functions, we will never see it here,
+ // unless we save the qualifiers used in function types.
+ // In that case, it might be possible NNS2 is a type,
+ // in which case we should degrade the result to
+ // a CXXRecordType.
+ return Ctx.getCanonicalNestedNameSpecifier(NNS1);
+ case NestedNameSpecifier::SpecifierKind::Global:
+ // The global NNS is a singleton.
+ assert(K2 == NestedNameSpecifier::SpecifierKind::Global &&
+ "Global NNS cannot be equivalent to any other kind");
+ llvm_unreachable("Global NestedNameSpecifiers did not compare equal");
+ }
+ assert(Ctx.getCanonicalNestedNameSpecifier(R) == Canon);
+ return R;
+}
+
template <class T>
-static NestedNameSpecifier *getCommonNNS(ASTContext &Ctx, const T *X,
- const T *Y) {
- // FIXME: Try to keep the common NNS sugar.
- return X->getQualifier() == Y->getQualifier()
- ? X->getQualifier()
- : Ctx.getCanonicalNestedNameSpecifier(X->getQualifier());
+static NestedNameSpecifier *getCommonQualifier(ASTContext &Ctx, const T *X,
+ const T *Y, bool IsSame) {
+ return ::getCommonNNS(Ctx, X->getQualifier(), Y->getQualifier(), IsSame);
}
template <class T>
@@ -13879,8 +14021,9 @@
*NY = cast<DependentNameType>(Y);
assert(NX->getIdentifier() == NY->getIdentifier());
return Ctx.getDependentNameType(
- getCommonTypeKeyword(NX, NY), getCommonNNS(Ctx, NX, NY),
- NX->getIdentifier(), NX->getCanonicalTypeInternal());
+ getCommonTypeKeyword(NX, NY),
+ getCommonQualifier(Ctx, NX, NY, /*IsSame=*/true), NX->getIdentifier(),
+ NX->getCanonicalTypeInternal());
}
case Type::DependentTemplateSpecialization: {
const auto *TX = cast<DependentTemplateSpecializationType>(X),
@@ -13889,8 +14032,9 @@
auto As = getCommonTemplateArguments(Ctx, TX->template_arguments(),
TY->template_arguments());
return Ctx.getDependentTemplateSpecializationType(
- getCommonTypeKeyword(TX, TY), getCommonNNS(Ctx, TX, TY),
- TX->getIdentifier(), As);
+ getCommonTypeKeyword(TX, TY),
+ getCommonQualifier(Ctx, TX, TY, /*IsSame=*/true), TX->getIdentifier(),
+ As);
}
case Type::UnaryTransform: {
const auto *TX = cast<UnaryTransformType>(X),
@@ -14047,7 +14191,8 @@
case Type::Elaborated: {
const auto *EX = cast<ElaboratedType>(X), *EY = cast<ElaboratedType>(Y);
return Ctx.getElaboratedType(
- ::getCommonTypeKeyword(EX, EY), ::getCommonNNS(Ctx, EX, EY),
+ ::getCommonTypeKeyword(EX, EY),
+ ::getCommonQualifier(Ctx, EX, EY, /*IsSame=*/false),
Ctx.getQualifiedType(Underlying),
::getCommonDecl(EX->getOwnedTagDecl(), EY->getOwnedTagDecl()));
}
diff --git a/clang/test/SemaCXX/sugar-common-types.cpp b/clang/test/SemaCXX/sugar-common-types.cpp
index 39a7621..3c4de0e 100644
--- a/clang/test/SemaCXX/sugar-common-types.cpp
+++ b/clang/test/SemaCXX/sugar-common-types.cpp
@@ -44,7 +44,7 @@
};
N t10 = 0 ? S1<X1>() : S1<Y1>(); // expected-error {{from 'S1<B1>' (aka 'S1<int>')}}
-N t11 = 0 ? S1<X1>::S2<X2>() : S1<Y1>::S2<Y2>(); // expected-error {{from 'S1<int>::S2<B2>' (aka 'S2<void>')}}
+N t11 = 0 ? S1<X1>::S2<X2>() : S1<Y1>::S2<Y2>(); // expected-error {{from 'S1<B1>::S2<B2>' (aka 'S2<void>')}}
template <class T> using Al = S1<T>;
@@ -88,7 +88,7 @@
// expected-error@-1 {{rvalue of type 'B1' (aka 'int')}}
N t20 = 0 ? (__underlying_type(EnumsX::X)){} : (__underlying_type(EnumsY::X)){};
-// expected-error@-1 {{rvalue of type '__underlying_type(Enums::X)' (aka 'int')}}
+// expected-error@-1 {{rvalue of type '__underlying_type(EnumsB::X)' (aka 'int')}}
using QX = const SB1 *;
using QY = const ::SB1 *;