[clang] implement printing of canonical expressions (#135133)

This patch extends the canonicalization printing policy to cover
expressions
and template names, and wires that up to the template argument printer,
covering expressions, and to the expression within a dependent decltype.

This is helpful for debugging, or if these expressions somehow end up
in diagnostics, as without this patch they can print as completely
unrelated
expressions, which can be quite confusing.

This is because expressions are not uniqued, unlike types, and
when a template specialization containing an expression is the first to
be
canonicalized, the expression ends up appearing in the canonical type of
subsequent equivalent specializations.

Fixes https://github.com/llvm/llvm-project/issues/92292
This commit is contained in:
Matheus Izvekov
2025-04-14 12:59:36 -03:00
committed by GitHub
parent dba757a33c
commit 13b55ad3bb
15 changed files with 1080 additions and 22 deletions

View File

@@ -69,7 +69,7 @@ void StaticAccessedThroughInstanceCheck::check(
PrintingPolicyWithSuppressedTag.SuppressTagKeyword = true;
PrintingPolicyWithSuppressedTag.SuppressUnwrittenScope = true;
PrintingPolicyWithSuppressedTag.PrintCanonicalTypes =
PrintingPolicyWithSuppressedTag.PrintAsCanonical =
!BaseExpr->getType()->isTypedefNameType();
std::string BaseTypeName =

View File

@@ -33,7 +33,7 @@ bool MatchesAnyListedTypeNameMatcher::matches(
PrintingPolicy PrintingPolicyWithSuppressedTag(
Finder->getASTContext().getLangOpts());
PrintingPolicyWithSuppressedTag.PrintCanonicalTypes = CanonicalTypes;
PrintingPolicyWithSuppressedTag.PrintAsCanonical = CanonicalTypes;
PrintingPolicyWithSuppressedTag.SuppressElaboration = true;
PrintingPolicyWithSuppressedTag.SuppressScope = false;
PrintingPolicyWithSuppressedTag.SuppressTagKeyword = true;

View File

@@ -76,7 +76,7 @@ struct PrintingPolicy {
MSWChar(LO.MicrosoftExt && !LO.WChar), IncludeNewlines(true),
MSVCFormatting(false), ConstantsAsWritten(false),
SuppressImplicitBase(false), FullyQualifiedName(false),
PrintCanonicalTypes(false), PrintInjectedClassNameWithArguments(true),
PrintAsCanonical(false), PrintInjectedClassNameWithArguments(true),
UsePreferredNames(true), AlwaysIncludeTypeForTemplateArgument(false),
CleanUglifiedParameters(false), EntireContentsOfLargeArray(true),
UseEnumerators(true), UseHLSLTypes(LO.HLSL) {}
@@ -310,9 +310,9 @@ struct PrintingPolicy {
LLVM_PREFERRED_TYPE(bool)
unsigned FullyQualifiedName : 1;
/// Whether to print types as written or canonically.
/// Whether to print entities as written or canonically.
LLVM_PREFERRED_TYPE(bool)
unsigned PrintCanonicalTypes : 1;
unsigned PrintAsCanonical : 1;
/// Whether to print an InjectedClassNameType with template arguments or as
/// written. When a template argument is unnamed, printing it results in

View File

@@ -735,7 +735,7 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
llvm::raw_string_ostream POut(Proto);
DeclPrinter TArgPrinter(POut, SubPolicy, Context, Indentation);
const auto *TArgAsWritten = D->getTemplateSpecializationArgsAsWritten();
if (TArgAsWritten && !Policy.PrintCanonicalTypes)
if (TArgAsWritten && !Policy.PrintAsCanonical)
TArgPrinter.printTemplateArguments(TArgAsWritten->arguments(), nullptr);
else if (const TemplateArgumentList *TArgs =
D->getTemplateSpecializationArgs())
@@ -1124,7 +1124,7 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) {
S->getSpecializedTemplate()->getTemplateParameters();
const ASTTemplateArgumentListInfo *TArgAsWritten =
S->getTemplateArgsAsWritten();
if (TArgAsWritten && !Policy.PrintCanonicalTypes)
if (TArgAsWritten && !Policy.PrintAsCanonical)
printTemplateArguments(TArgAsWritten->arguments(), TParams);
else
printTemplateArguments(S->getTemplateArgs().asArray(), TParams);

View File

@@ -1724,6 +1724,8 @@ void JSONNodeDumper::VisitTemplateExpansionTemplateArgument(
void JSONNodeDumper::VisitExpressionTemplateArgument(
const TemplateArgument &TA) {
JOS.attribute("isExpr", true);
if (TA.isCanonicalExpr())
JOS.attribute("isCanonical", true);
}
void JSONNodeDumper::VisitPackTemplateArgument(const TemplateArgument &TA) {
JOS.attribute("isPack", true);

View File

@@ -1305,9 +1305,13 @@ void StmtPrinter::VisitDeclRefExpr(DeclRefExpr *Node) {
Qualifier->print(OS, Policy);
if (Node->hasTemplateKeyword())
OS << "template ";
bool ForceAnonymous =
Policy.PrintAsCanonical && VD->getKind() == Decl::NonTypeTemplateParm;
DeclarationNameInfo NameInfo = Node->getNameInfo();
if (IdentifierInfo *ID = NameInfo.getName().getAsIdentifierInfo();
ID || NameInfo.getName().getNameKind() != DeclarationName::Identifier) {
!ForceAnonymous &&
(ID || NameInfo.getName().getNameKind() != DeclarationName::Identifier)) {
if (Policy.CleanUglifiedParameters &&
isa<ParmVarDecl, NonTypeTemplateParmDecl>(VD) && ID)
OS << ID->deuglifiedName();

View File

@@ -559,9 +559,12 @@ void TemplateArgument::print(const PrintingPolicy &Policy, raw_ostream &Out,
printIntegral(*this, Out, Policy, IncludeType);
break;
case Expression:
getAsExpr()->printPretty(Out, nullptr, Policy);
case Expression: {
PrintingPolicy ExprPolicy = Policy;
ExprPolicy.PrintAsCanonical = isCanonicalExpr();
getAsExpr()->printPretty(Out, nullptr, ExprPolicy);
break;
}
case Pack:
Out << "<";

View File

@@ -410,9 +410,9 @@ bool TemplateName::containsUnexpandedParameterPack() const {
void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
Qualified Qual) const {
auto handleAnonymousTTP = [](TemplateDecl *TD, raw_ostream &OS) {
auto handleAnonymousTTP = [&](TemplateDecl *TD, raw_ostream &OS) {
if (TemplateTemplateParmDecl *TTP = dyn_cast<TemplateTemplateParmDecl>(TD);
TTP && TTP->getIdentifier() == nullptr) {
TTP && (Policy.PrintAsCanonical || TTP->getIdentifier() == nullptr)) {
OS << "template-parameter-" << TTP->getDepth() << "-" << TTP->getIndex();
return true;
}
@@ -430,6 +430,8 @@ void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
// names more often than to export them, thus using the original name is
// most useful in this case.
TemplateDecl *Template = getAsTemplateDecl();
if (Policy.PrintAsCanonical)
Template = cast<TemplateDecl>(Template->getCanonicalDecl());
if (handleAnonymousTTP(Template, OS))
return;
if (Qual == Qualified::None)
@@ -437,6 +439,10 @@ void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
else
Template->printQualifiedName(OS, Policy);
} else if (QualifiedTemplateName *QTN = getAsQualifiedTemplateName()) {
if (Policy.PrintAsCanonical) {
QTN->getUnderlyingTemplate().print(OS, Policy, Qual);
return;
}
if (NestedNameSpecifier *NNS = QTN->getQualifier();
Qual != Qualified::None && NNS)
NNS->print(OS, Policy);

View File

@@ -1357,6 +1357,8 @@ void TextNodeDumper::VisitTemplateExpansionTemplateArgument(
void TextNodeDumper::VisitExpressionTemplateArgument(
const TemplateArgument &TA) {
OS << " expr";
if (TA.isCanonicalExpr())
OS << " canonical";
dumpTemplateArgument(TA);
}

View File

@@ -177,7 +177,7 @@ void TypePrinter::spaceBeforePlaceHolder(raw_ostream &OS) {
static SplitQualType splitAccordingToPolicy(QualType QT,
const PrintingPolicy &Policy) {
if (Policy.PrintCanonicalTypes)
if (Policy.PrintAsCanonical)
QT = QT.getCanonicalType();
return QT.split();
}
@@ -1273,8 +1273,11 @@ void TypePrinter::printTypeOfAfter(const TypeOfType *T, raw_ostream &OS) {}
void TypePrinter::printDecltypeBefore(const DecltypeType *T, raw_ostream &OS) {
OS << "decltype(";
if (T->getUnderlyingExpr())
T->getUnderlyingExpr()->printPretty(OS, nullptr, Policy);
if (const Expr *E = T->getUnderlyingExpr()) {
PrintingPolicy ExprPolicy = Policy;
ExprPolicy.PrintAsCanonical = T->isCanonicalUnqualified();
E->printPretty(OS, nullptr, ExprPolicy);
}
OS << ')';
spaceBeforePlaceHolder(OS);
}
@@ -1548,7 +1551,7 @@ void TypePrinter::printTag(TagDecl *D, raw_ostream &OS) {
const ASTTemplateArgumentListInfo *TArgAsWritten =
S->getTemplateArgsAsWritten();
IncludeStrongLifetimeRAII Strong(Policy);
if (TArgAsWritten && !Policy.PrintCanonicalTypes)
if (TArgAsWritten && !Policy.PrintAsCanonical)
printTemplateArgumentList(OS, TArgAsWritten->arguments(), Policy,
TParams);
else
@@ -2422,9 +2425,8 @@ static void
printTo(raw_ostream &OS, ArrayRef<TA> Args, const PrintingPolicy &Policy,
const TemplateParameterList *TPL, bool IsPack, unsigned ParmIndex) {
// Drop trailing template arguments that match default arguments.
if (TPL && Policy.SuppressDefaultTemplateArgs &&
!Policy.PrintCanonicalTypes && !Args.empty() && !IsPack &&
Args.size() <= TPL->size()) {
if (TPL && Policy.SuppressDefaultTemplateArgs && !Policy.PrintAsCanonical &&
!Args.empty() && !IsPack && Args.size() <= TPL->size()) {
llvm::SmallVector<TemplateArgument, 8> OrigArgs;
for (const TA &A : Args)
OrigArgs.push_back(getArgument(A));

View File

@@ -287,7 +287,7 @@ PrintingPolicy CGDebugInfo::getPrintingPolicy() const {
PP.SuppressInlineNamespace =
PrintingPolicy::SuppressInlineNamespaceMode::None;
PP.PrintCanonicalTypes = true;
PP.PrintAsCanonical = true;
PP.UsePreferredNames = false;
PP.AlwaysIncludeTypeForTemplateArgument = true;
PP.UseEnumerators = false;

View File

@@ -3463,7 +3463,7 @@ Sema::findFailedBooleanCondition(Expr *Cond) {
{
llvm::raw_string_ostream Out(Description);
PrintingPolicy Policy = getPrintingPolicy();
Policy.PrintCanonicalTypes = true;
Policy.PrintAsCanonical = true;
FailedBooleanConditionPrinterHelper Helper(Policy);
FailedCond->printPretty(Out, &Helper, Policy, 0, "\n", nullptr);
}

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,10 @@ class Foo {
template float Foo::As();
template double Foo::As2();
template<int B1> struct B {};
template<class C1> struct C {};
template<class D1, int D2> struct D {};
// Partial ordering with conversion function templates.
struct X0 {
template<typename T> operator T*() {
@@ -64,6 +68,11 @@ struct X0 {
}
template<typename T> operator T*() const; // expected-note{{explicit instantiation refers here}}
template<int V> operator B<V>() const; // expected-note{{explicit instantiation refers here}}
template<class T, int V> operator C<T[V]>() const; // expected-note{{explicit instantiation refers here}}
#if __cplusplus >= 201103L
template<int V> operator D<decltype(V), V>() const; // expected-note{{explicit instantiation refers here}}
#endif
template<typename T> operator const T*() const {
T x = T();
@@ -76,7 +85,15 @@ struct X0 {
template X0::operator const char*() const; // expected-note{{'X0::operator const char *<char>' requested here}}
template X0::operator const int*(); // expected-note{{'X0::operator const int *<const int>' requested here}}
// FIXME: These diagnostics are printing canonical types.
template X0::operator float*() const; // expected-error{{explicit instantiation of undefined function template 'operator type-parameter-0-0 *'}}
template X0::operator B<0>() const; // expected-error {{undefined function template 'operator B<value-parameter-0-0>'}}
// FIXME: Within the above issue were we print canonical types here, printing the array
// index expression as non-canonical is extra bad.
template X0::operator C<int[1]>() const; // expected-error {{undefined function template 'operator C<type-parameter-0-0[V]>'}}
#if __cplusplus >= 201103L
template X0::operator D<int, 0>() const; // expected-error {{undefined function template 'operator D<decltype(value-parameter-0-0), value-parameter-0-0>'}}
#endif
void test_X0(X0 x0, const X0 &x0c) {
x0.operator const int*(); // expected-note{{in instantiation of function template specialization}}

View File

@@ -76,7 +76,7 @@ TEST(TypePrinter, TemplateId2) {
ASSERT_TRUE(PrintedTypeMatches(Code, {}, Matcher, "<int>",
[](PrintingPolicy &Policy) {
Policy.FullyQualifiedName = true;
Policy.PrintCanonicalTypes = true;
Policy.PrintAsCanonical = true;
}));
}