Biohazrd icon indicating copy to clipboard operation
Biohazrd copied to clipboard

Allow disambiguation of template specializations involving typedefs

Open PathogenDavid opened this issue 3 years ago • 2 comments

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 TemplateSpecializationTypes: MyTemplate<MyInt> and MyTemplate<int> respectively.

However, resolving the declaration associated with these two TemplateSpecializationTypes 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.)

PathogenDavid avatar Mar 28 '21 06:03 PathogenDavid

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)

PathogenDavid avatar Mar 28 '21 06:03 PathogenDavid

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>

PathogenDavid avatar Jul 01 '22 20:07 PathogenDavid