This is an archive of the discontinued LLVM Phabricator instance.

[WIP][clang] Add PrintingPolicy callback for identifying default template arguments
AbandonedPublic

Authored by Michael137 on Dec 20 2022, 11:36 AM.

Details

Summary

This patch adds a hook into the TypePrinter to allow clients
decide whether a template argument corresponds to the default
parameter.

Motivation

DWARF encodes information about template instantiations, but
not about the generic definition of a template. If an argument
in a template instantiation corresponds to the default parameter
of the generic template then DWARF will attach a DW_AT_default_value
to that argument. LLDB uses the Clang TypePrinter for
displaying/formatting C++ types but currently can't give Clang the ClassTemplateDecl
that it expects. Since LLDB does know whether a particular instantiation has
default arguments, this patch allows LLDB (and other clients with external AST sources)
to help Clang in identifying those default arguments.

Alternatives

  1. Encode information about generic template definitions in DWARF. It's unclear how long it would take to get this proposed and implemented and whether something like this would work in the first place. If we could construct ClassTemplateDecls with the correct generic parameters this patch wouldn't be required.
  1. Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

Diff Detail

Event Timeline

Michael137 created this revision.Dec 20 2022, 11:36 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 20 2022, 11:36 AM
  • Remove debug print in test

Overall this seems to have a pretty small surface area, so I'd be fine with including this.

clang/lib/AST/TypePrinter.cpp
2095

maybe this body becomes slightly less oddly indented when converting it to a range-based for with a counter?

Michael137 edited the summary of this revision. (Show Details)

@aaron.ballman we're trying to fix printing of C++ types with default template arguments in LLDB. Currently DWARF doesn't have the necessary info to construct a ClassTemplateDecl with generic parameters, which means the logic for suppressing them in the TypePrinter doesn't quite work.

This patch outlines of one approach we considered. We add a hook into the TypePrinter to allow external AST sources to answer the question "is template argument X defaulted?".

Another alternative would be to have a TemplateArgument::IsDefaulted which gets set in LLDB and read from the TypePrinter.

Do you think either of these approaches would be fine for inclusion?

clang/include/clang/AST/PrettyPrinter.h
52

The TrisState is needed because LLDB has two AST sources:

  1. DWARF
  2. clang modules

When we import a type from a clang module we would never have consulted DWARF about whether a parameter was defaulted. So instead of more complex bookkeeping it seemed simpler to say "if we've never seen this decl when parsing DWARF, I can't say anything about the default-ness of the arguments"

  • Fix test. Move variable into loop
Michael137 published this revision for review.Dec 22 2022, 3:14 AM

putting into review queue to hear people's opinion on something along these lines

Herald added a project: Restricted Project. · View Herald TranscriptDec 22 2022, 3:14 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
dblaikie added inline comments.Jan 3 2023, 12:36 PM
clang/include/clang/AST/PrettyPrinter.h
52

We don't usually use the k prefix for enums (the style guide mentions using an acronym like TS_ - though even that's unnecessary with an enum class, where you have to use EnumClassName prefix anyway, so there's no issue with ambiguity/name collisions of the enumerators)

But also, I'm guessing we probably use std::optional<bool> for this sort of thing more frequently than defining a three-state enum (even though std::optional<bool> can be a bit awkward to use/error prone, it's not too bad for limited uses like this).

Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

I think this might be worth exploring as a cleaner solution to the problem. TemplateArgument has a union of structures for the various kinds of template arguments it represents (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L140). All of the structures in that union start with an unsigned Kind field to discriminate between the members. There are only 8 kinds currently (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L63), so we could turn Kind into a bit-field and then steal a bit for IsDefaulted without increasing memory overhead. Do you think that's a reasonable approach to try instead?

Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

