Hi Alejandro,
There would be two different metadata pointers for Dictionary<K, V> (for fixed K and V) if there were two different conformances of K to Hashable. In general, there should be exactly one metadata record for any given list of metadata records and witness tables¹ to be passed to the metadata access function. In this case, it looks like the issue is that the witness table being passed to the function is garbage (and maybe null).
Here, it looks like the problem may be that you aren't passing all the arguments that the metadata accessor for Dictionary expects. Its signature looks something like
define swiftcc %swift.metadata_response @"$sSDMa"(i64 %0, %swift.type* %1, %swift.type* %2, i8** %3)
If the value at reflectStruct([String: Int].self).descriptor.accessor is a pointer to $sSDMa, then calling it like
accessor(.complete, Int.self, Double.self)
doesn't pass the final argument expected by $sSDMa (the witness table for the key to Hashable) resulting in a garbage (maybe null) value being seen as the value for that argument by that function. We need to pass a witness table for Int's conformance to Hashable as well.
I was able to see similar behavior by misdeclaring the signature and calling it as you are
@_silgen_name("$sSDMa")
func metadataAccessorForDictionaryBad(state: UInt64, keyType: Any.Type, valueType: Any.Type) -> Any.Type
func accessBad() {
print(Dictionary<Int, Int>.self)
print(metadataAccessorForDictionaryBad(state: 0, keyType: Int.self, valueType: Int.self))
}
accessBad()
By declaring the signature with the fourth argument that's required, the witness table for the key's conformance to Hashable, and passing along the appropriate witness table
@_silgen_name("$sSDMa")
func metadataAccessorForDictionary(state: UInt64, keyType: Any.Type, valueType: Any.Type, witnessTable: UInt64) -> Any.Type
func accessGood() {
print(Dictionary<Int, Int>.self)
print(metadataAccessorForDictionary(state: 0, keyType: Int.self, valueType: Int.self, witnessTable: witnessTableForIntToHashable))
}
accessGood()
I am able to see the expected behavior (no crash). The constant witnessTableForIntToHashable is defined in a cpp file like this
extern uint64_t $sSiSHsWP;
uint64_t witnessTableForIntToHashable = (uint64_t)&$sSiSHsWP;
As for your Foo<T, U> example, that works because neither generic parameter is constrained to conform to a protocol, so there are no witness tables to pass to the metadata accessor. Once you add a conformance (say of T to Hashable as with Dictionary), you will start to see similar behavior.
¹ Up to equivalence of witness tables. See MetadataCacheKey::compareWitnessTables.
Thanks!
Nate