Skip to content

[clang] ASTContext: flesh out implementation of getCommonNNS #131964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ Improvements to Clang's diagnostics
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).
Expand Down
167 changes: 156 additions & 11 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13462,13 +13462,155 @@ static ElaboratedTypeKeyword getCommonTypeKeyword(const T *X, const T *Y) {
: 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>
Expand Down Expand Up @@ -13879,8 +14021,9 @@ static QualType getCommonNonSugarTypeNode(ASTContext &Ctx, const Type *X,
*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),
Expand All @@ -13889,8 +14032,9 @@ static QualType getCommonNonSugarTypeNode(ASTContext &Ctx, const Type *X,
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),
Expand Down Expand Up @@ -14047,7 +14191,8 @@ static QualType getCommonSugarTypeNode(ASTContext &Ctx, const Type *X,
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()));
}
Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/sugar-common-types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ template <class T> struct S1 {
};

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>;

Expand Down Expand Up @@ -88,7 +88,7 @@ N t19 = 0 ? (__underlying_type(EnumsX::X)){} : (__underlying_type(EnumsY::Y)){};
// 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 *;
Expand Down
Loading