I think this might be worth exploring as a cleaner solution to the problem. TemplateArgument has a union of structures for the various kinds of template arguments it represents (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L140). All of the structures in that union start with an unsigned Kind field to discriminate between the members. There are only 8 kinds currently (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L63), so we could turn Kind into a bit-field and then steal a bit for IsDefaulted without increasing memory overhead. Do you think that's a reasonable approach to try instead?

FWIW, I Think I discouraged @Michael137 from going down that direction since it felt weird to me to add that sort of thing to the Clang ASTs for an lldb-only use case, and a callback seemed more suitable. But this is hardly my wheelhouse - if you reckon that's the better direction, carry on, I expect @Michael137 will be on board with that.

aaron.ballman added a subscriber: erichkeane.

Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

I think this might be worth exploring as a cleaner solution to the problem. TemplateArgument has a union of structures for the various kinds of template arguments it represents (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L140). All of the structures in that union start with an unsigned Kind field to discriminate between the members. There are only 8 kinds currently (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L63), so we could turn Kind into a bit-field and then steal a bit for IsDefaulted without increasing memory overhead. Do you think that's a reasonable approach to try instead?

FWIW, I Think I discouraged @Michael137 from going down that direction since it felt weird to me to add that sort of thing to the Clang ASTs for an lldb-only use case, and a callback seemed more suitable. But this is hardly my wheelhouse - if you reckon that's the better direction, carry on, I expect @Michael137 will be on board with that.

Adding in @erichkeane as templates code owner in case he has opinions.

I agree it's a bit weird to modify the AST only for lldb only, but adding a callback to the printing policy is basically an lldb-only change as well (I don't imagine folks would use that callback all that often). So my thinking is that if we can encode the information in the AST for effectively zero cost, that helps every consumer of the AST (thinking about things like clang-tidy) and not just people printing. However, this is not a strongly held position, so if there's a preference for the current approach, it seems workable to me.

A Class template instantiation SHOULD have its link back to the class template, and should be able to calculate whether the template argument is defaulted, right? At least if it is the SAME as the default (that is, I'm not sure how well we can tell the difference between a defaulted arg, and a arg set to the default value).

I wouldn't expect a bool to be necessary, and I see isSubstitutedDefaultArgument seems to do that work, right?

So I guess I wonder why 'print the defaulted template args' isn't just a printing-policy?

A Class template instantiation SHOULD have its link back to the class template, and should be able to calculate whether the template argument is defaulted, right?

For a normal/clang-built AST, yes. But as mentioned in this review description, for LLDB, DWARF doesn't encode enough information to make the class template properly. So it only knows if a given class template specialization's argument is defaulted.

Michael137 added a comment.EditedJan 16 2023, 1:43 AM

Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

I think this might be worth exploring as a cleaner solution to the problem. TemplateArgument has a union of structures for the various kinds of template arguments it represents (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L140). All of the structures in that union start with an unsigned Kind field to discriminate between the members. There are only 8 kinds currently (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L63), so we could turn Kind into a bit-field and then steal a bit for IsDefaulted without increasing memory overhead. Do you think that's a reasonable approach to try instead?

FWIW, I Think I discouraged @Michael137 from going down that direction since it felt weird to me to add that sort of thing to the Clang ASTs for an lldb-only use case, and a callback seemed more suitable. But this is hardly my wheelhouse - if you reckon that's the better direction, carry on, I expect @Michael137 will be on board with that.

Adding in @erichkeane as templates code owner in case he has opinions.

I agree it's a bit weird to modify the AST only for lldb only, but adding a callback to the printing policy is basically an lldb-only change as well (I don't imagine folks would use that callback all that often). So my thinking is that if we can encode the information in the AST for effectively zero cost, that helps every consumer of the AST (thinking about things like clang-tidy) and not just people printing. However, this is not a strongly held position, so if there's a preference for the current approach, it seems workable to me.

Thanks for taking a look @aaron.ballman @erichkeane

I prepared an alternative draft patch series with your suggestion of adding an IsDefaulted bit to TemplateArgument:

Is this what you had in mind?

