Hi all, I wanted to spawn a thread about some of the issues I'm running into while implementing parameterized extensions to hopefully get some feedback from some compiler developers, but to also give the community insight into where I am in the current implementation. I've recently picked this back up as Tuples are EHC are coming to a close, and am running into some of the same issues I was running into a while back.
One of the main issues I'm running into is that an extension's generic parameters don't show up in the type definition. This is an issue when asking to get a type's context substitution map because we walk the type and find any bound generic and apply it to the respectful generic parameter found in the generic signature. However, we can't do this for a generic parameter who doesn't show up in the type at all. My naïve solution was to manually attempt to look at all generic requirements and try to fish out the substituted type from these requirements. For example:
struct Generic<A> {}
// extension<B> Generic where A == B? {}
extension<B> Generic<B?> {
struct Nested {
let value: B
}
}
// let x = Generic<Int?>.Nested(value: 128)
let x = Generic.Nested(value: 128)
print(x)
Here we have a nested type Nested
whose generic signature looks something like this:
<A, B where A == B?>
and when we come across a type like Generic<Int?>.Nested
we have to calculate the substitution map here. Currently, we generate something like this:
A => Int?
B => B
This is incorrect though because B
must be substituted to the correct type Int
, but because this Int
never appears in Generic<Int?>.Nested
, we don't assign it to B
. My solution looks through this generic signature, <A, B where A == B?>
and uses a type walker over the same-type requirements looking for B
as the second type. If I managed to find B
, I use the same type walker, who recorded the list of actions took to find B
, on the substituted type we generated for A
, which was Int?
and we stop at Int
and assign this to B
. This seems to work for many of the simple examples, however a similar situation happens at runtime.
For mangling, I've added a new node named GenericExtension
who follows the same formula as extensions, however in their generic signature it tells us exactly how many generic parameter the extension introduced allowing me to easily know how many and what kind of generic parameters to create during type decoding. However, the generic parameter introduced by the extension is never bound to any type in mangling because once again, the parameter doesn't appear in the type signature. This is an issue when the runtime is attempting to instantiate metadata from a demangle node because it has the bound generic type Generic<Int?>
in the example above, but the generic parameter count and what we've demangled aren't equal. I.e. in the Nested
's generic context it states it has 2 generic parameters with the generic parameter introduced by the extension being a key argument, but in the mangling we only supply it with the bound generic type Generic<Int?>
meaning we only have 1 generic argument.
I could do something similar where I walk the generic signature of the demangled extension node, and try to fish out a substituted type for it's generic parameters, but maybe there are other solutions? Perhaps mangle the generic argument for the extension in the mangling, something like a bound generic extension?
Sorry if this should really be communicated through the PR, or if this was hard to follow! This is sort of where I am right now with parameterized extensions. I have a lot of the simple use cases working (like functions and computed properties within these extensions), but it's making sure that everything works is my priority. Maybe there just isn't something I considered yet that is causing some of these issues, or maybe I'm thinking about this incorrectly. Any help would be appreciated!
(For reference, my recent work is here: Parameterized extensions by Azoy · Pull Request #25263 · apple/swift · GitHub)
cc: @Slava_Pestov @Joe_Groff @Douglas_Gregor (sorry for the ping!)