Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dyno: implement generic initializers (chapel-lang#25026)
Closes Cray/chapel-private#6232. Closes Cray/chapel-private#6109. This PR fixes various issues in the way of initializing generic types. There are two broad classes of features that this addresses. ## Compiler-generated initializers Prior to this, we didn't support compiler-generated initializers for generic types. This PR moves towards this support by making the following changes: * Explicitly introducing a new 'has default' state: 'might have default'. The reason for this is that fields of generic types may or may not have default values, which means that we don't quite know the signature of the initializer until we start instantiating it. But we need that signature when considering initial candidates! This PR relaxes the rule to initial mark formals with unknown types to have a 'might have default' value, allowing missing formals in that case. * Adjusting function signature instantiate to not assume that a compiler-generated function on an aggregate type is its type constructor (since initializers might need instantiations too now). This is largely a mechanical change; the function is changed to skip the first formal when creating substitutions (since the first formal is 'this'), and to populate said formal with the instantiated fields. * Making the decision that substitutions should only be present in a type if the corresponding field requires a formal in the type constructor (re-using the logic for deciding this). The rationale is simple: if `typeConstructor(a1, ..., an)` is sufficient to construct a type, then the actuals it accepts are sufficient to fully instantiate that type. The rest can be figured out given these substitution. Making this assumption helps remain consistent about when to insert substitutions, and in this particular case, to avoid pulling instantiation hints from function resolution into resulting records. * Exposing the "built type with substitutions" helper from return type inference, which is needed to properly set the `this` formal of an instantiated compiler-generated initializer. While there I, 1. Observed that the we don't emit mismatched candidates when trying to resolve initializers. This is unfortunate, since we can't read the error message to figure out why our `new` call is not working. I adjusted the logic to re-run resolution gathering candidates, which is what's currently done for regular calls. 2. In looking at the new errors I got that listed the rejected candidates, I noticed that for dependent functions, the formal type is printed as "Unknown" (since the initial typed signature's formals are used for the printing). This is less-than-ideal, so I specialized the error message in that case to not mention 'UnknownType'. 3. Noticed a bug with type constructors, in which generic `var` fields that directly depend on `type` fields (e.g., `var varField: typeField`) are marked for inclusion in the type constructor. This was caused by the fact that they are technically marked "anytype" as opposed to "unknown type". To fix this, I adjusted the type constructor code treat "var AnyType" as "Unknown". ## User-provided initializers User-provided initializers work fine for records after the previous section (or maybe even before that, I didn't test `main`), but classes caused problems. I tracked this down to issues with formal-actual mismatch in the `this` formal. The failure mode is the following: When resolving `new R(...)`, we resolve `R`, and convert it to a `var`-intent receiver. This helps properly call `init` functions, which accept `ref` or `in` receivers. Next, the can-pass logic proceeds in two steps: first, it compares the types using `==` for a quick return; then, it falls back to a "can convert" check. For a generic __record__, `canPass(R, R)` works, because the initial `==` check succeeds. However, for classes, the formal is an `in borrowed`, but the actual is a `var owned`, so `==` fails, and a "conversion check" is performed. However, the tricky part is that we typically don't allow generic actuals for non-type formals. Thus, `numeric` is a valid actual type for a `type t`, but not for a `var x`. As a result, our imaginary generic receiver is rejected by the more complicated conversion check. The core conundrum was the following: the 'var R' we pass in is not _really_ a value; it's more of a "distinguisher" used to select relevant initializers, since we're creating a new value of that type. We do _not_ want to require it to be concrete, since we might be trying to call a generic initializer. But we do want to continue disallowing generic actuals in general. So then: how do we distinguish initializer actuals that can be generic from the garden-variety non-generic kind? This PR makes that work using a new `INIT_RECEIVER` _kind_. This made sense to me because any other way of propagating this information will make the behavior of `canPass` need to depend on more than just the formal/actual that's being passed in (it would need additional info to know if it's a receiver). Based on the call sites of `canPass`, this information would be relatively difficult to gather and thread through. Thus, this PR: * Adds the new `INIT_RECEIVER` intent, for use only with `new` and `init`. It adjusts the code to handle calls for `new` and `this.init` / `init`. The former requires a simple adjustment, since we already override the intent to `VAR` on `main`. Now, we just override it to `INIT_RECEIVER`. The latter required some fiddling with `CallInfo` to detect calls to `init` and mark their receivers with `INIT_RECEIVER` appropriately. * Note that `INIT_RECEIVER` is an internal intent, and is not user facing. It only exists while resolving calls, and disappears after a call to an initializer has been resolved. * Fixes a bug with `call-init-deinit` that I discovered while testing, in which we call `=` for nested calls to `this.init`. This isn't necessary, since we're just invoking another initializer in-place. Reviewed by @riftEmber -- thanks! ## Testing - [x] dynotests - [x] paratest
- Loading branch information