This is an archive of the discontinued LLVM Phabricator instance.

[Sema] Fix a crash while converting constructors to deduction guides
ClosedPublic

Authored by erik.pilkington on Jul 17 2018, 11:38 AM.

Details

Summary

The problem was with constructors that have parameters that refer to previous ones, such as D below:

template <size_t> struct A {};
template <class T> struct D {
  D(T t, A<sizeof(t)>);
};
int main() { D d(0, A<4>()); }

We were canonicalizing the type of A<sizeof(t)> to refer to the t in the constructor as opposed to the new one in the deduction guide. This is because ParmVarDecls are profiled based on their type and offset, so the two ParmVarDecls profiled to the same ID, despite them belonging to distinct functions. The fix for this here is to add a bit to ParmVarDecl, IsParamOfDeductionGuide, that tracks if this ParmVarDecl is "spliced" from another function. This makes references to the new ParmVarDecl not canonicalize to the previous ParmVarDecl.

This patch also fixes a closely related bug where newly created ParmVarDecls were not being added to the CurrentInstantiationScope, leading to an assert when we try to find it later.

rdar://41330135

Thanks for taking a look!
Erik

Diff Detail

Repository
rC Clang

Event Timeline

rsmith added inline comments.Jul 17 2018, 5:04 PM
clang/lib/Sema/SemaTemplate.cpp
1899–1907 ↗(On Diff #155928)

This is really the problem: we shouldn't be doing a full canonicalization step here. I expect that even with your patch applied we'll still see problems for cases like:

template<unsigned long> struct X {};
template<typename T> struct A {
  A(T t, X<sizeof(t)>);
};
A a(0, {});
template<typename U> struct B {
  B(U u, X<sizeof(u)>);
};
B b(0, {});

... because we'll canonicalize the second parameter of B's deduction guide to have type X<sizeof(t)> (where that's the t from A's deduction guide).

So I think we should look at fixing the FIXME here properly. There seem to be at least two viable options:

  1. don't canonicalize the type; instead, extend template instantiation to be able to cope with one template referring to members of another template directly, without instantiating the class containing those members, or
  2. add a custom TreeTransform to do the canonicalization we want to do here, and to avoid the canonicalization we don't want to do

Both of these seem pretty tricky to get right, though, which is why we currently use the canonicalization hack :(

Implement @rsmith's second suggestion. Thanks!

clang/lib/Sema/SemaTemplate.cpp
1899–1907 ↗(On Diff #155928)

Yep, that still crashes :/

I started to implement 2 in the new patch. This implementation just unwraps typedefs into the deduction guide, but that is already enough to pass libcxx's test suite. This doesn't handle everything that we could, such as expression in a decltype [1]. This is fine for now though, because the canonicalization hack doesn't either. In fact, I couldn't find any cases where this patch fails but the canonicalization succeeds. I'm inclined to fix the crash now, address any extra cases in a follow-up if we actually want to support them. Does this seem reasonable to you?

It also seems we're reading pretty far between the lines of the standard here, do you think a DR should be filed?

[1]: I think we could support this is we wanted by stubbing out DeclRefExprs to members for a new (or existing?) opaque reference AST node that acted like a declval<T>() analog. This would allow us to do sfinae using the member's type without relying on it's context.

rsmith accepted this revision.Jul 27 2018, 11:42 AM
rsmith added inline comments.
clang/lib/Sema/SemaTemplate.cpp
1899–1907 ↗(On Diff #155928)

In core discussion, we agreed to handwave vigorously about such things in the wording for now... (the other option was that we'd spend many meetings refining wording to express what we mean here). A DR to keep us honest seems like a very good thing, especially if you have examples (such as the decltype example) where the correct behavior is unclear.

I think it's fine to do as you've done here (fix the crash for now, and address any subsequent necessary transformations as we find we need them). I'm pleasantly surprised by how simple the TreeTransform turned out to be (though I bet it makes the clang binary significantly larger).

Please can you add the example I gave above (or something like it) as a test case?

This revision is now accepted and ready to land.Jul 27 2018, 11:42 AM
This revision was automatically updated to reflect the committed changes.