Biohazrd
Biohazrd copied to clipboard
Allow disambiguation of template specializations involving typedefs
Internally, Clang combines common declarations for template specializations when their canonical types match. For example:
template<typename T> class MyTemplate
{
public:
T Field;
};
typedef int MyInt;
typedef MyTemplate<MyInt> MySpecialization1;
typedef MyTemplate<int> MySpecialization2;
MySpecialization1
and MySpecialization2
have different TemplateSpecializationType
s: MyTemplate<MyInt>
and MyTemplate<int>
respectively.
However, resolving the declaration associated with these two TemplateSpecializationType
s both point to the same TemplateSpecializationDecl
: MyTemplate<int>
This makes sense since in the context of C++ they are effectively the same. However, this doesn't mesh nicely with Biohazrd's object model, which keeps typedefs separate. This is done to allow replacing plain C++ types with more specific types. This is most commonly used to replace an int typedef with an appropriate strongly-typed enum. (For example: Dear ImGui uses typedefs to explicitly size enums for compatibility with older versions of C++ which did not allow explicit enum sizes..)
I suspect that solving this issue is non-trivial. I'm not even entirely sure how we'd want to do it since in theory MySpecialization1
and MySpecialization2
should become the same MyTemplate<int>
at emit time if MyInt
is not replaced with a non-TranslatedTypedef
. The actual types are distinct, so it's a matter of emitting a TranslatedTemplateSpecialization
which uses MyInt
instead of int
. In theory both types have the same underlying Clang Decl
, so we'd need some sort of special handling here.
As a workaround, generator authors can special-case replace the MySpecialization1
with a version of MyTemplate<int>
which uses whatever they replaced MyInt
with. (This obviously doesn't work well if there isn't a typedef for the specialization.)
Note that this issue only affects typedefs (and probably aliases). An int-sized enum will not flatten with int
.
Here is a quick and dirty test that demonstrates this issue:
[Fact]
public unsafe void __TEMP()
{
TranslatedLibrary library = CreateLibrary
(@"
template<typename T> class MyTemplate
{
public:
T Field;
};
enum MyEnum { A, B, C };
typedef int MyInt;
typedef MyTemplate<MyInt> MySpecialization1;
typedef MyTemplate<int> MySpecialization2;
");
TranslatedTypedef specializationTypedef1 = library.FindDeclaration<TranslatedTypedef>("MySpecialization1");
TranslatedTypedef specializationTypedef2 = library.FindDeclaration<TranslatedTypedef>("MySpecialization2");
TemplateSpecializationType type1 = (TemplateSpecializationType)((ClangTypeReference)specializationTypedef1.UnderlyingType).ClangType;
TemplateSpecializationType type2 = (TemplateSpecializationType)((ClangTypeReference)specializationTypedef2.UnderlyingType).ClangType;
Console.WriteLine(type1.TypeClass);
Console.WriteLine(type2.TypeClass);
Console.WriteLine(type1);
Console.WriteLine(type2);
Console.WriteLine((IntPtr)type1.Handle.data[0]);
Console.WriteLine((IntPtr)type2.Handle.data[0]);
Console.WriteLine(type1 == type2);
Decl decl1 = (Decl)library.FindClangCursor(type1.Handle.Declaration);
Decl decl2 = (Decl)library.FindClangCursor(type2.Handle.Declaration);
Console.WriteLine(decl1 == decl2);
}
(That final Decl
comparison returns true.)
Here's the proper TypeReductionTransformation
test I wrote for this test case before I realized it was intrinsic to how Clang processes templates:
[FutureFact]
[RelatedIssue("https://github.com/InfectedLibraries/Biohazrd/issues/178")]
public void TemplateSubstitutionIsResolvedDirectly()
{
TranslatedLibrary library = CreateLibrary
(@"
template<typename T> class MyTemplate
{
public:
T Field;
};
typedef int MyInt;
typedef MyTemplate<MyInt> MySpecialization;
");
library = new TypeReductionTransformation().Transform(library);
TranslatedTypedef myInt = library.FindDeclaration<TranslatedTypedef>("MyInt");
TranslatedTypedef specializationTypedef = library.FindDeclaration<TranslatedTypedef>("MySpecialization");
TranslatedTypeReference specializationReference = Assert.IsAssignableFrom<TranslatedTypeReference>(specializationTypedef.UnderlyingType);
TranslatedTemplateSpecialization specialization = Assert.IsType<TranslatedTemplateSpecialization>(specializationReference.TryResolve(library));
TranslatedNormalField field = specialization.FindDeclaration<TranslatedNormalField>("Field");
// Ensure the field was resolved to `MyInt` (as opposed to `int` directly.)
TranslatedTypeReference fieldTypeReference = Assert.IsAssignableFrom<TranslatedTypeReference>(field.Type);
TranslatedDeclaration fieldTypeDeclaration = Assert.NotNull(fieldTypeReference.TryResolve(library));
Assert.ReferenceEqual(myInt, fieldTypeDeclaration);
}
(A proper proper test should probably involve MyTemplate<int>
too)
While doing some investigations for https://github.com/MochiLibraries/Biohazrd/issues/247, I found this information is retained in the TemplateSpecializationArgsAsWritten
(for function template instantiations at least, I assume something similar exists for record template specializations.)
Here's a dump of some info for the MyShort
specialization below:
template<typename T> T Add(T a, T b);
template<> int Add<int>(int a, int b);
typedef short MyShort;
template<> MyShort Add<MyShort>(MyShort a, MyShort b);
Dumping function info for Function template<> MyShort Add<MyShort>(MyShort a, MyShort b) @ A.h:6:20
isCanonicalDecl: No
CanonicalDecl: Function template<> short Add<short>(short a, short b) @ A.h:6:20
isLateTemplateParsed: No
isTemplateInstantiation: No
isFunctionTemplateSpecialization: Yes
isImplicitlyInstantiable: No
isOutOfLine: No
TemplatedKind: FunctionTemplateSpecialization
TemplateSpecializationKind: ExplicitSpecialization
TemplateSpecializationKindForInstantiation: ExplicitSpecialization
PrimaryTemplate: FunctionTemplate template <typename T> T Add(T a, T b) @ A.h:2:24
DescribedFunctionTemplate: <nullptr>
TemplateInstantiationPattern: Function T Add(T a, T b) @ A.h:2:24
TemplateInstantiationPattern (ForDefinition): <nullptr>
PointOfInstantiation: <invalid loc>
MemberSpecializationInfo: <nullptr>
TemplateSpecializationInfo:
Function: Function template<> MyShort Add<MyShort>(MyShort a, MyShort b) @ A.h:6:20
Template: FunctionTemplate template <typename T> T Add(T a, T b) @ A.h:2:24
TemplateSpecializationKind: ExplicitSpecialization
isExplicitSpecialization: Yes
isExplicitInstantiationOrSpecialization: Yes
PointOfInstantiation: <invalid loc>
MemberSpecializationInfo: <nullptr>
TemplateArguments:
[0] short
TemplateArgumentsAsWritten:
[0] MyShort
TemplateSpecializationArgs:
[0] short
TemplateSpecializationArgsAsWritten:
[0] MyShort
DependentSpecializationInfo: <nullptr>