-
Notifications
You must be signed in to change notification settings - Fork 424
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
Should copy initializers be generated by default, even when other explicit initializers are present? #8065
Comments
@mppf - how does this look? |
I have been assuming that this would prove
to be desirable at some point and I support
this direction.
|
It seems inconsistent to me right now that we generate the assignment operator by default, even if an initializer is provided, but we don't generate a copy initializer by default. I think it'd be reasonable to generate both assignment and the copy initializer by default; if the user doesn't want the default one they have to write a copy initializer. Since the compiler/modules generally expects that It's certainly the case for One day, I hope that simply providing a copy-initializer that calls compilerError would be sufficient for a record that doesn't want to be copyable. That's not easily achievable today with the autoCopyMap though - and I'd rather have a good default than have good support for the weird case of no copy initializer for a record. (In the meantime, we have a flag/pragma that Owned uses). (Edit -- actually supporting that pattern might not be that hard, it'd just be more weird cases in resolution to do it the obvious way). |
I'd expect a default copy initializer to be created for records, but to be shadowed by a user-specified initializer that "fit" the expected signature as described in my comments on issue #8064. |
Closing. The current decision is "yes, copy initializers should be generated by default" |
Based on experience converting module code over, I'm wondering if we made the wrong decision here. As initializers currently stand, I'm only somewhat convinced we did. If we were to split the phase 1 and phase 2 portions of initializers apart, as is being explored in issue #8018 then I feel more confident that reversing this decision might be correct. In more detail: In converting a complex initializer case over (e.g., one that had deep/shallow copy and ownership issues), I converted the existing constructors and added a few new overloads that were required in the new world, but did not add a copy initializer. The compiler provided one, but because of the parenthetical issues above, it was inherently broken. Because of the decision made on this issue, however, the result was that the program blew up which was difficult to diagnose. If instead, the presence of the custom initializers had disabled the automatic copy initializer, it would've forced me to think about what I should do in that case and made it less likely that I would've stepped into the hole. If there were a mechanism for opting into having the compiler provide such an initializer for use (i.e., "fulfill my need for a copy initializer by having the compiler provide it") then the effort would've been low for other cases. As I write this, something I'm wondering about is what the cues might be for "the compiler's default initializer is unlikely to cut it." In our current "initializers contain phase 1 and 2 code" world, a challenge with reversing this decision is that initializers may contain all sorts of code that is unrelated to questions of ownership, deep/shallow copies, etc. E.g., maybe they have a check and halt which required a user initializer, but didn't require a custom copy initializer. So something I'm wondering about is whether the cue for bringing the user initializer into the mix might be something different like "presence of an assignment overload" or "presence of a custom phase 1 initializer, but not a custom phase 2 initializer" or ... I'm still thinking about this question. |
What if we only prevented the generation of a default copy initializer in the case where an explicit assignment function was provided for the type? |
Yeah, that's one of the options I was considering (see "So something I'm wondering about..." above). OTOH, I could imagine the situation reversed: maybe the compiler shouldn't create a default assignment for the case when there was a copy initializer (or shouldn't create either in the event of ...something else...?) |
I think it'd be reasonable to require record authors to accept the compiler's copy initializer and assignment function or implement them both. I.e. it would be an error to provide assignment and no copy initializer or to provide copy initializer and not assignment. A somewhat reasonable alternative would be for the default compiler-generated one to call the one the user provided, but that can create some weird cases... (e.g. we could implement channel assignment out of init copy or implement channel init copy out of assignment... but I worry this won't generalize well to arbitrary records with fields with their own copy/assignment). |
It's best to start from less permissive and move to more permissive, anyways, so requiring both to provided if one is a good starting point if we don't feel we have enough information yet. |
I'm reopening this based on recent experiences and discussion. |
I'm running into problems related to this issue as part of working on https://github.com/Cray/chapel-private/issues/667 / #13600. I need a way for a type like |
Adjust tryResolve to return false when compilerError encountered Resolves #13195. This PR updates resolution handling of tryResolve to hide compilerErrors encountered along the way and instead just make tryResolve return false in that case. Additionally, it adjusts tryResolve to include a boolean argument indicating if the body of the called function should also be resolved (so that if it contains a compilerError call we can handle that appropriately). The PR also makes some adjustments to misc.cpp towards making it possible to capture and re-issue all errors but that is not currently used. However I am including it here because I think it is an improvement to the structure of error reporting. This PR takes no particular action for resolving functions called by the function being attempted. These are resolved in whatever order resolution gets to them normally. I started on this patch because in working on #13600 I needed a way to mark an `init=` or `=` function as not resolveable (see #8065) so that isCopyable etc (from PR #14842) can return false. Future work: Answer the open design questions in this area 1. Should canResolve always try to resolve the body of the called function? 2. Should canResolve always try to resolve the body of the called function and all functions called within it, recursively? 3. Should canResolve return false for other errors besides compilerError? - [x] full local testing Reviewed by @lydia-duncan - thanks!
I would expect that if there is any user-provided Additionally we need a way to have an
There is a similar question for the assignment operator. For the assignment operator, I'm currently thinking that the compiler would generate one only if there isn't a user-provided Users will need a way to indicate that the type does not support assignment. That could be:
Note that the new syntax options like |
If we wanted new syntax that conceptually solved both #8136 and #8566, we could add:
(I think one of the main complexities with requesting the default init/init= is that we wouldn't want to put an argument list since repeating the argument list would be much of the work in manually writing the default init). (Also the Of course that's not the only approach to solving this issue.... but such a syntax seems fairly unambiguous/clear to me while allowing flexibility for the different things we want to do. |
I'm proposing: (A) Make it an error to specify one of (B) Adjust the compiler to avoid generating (C) Provide a way for users to indicate that a type has no functional
C1 compilerErrorPros:
Cons:
C2 where falsePros:
Cons:
C3 new syntaxPros:
Cons:
C4 attributesPros:
Cons:
|
There is some agreement on A and B. For C, we will have C1 implemented now and hope to enable also C2 in this release. |
Add check for both init= = or none Related to issue #8065. Previously, the compiler generated `init=` and `=` for all types, but did so separately. That means that one could use the compiler-generated `=` but a custom `init=` or vice versa. This could in some cases lead to confusing and hard to debug problems if a custom `init=` or `=` was forgotten or included the wrong type signature. To resolve these problems, this PR started by adjusting the compiler to check that if a type has custom `init=`, it does not use the default `=`; and that if a type has a custom `=`, it does not use the default `init=`. The compiler currently only considers the result type (`this` for `init=` and the LHS for `=`) when doing this check. As a result, a type with an `init=` or `=` function with a different type RHS causes the error unless both `=` and `init=` are added for the same type. This does not seem to be an undue burden but could have an impact on determining which types are POD. I recommend that in the future types can opt in to being considered POD (in which case `=` and `init=` will be resolved, and compilerErrors in them issued, but can be removed during optimization). The generalized attribute syntax #14141 would be a reasonable way to do that. Even before this PR, the `range` type needed to opt in to being POD because its `init=` included various checks. Next, this PR adjusted the compiler to also check for a mix of uses of compiler-generated `init=` and custom `init=`; or compiler-generated `=` and custom `=`. This only required adjusting 2 additional tests beyond the tests adjusted for the above rule. In all, this PR required adjusting ~30 tests / modules and almost all of these were tests of record initializers/assignment. Reviewed by @lydia-duncan - thanks! Thanks to many others for discussion about the language design direction. - [x] full local futures testing
There are a lot of situations where a copy initializer is required, but today the presence of an explicit initializer prevents it from being generated.
Example 1:
Example 2:
Should we always generate a copy initializer unless we recognize that an explicit one has been defined?
Should this be connected to whether
=
or a non-copy initializer was provided?The text was updated successfully, but these errors were encountered: