Skip to content
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

[cxx-interop] Support class template specializations in namespaces. #34538

Merged
merged 1 commit into from Dec 4, 2020

Conversation

zoecarver
Copy link
Collaborator

This just changes an assertion and adds a test.

@zoecarver zoecarver added the c++ interop Feature: Interoperability with C++ label Nov 2, 2020
if (auto nominalDecl =
dyn_cast<NominalTypeDecl>(member->getDeclContext())) {
assert(nominalDecl == result ||
member == result && "interesting nesting of C types?");
Copy link
Collaborator

@hlopko hlopko Nov 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert doesn't make much sense to me. I tried to debug into your test case and I see that the ClassTemplateSpecializationDecl->decls() returns this (to me) surprising prev decl. And it's this one that member == resultapplies to. Do you understand why is this prev decl there?

In any case, could you please update the comment there to explain why member == result is acceptable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me preface this by saying I am not confident in my answer, this is only my hypothesis, someone more familiar with clang might have a more correct answer.

Because Orbiter is a redeclaration of Ship, it should get the previous declaration (Ship) as the PrevDecl (which gets added as one of the decl context's sub-declarations). However, it looks like it actually gets Orbiter as the previous declaration... not sure why that is.

Why didn't we come across this issue for record decls? Because (AFAIK) they aren't "redeclarable".

Why doesn't this issue arise for top-level declarations? Because then the "member" (really the class template specialization)'s decl context is not a nominal type decl, it's a "FileUnit" so this path wasn't ever taken.

So maybe it would be better to keep the old assertion and add:

if (member->getClangDecl() == result->getClangDecl()->getPreviousDecl())
  continue;

...with a comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you think of an example where a member would have a decl context that isn't result? Could we just say:

if (member->getDeclContext() != result) 
  continue;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised to see the redeclaration as a member, but then I'm not sure I understand why we're even attempting to import the redeclaration? My first instinct would be to check if the member is in decl->redecls() and if yes skip it. I tried this quickly and it seems it doesn't break any tests. Wdyt?

Also cc @gribozavr for clang expertise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me, I would also be interested to hear what Dmitri thinks, though.

I agree this is all odd. I think a similar (or the same?) problem is the genesis of the issue highlighted in #34557. In the test case in that PR, somehow we get from a fully specialized class template to the original class template (I presume via one of its redecls or something).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, that makes a lot of sense. I'll use that. Thanks for the information :)

The only thing that still seems odd to me: both class templates and "normal" record decls should contain injected class names, not just class templates. Any idea why this would only be failing for class templates and not all record decls?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While that does seem to work for the test case in this PR, it doesn't work for the following:

namespace Space {

template <class...> struct Ship;
template <class T, class ...Args> struct Ship<T(Args...)> {};

using Orbiter = Ship<void(bool)>;

} // namespace Space

I'd be interested to hear @gribozavr and your thoughts on why that is still asserting and on my above question (why this doesn't show up for "normal" record types).

Also, unfortunately, this doesn't fix the issue in #34557.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I see my mistake.

I decided to put the check for isInjectedClassName at the top of VisitRecordDecl (my thinking was that we should never really be visiting an injected class name, and it would have the same effect), but right above we set the decl we're visiting to decl->getDefinition() which essentially made the check a noop.

After applying your suggested fix, both test cases complied. (The issue in #34557 still did not, though.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also observe injected class names with record decls, it's just that their import doesn't fail so nobody noticed? At some point of the import we ask for canonical decl and might get a cache hit for the injected class name. I'm guessing though, we could just step through that with a debugger to be sure.

Copy link
Collaborator Author

@zoecarver zoecarver Nov 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also observe injected class names with record decls, it's just that their import doesn't fail so nobody noticed?

The import doesn't fail for these class template specializations either; we hit an assertion. I would expect that we should hit that assertion for "normal" record decl injected class names too.

Edit: I'll set some breakpoints and see if we ever run into an injected class name for a "normal" record decl.

@zoecarver zoecarver force-pushed the cxx/fix/same-nominal-in-namespace branch 2 times, most recently from 1985572 to 6c359cc Compare November 5, 2020 06:19
Copy link
Collaborator

@hlopko hlopko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@zoecarver
Copy link
Collaborator Author

@swift-ci please smoke test.

@zoecarver
Copy link
Collaborator Author

#34706 will fix the build errors.

// declaration.
if (auto nominalDecl =
dyn_cast<NominalTypeDecl>(result->getDeclContext()))
nominalDecl->addMember(result);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick comment (not sure I'll have time today to look into it deeper today). Doesn't this collide with this logic for namespaces?

enumDecl->addMember(member);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, you might be right. I wonder if this allows us to omit that logic entirely. (That would be nice!) I'll investigate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I did some investigation, and you're right. This was the source of the assertion we're hitting. The issue is that this only needs to be here for class template specializations because they're not added in loadAllMembers. So, I removed the addMember here and moved it to a new ClassTemplateVisitor member. Let me know what you think.

Copy link
Collaborator Author

@zoecarver zoecarver Dec 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also (kind of unrelated) I'm going to put up a patch to refactor the namespace logic in loadAllMembers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI @hlopko, ClassTemplateVisitor doesn't overlap with #33284. But you might still have merge conflicts.

This just changes an assertion and adds a test.
@zoecarver zoecarver force-pushed the cxx/fix/same-nominal-in-namespace branch from 6c359cc to c4363b9 Compare December 3, 2020 20:13
@zoecarver
Copy link
Collaborator Author

@swift-ci please smoke test.

@zoecarver
Copy link
Collaborator Author

@swift-ci please smoke test OS X.

@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

3 similar comments
@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

@zoecarver
Copy link
Collaborator Author

Still a no space left on disk error.

@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

1 similar comment
@zoecarver
Copy link
Collaborator Author

@swift-ci please test Windows.

@zoecarver zoecarver merged commit 40723a1 into apple:main Dec 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants