Open deadlocklogic opened 5 months ago
@llvm/issue-subscribers-clang-frontend
Author: None (deadlocklogic)
CC @erichkeane
@shafik @erichkeane Any prognosis at least? Is this a bug or not? Thanks.
@mizvekov
This is a known limitation of how we deal with canonicalization of template arguments of expression kind.
Expressions in clang are not uniqued and we don't build a canonical form for them Also, uniquing them in their current form would yield little benefit, since they carry their source locations.
So when we need to build a canonical TemplateSpecializationType
which has a template argument of expression kind, we choose the first expression encountered as a placeholder, and the TemplateSpecializationType is uniqued based on a canonical profiling of that expression.
If you later try to build another canonical TemplateSpecializationType
which has a different expression, but which is canonically the same as the previous expression, then we won't build a new type and just return an existing one.
I suspect what is occurring in your example is that the canonical TST from the specialization argument of the partial specialization of ns1::Template2 is reusing the same TST that was built earlier for the injected specialization type of ns1::Template1. They have the same template arguments in the same order.
The issue does not occur in ns2 because there they don't have the same order.
Ie:
Injected TST of ns1::Template1: ns1::Template1<type-parameter-0-0, expression-parameter-0-1>
.
TST from partial specialization arguments of ns1::Template2: ns1::Template1<type-parameter-0-0, expression-parameter-0-1>
. The same as above.
Injected TST of ns2::Template1: ns2::Template1<type-parameter-0-0, expression-parameter-0-1>
.
TST from partial specialization arguments of ns2::Template2: ns2::Template1<type-parameter-0-0, expression-parameter-0-2>
. Not the same, notice the difference in the last argument.
we choose the first expression encountered as a placeholder, and the TemplateSpecializationType is uniqued based on a canonical profiling of that expression. If you later try to build another canonical TemplateSpecializationType which has a different expression, but which is canonically the same as the previous expression, then we won't build a new type and just return an existing one.
Yep, this mechanism also causes https://github.com/llvm/llvm-project/issues/84052.
I think one solution for the specific problem of AST dumps is to build a canonical expression printer, and use that for printing expressions off of any AST node which is uniqued.
So this ast-dump would have printed instead:
TemplateSpecializationType 'Template1<type-parameter-0-0, expr-parameter-0-1>' dependent Template1
,
which is less confusing.
Ie this printer would dig down an expression and ignore any non-structural parts.
In my case: the main usage of this is with printing, so I must resolve every AST declaration to its fully qualified name.
I wasn't satisfied with the default DeclPrinter
because it had difficulties with template arguments (type-parameter-*-*
), so I wrote my own.
Now consider the same example with replacing the NonTypeTemplateParmDecl
with TemplateTypeParmDecl
, the AST behaves correctly and as expected without any other dependencies on the outer context.
The default DeclPrinter is not at fault here. You are asking it to print a canonical type, and that's the information there.
If you are dumping the whole AST of a program, then you are going to find canonical types and references to canonical template parameters. There is no helping that.
If you are talking about seeing these 'type-parameters--' in the as-written portion of compiler issued diagnostics, then that's a bug, and we have to fix these one by one.
If you are talking about seeing these 'type-parameters--' in the as-written portion of compiler issued diagnostics, then that's a bug, and we have to fix these one by one.
Yes indeed, these are pretty annoying while printing a type but wouldn't solve the problem, as ns::Template2
in:
namespace ns1 {
template <typename TA1, bool TA2>
struct Template1 {};
template <typename TB1, bool TB2>
struct Template2 {};
template <typename U1, bool U2, bool U3>
struct Template2<Template1<U1, U2>, U3> {};
}
is printed as ns1::Template2<ns1::Template1<type-parameter-0-0, TA2>, U3>
instead of ns1::Template2<ns1::Template1<type-parameter-0-0, U2>, U3>
(independently on how expressions are represented in the AST, and disregarding the fact of unresolved arguments 'type-parameters--').
Ie this printer would dig down an expression and ignore any non-structural parts.
So the current DeclPrinter behavior is kind of broken: when I need to print types I am not really concerned on how expressions are expressed in the AST, rather than getting a valid string (which hopefully can be processed/parsed/used in code generation etc).
Expressions in clang are not uniqued and we don't build a canonical form for them Also, uniquing them in their current form would yield little benefit, since they carry their source locations.
So when we need to build a canonical TemplateSpecializationType which has a template argument of expression kind, we choose the first expression encountered as a placeholder, and the TemplateSpecializationType is uniqued based on a canonical profiling of that expression.
So currently, what is the best way to obtain a valid fully qualified notation of a decl (considering template arguments in case this is a template)?
Actually I am working on a tool and which needs to construct parallel raw templates but these synthetized templates may appear in a different namespace/context. So using a modified default type/decl printer, I can obtain at best:
namespace custom_ns {
template <typename TA1, bool TA2>
struct Template1 {};
template <typename TB1, bool TB2>
struct Template2 {};
template <typename U1, bool U2, bool U3>
struct Template2<::ns::Template1<U1, TA2 /*This is so unwanted/ breaks the compilation*/>, U3> {};
}
Anything to do here, or I have to wait for a better printer, or maybe fixing the mechanism as mentioned in tagged issue?
Anything to do here, or I have to wait for a better printer, or maybe fixing the mechanism as mentioned in tagged issue?
I believe this old patch of mine gets you almost there: https://github.com/mizvekov/llvm-project/commit/252f292ed22dd374aa177faaf18bab1a32199cfe
It's on my TODO list to rebase and create a PR for this soon.
Except that, in that patch, I chose not to preserve the as-written expression, as that would mean we would basically never be able to unique those CanonicalTemplateSpecialziationTypes.
But that patch would have those TSTs carry a converted argument list which is fully sugared.
For example, that list would have a converted argument list, in which this argument would be represented by a Declaration kind of TemplateArgument pointing to the NTTP decl as written.
Though again in that patch, the converted argument list is not wired to the AST dumper.
Consider:
The difference between the class templates in the 2 namespaces is:
typename U1, bool U3, bool U2
instead oftypename U1, bool U2, bool U3
in the class template partial specialization. A repro dump is provided. While the expected difference is just the template parameter order, this is not the case. The AST difference is:Why the
clang::NonTypeTemplateParm
TA2
is present? Even though it is not in the current semantic context. I really think this is a bug, and the second result should be the correct one. Thanks.