This *significantly* simplifies the LLDB support: https://reviews.llvm.org/D141828

So I'd prefer this over the callback approach TBH.

A Class template instantiation SHOULD have its link back to the class template, and should be able to calculate whether the template argument is defaulted, right? At least if it is the SAME as the default (that is, I'm not sure how well we can tell the difference between a defaulted arg, and a arg set to the default value).

I wouldn't expect a bool to be necessary, and I see isSubstitutedDefaultArgument seems to do that work, right?

As @dblaikie mentioned, unfortunately LLDB currently isn't able to construct the ClassTemplateDecl in a way where isSubstitutedDefaultArgument would correctly deduce whether a template instantiation has defaulted arguments. In DWARF we only have info about a template instantiation (i.e., the structure of the generic template parameters is not encoded). So we can't supply (Non)TypeTemplateParamDecl::setDefaultArgument with the generic arguments Clang expects. The ClassTemplateDecl parameters are set up here: https://github.com/llvm/llvm-project/blob/main/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp#L1391-L1402

Regardless of how complex the template parameters get, LLDB just turns each into a plain (Non)TypeTemplateParamDecl

Add something like a bool IsDefaulted somewhere in Clang, e.g., in TemplateArgument and consult it from the TypePrinter. This would be much simpler but requires adding a field on one of the Clang types

I think this might be worth exploring as a cleaner solution to the problem. TemplateArgument has a union of structures for the various kinds of template arguments it represents (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L140). All of the structures in that union start with an unsigned Kind field to discriminate between the members. There are only 8 kinds currently (https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/TemplateBase.h#L63), so we could turn Kind into a bit-field and then steal a bit for IsDefaulted without increasing memory overhead. Do you think that's a reasonable approach to try instead?

FWIW, I Think I discouraged @Michael137 from going down that direction since it felt weird to me to add that sort of thing to the Clang ASTs for an lldb-only use case, and a callback seemed more suitable. But this is hardly my wheelhouse - if you reckon that's the better direction, carry on, I expect @Michael137 will be on board with that.

Adding in @erichkeane as templates code owner in case he has opinions.

I agree it's a bit weird to modify the AST only for lldb only, but adding a callback to the printing policy is basically an lldb-only change as well (I don't imagine folks would use that callback all that often). So my thinking is that if we can encode the information in the AST for effectively zero cost, that helps every consumer of the AST (thinking about things like clang-tidy) and not just people printing. However, this is not a strongly held position, so if there's a preference for the current approach, it seems workable to me.

Thanks for taking a look @aaron.ballman @erichkeane

I prepared an alternative draft patch series with your suggestion of adding an IsDefaulted bit to TemplateArgument:

Is this what you had in mind?

This *significantly* simplifies the LLDB support: https://reviews.llvm.org/D141828

So I'd prefer this over the callback approach TBH.

A Class template instantiation SHOULD have its link back to the class template, and should be able to calculate whether the template argument is defaulted, right? At least if it is the SAME as the default (that is, I'm not sure how well we can tell the difference between a defaulted arg, and a arg set to the default value).

I wouldn't expect a bool to be necessary, and I see isSubstitutedDefaultArgument seems to do that work, right?

As @dblaikie mentioned, unfortunately LLDB currently isn't able to construct the ClassTemplateDecl in a way where isSubstitutedDefaultArgument would correctly deduce whether a template instantiation has defaulted arguments. In DWARF we only have info about a template instantiation (i.e., the structure of the generic template parameters is not encoded). So we can't supply (Non)TypeTemplateParamDecl::setDefaultArgument with the generic arguments Clang expects. The ClassTemplateDecl parameters are set up here: https://github.com/llvm/llvm-project/blob/main/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp#L1391-L1402

Regardless of how complex the template parameters get, LLDB just turns each into a plain (Non)TypeTemplateParamDecl

Got it, I missed that part, I'm not too familiar with how LLDB works in this case. I'll take a look at those other patches.