-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[cxx-interop] Emit diagnostic to mark C++ type as non-copyable #84520
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
base: main
Are you sure you want to change the base?
[cxx-interop] Emit diagnostic to mark C++ type as non-copyable #84520
Conversation
@swift-ci please smoke test |
|
||
using DerivedNonCopyable = Derived<NonCopyable>; | ||
|
||
template <typename T> struct SWIFT_COPYABLE_IF(T) Annotated { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should have a note here, saying something like:
template <typename T> struct SWIFT_COPYABLE_IF(T) Annotated {
`- note: Annotated<OwnsT<NonCopyable>> is not Copyable because the required type `T` (aka `OwnsT<NonCopyable>`) is not Copyable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(feel free to bikeshed this phrasing btw)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, but SWIFT_COPYABLE_IF
can take multiple parameters, so we may end up with something like Annotated<OwnsT<NonCopyable>, B, C<D<E>>> is not Copyable because none of the required types 'T1' (aka 'OwnsT<NonCopyable>'), 'T2' (aka 'B'), 'T3' (aka 'C<D<E>>') are ~Copyable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. It may be simpler to emit those as separate notes, since not all of the required types are necessary to be noncopyable for this. So one note pointing out each type that is noncopyable but needs to be copyable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I can do that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No wait, you mean emitting a note when we import Annotated<OwnsT<NonCopyable>>
as Copyable? Or non copyable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would this note be emitted? When we try to "copy" a type twice and get an error saying that it cannot be consumed twice, so we explain to the user that actually the type got imported as ~Copyable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah no my bad, I was misunderstanding the SWIFT_COPYABLE_IF attribute. Yeah in the cases we would emit this, every parameter is known to be noncopyable.
Annotated() : element() {} | ||
Annotated(const Annotated &other) : element(other.element) {} | ||
// expected-TEST3-error@-1 {{failed to copy 'Annotated<OwnsT<NonCopyable>>'; did you want to import 'Annotated<OwnsT<NonCopyable>>' as ~Copyable?}} | ||
// expected-TEST3-note@-2 {{maybe one of the types that 'Annotated<OwnsT<NonCopyable>>' depends on needs a 'SWIFT_COPYABLE_IF' annotation}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we keep this note I think it should be rephrased to something like "one of the types that 'Annotated<OwnsT>' depends on may need a 'SWIFT_COPYABLE_IF' annotation". Ideally I'd want to point out the singular type we depend on, in the case where there's only 1 (as opposed to referring to 1 type using "one of the types"), but that may be unnecessarily complex, idk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here we do not know what is the right fix for one of the types that Annotated<OwnsT<NonCopyable>>
depends on. That type might miss a requires
a SWIFT_COPYABLE_IF
or maybe a SWIFT_NONCOPYABLE
. Or maybe the SWIFT_COPYABLE_IF
is missing a parameter. It is hard to tell. Picking only one in this diagnostic might give too much confidence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hnrklssn unfortunately it's very hard to figure out what's the specific type that needs an annotation, so I think we'll have to stick with "one of the types" for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Xazax-hun SWIFT_COPYABLE_IF
and requires
both can be used for conditional copyability, so I assumed that if the user opted for one of these in some parts of the code, probably will always use this option. That's why I filter out the other.
There's also a second note suggesting that SWIFT_NONCOPYABLE
can be used.
Having said that, I'm also fine with always showing all the options in the diagnostic
// expected-TEST1-error@-3 {{failed to copy 'OwnsT<NonCopyable>'; did you want to import 'OwnsT<NonCopyable>' as ~Copyable?}} | ||
// expected-TEST1-note@-4 {{use 'requires' (since C++20) to specify the constraints under which the copy constructor is available}} | ||
// expected-TEST1-note@-5 {{annotate a type with 'SWIFT_COPYABLE_IF(<T>)' in C++ to specify that the type is Copyable if <T> is Copyable}} | ||
// expected-TEST1-note@-6 {{annotate a type with 'SWIFT_NONCOPYABLE' in C++ to import it as ~Copyable}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These notes suggesting solutions should have fix-its attached to them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we cannot easily create high-enough confidence diagnostics to generate fixits because:
- In this case it is obvious where is the problem. But in the more general case we cannot yet what is the right place to insert the annotation. For example if we have
Foo<A<B>, B>
and we failed instantiate the copy ctor ofB
, what is the right solution? IsA
missing a conditional copyable annotation or isB
missing aSWIFT_NONCOPYABLE
? It is possibleA
never copies B, in that case it is the latter. Or in caseOwns
never copiesB
it might be the latter. We could potentially figure out if we inspect the full stack of instantiations that lead to the error but this is a non-trivial and is way beyond the scope of this patch as that information is not readily available here. Also, did we want to annotate that class or one of the base classes? - I think currently we don't have a good way to present multiple alternative fixes. It is not easy to figure out if the right annotation is
SWIFT_NONCOPYABLE
orSWIFT_COPYABLE_IF(<T>)
, or some requires clause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough on the complexity part, but I was under the impression that we can present multiple alternative fixes, as long as they are each attached to a separate note diagnostic.
// expected-TEST1-error@-3 {{failed to copy 'OwnsT<NonCopyable>'; did you want to import 'OwnsT<NonCopyable>' as ~Copyable?}} | ||
// expected-TEST1-note@-4 {{use 'requires' (since C++20) to specify the constraints under which the copy constructor is available}} | ||
// expected-TEST1-note@-5 {{annotate a type with 'SWIFT_COPYABLE_IF(<T>)' in C++ to specify that the type is Copyable if <T> is Copyable}} | ||
// expected-TEST1-note@-6 {{annotate a type with 'SWIFT_NONCOPYABLE' in C++ to import it as ~Copyable}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we cannot easily create high-enough confidence diagnostics to generate fixits because:
- In this case it is obvious where is the problem. But in the more general case we cannot yet what is the right place to insert the annotation. For example if we have
Foo<A<B>, B>
and we failed instantiate the copy ctor ofB
, what is the right solution? IsA
missing a conditional copyable annotation or isB
missing aSWIFT_NONCOPYABLE
? It is possibleA
never copies B, in that case it is the latter. Or in caseOwns
never copiesB
it might be the latter. We could potentially figure out if we inspect the full stack of instantiations that lead to the error but this is a non-trivial and is way beyond the scope of this patch as that information is not readily available here. Also, did we want to annotate that class or one of the base classes? - I think currently we don't have a good way to present multiple alternative fixes. It is not easy to figure out if the right annotation is
SWIFT_NONCOPYABLE
orSWIFT_COPYABLE_IF(<T>)
, or some requires clause.
recordDecl, "'SWIFT_COPYABLE_IF' annotation"); | ||
} else { | ||
ctx.Diags.diagnose(copyConstructorLoc, diag::use_requires_expression); | ||
ctx.Diags.diagnose(copyConstructorLoc, diag::annotate_copyable_if); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SWIFT_COPYABLE_IF
only makes sense to annotate templated types (or types nested in templated types). Do we ever suggest this annotation for non-templated types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we do, but I'll double check.
It's also the case for requires
though, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think so. Same for requires
.
lib/IRGen/GenStruct.cpp
Outdated
}); | ||
|
||
if (copyConstructor->getTrailingRequiresClause()) { | ||
ctx.Diags.diagnose(copyConstructorLoc, diag::maybe_missing_annotation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe if the code is compiled in pre-c++20 standard we should not suggest using requires
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was: might be still worth it to make people aware that requires
exists from C++20, in case they don't mind updating. But probably won't be very useful in most cases.
I don't feel strongly, so don't mind removing this in pre-c++20
Annotated() : element() {} | ||
Annotated(const Annotated &other) : element(other.element) {} | ||
// expected-TEST3-error@-1 {{failed to copy 'Annotated<OwnsT<NonCopyable>>'; did you want to import 'Annotated<OwnsT<NonCopyable>>' as ~Copyable?}} | ||
// expected-TEST3-note@-2 {{maybe one of the types that 'Annotated<OwnsT<NonCopyable>>' depends on needs a 'SWIFT_COPYABLE_IF' annotation}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here we do not know what is the right fix for one of the types that Annotated<OwnsT<NonCopyable>>
depends on. That type might miss a requires
a SWIFT_COPYABLE_IF
or maybe a SWIFT_NONCOPYABLE
. Or maybe the SWIFT_COPYABLE_IF
is missing a parameter. It is hard to tell. Picking only one in this diagnostic might give too much confidence.
ed5147d
to
633230d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This will be a good UX improvement on the status quo.
@swift-ci please smoke test |
In IRGen, we instantiate the definition of a C++ copy constructor for the first time. If this fails, we get an unrecoverable error from Clang, which doesn't allow us to rollback and try to import the type as
~Copyable
.Instead, we emit a Swift error, on top of the C++ error, suggesting that the user can either add a
requires
clause, aSWIFT_COPYABLE_IF
annotation or aSWIFT_NONCOPYABLE
annotation. For the general cause, the Swift diagnostic will look like this:However, if the record already has a
SWIFT_COPYABLE_IF
annotation or if it's copy constructor has arequires
, then we instead suggest that some other type might need more information. Example:Would really appreciate feedback and/or suggestions for these diagnostics!
rdar://161169673