in resharper/resharper-unity/src/Unity/CSharp/Feature/Services/SerializeReference/SerializeReferenceTypesUtils.cs [34:612]
internal record ClassInfoAdapter(ElementId? ElementId,
CountingSet<ElementId> SuperClasses,
string FullyQualifiedName,
Dictionary<ElementId, TypeParameter> TypeParametersDictionary,
FieldAdapter[] SerializedFields,
List<TypeParameterResolve> TypeParameterResolves);
internal static class SerializeReferenceTypesUtils
{
private static readonly ILogger ourLogger = Logger.GetLogger<UnitySerializedReferenceProvider>();
private static CountingSet<ElementId> CreateCountingSet(IEnumerable<KeyValuePair<ElementId, int>> elementIds)
{
var result = new CountingSet<ElementId>();
foreach (var (key, value) in elementIds)
{
if (value <= 0)
throw new ArgumentException("Wrong value");
result.Add(key, value);
}
return result;
}
private static IDeclaredType? GetUnityTypeOwnerType(ITypeOwner typeOwner)
{
Assertion.Require(typeOwner is IProperty or IField);
var typeOwnerType = typeOwner.Type;
if (typeOwnerType is IArrayType { Rank: 1 } arrayType) //array
return arrayType.ElementType as IDeclaredType;
if (typeOwnerType is not IDeclaredType declaredType)
return null;
var typeElement = declaredType.GetTypeElement();
if (typeElement == null)
return null;
var typeParameters = typeElement.TypeParameters;
if (typeParameters.Count == 0)
return declaredType;
var substitution = declaredType.GetSubstitution();
if (declaredType.IsGenericList()) //List<>
return substitution[typeParameters[0]] as IDeclaredType;
return null;
}
private static Dictionary<ElementId, TypeParameter> GetTypeParametersDict(
ITypeElement classLikeDeclaration,
IUnityElementIdProvider unityElementIdProvider)
{
var result = new Dictionary<ElementId, TypeParameter>();
foreach (var typeParameterOfTypeDeclaration in classLikeDeclaration.GetAllTypeParameters())
{
if (typeParameterOfTypeDeclaration == null)
continue;
var elementId =
unityElementIdProvider.GetElementId(typeParameterOfTypeDeclaration, classLikeDeclaration);
if (elementId == null)
continue;
var declaredElementShortName = GetDeclaredElementNameDescription(classLikeDeclaration, typeParameterOfTypeDeclaration);
result.Add(elementId.Value,
new TypeParameter(elementId.Value, declaredElementShortName, typeParameterOfTypeDeclaration.Index,
new CountingSet<ElementId>()));
}
return result;
}
private static Dictionary<ElementId, TypeParameter> GetTypeParametersDict(IMetadataTypeInfo metadataTypeInfo,
IPsiAssemblyFile assemblyFile, IUnityElementIdProvider unityElementIdProvider)
{
var metadataTypeParameters = metadataTypeInfo.TypeParameters;
var result = new Dictionary<ElementId, TypeParameter>();
foreach (var typeParameter in metadataTypeParameters)
{
var elementId = unityElementIdProvider.GetElementId(typeParameter, assemblyFile);
if (elementId == null)
continue;
var parameterName = GetParameterNameDescription(typeParameter);
var parameter = new TypeParameter(
elementId.Value,
parameterName,
(int)typeParameter.Index,
new CountingSet<ElementId>()
);
result.Add(elementId.Value, parameter);
}
return result;
}
internal static void CollectClassData(ClassInfoAdapter classInfoAdapter,
ClassMetaInfoDictionary resultInfoTypeToInterfaces,
CountingSet<TypeParameterResolve> resultTypeParameterResolves)
{
var classId = ProcessClassInfo(classInfoAdapter, resultInfoTypeToInterfaces);
if (classId == null)
return;
foreach (var typeParameterResolve in classInfoAdapter.TypeParameterResolves)
resultTypeParameterResolves.Add(typeParameterResolve);
ProcessFields(classInfoAdapter, resultInfoTypeToInterfaces);
}
private static ElementId? ProcessClassInfo(ClassInfoAdapter classAdapter,
ClassMetaInfoDictionary classMetaInfoDictionary)
{
var elementId = classAdapter.ElementId;
if (elementId == null)
return elementId;
var classId = elementId.Value;
var typeParameters = classAdapter.TypeParametersDictionary;
var classMetaInfo = new ClassMetaInfo(
classAdapter.FullyQualifiedName,
classAdapter.SuperClasses,
new CountingSet<ElementId>(),
typeParameters
);
if (classMetaInfoDictionary.TryGetValue(classId, out var existedValue))
{
ourLogger.Verbose(
$"ClassId {classId} already exists. Existed class:{existedValue}, new info: {classMetaInfo}");
existedValue.UnionWith(classMetaInfo);
}
else
{
ourLogger.Trace(
$"Adding id:{classId}, name:{classMetaInfo.ClassName}, {classAdapter.FullyQualifiedName}");
classMetaInfoDictionary.Add(classId, classMetaInfo);
}
return classId;
}
private static void ProcessFields(ClassInfoAdapter classInfoAdapter,
ClassMetaInfoDictionary resultInfoTypeToInterfaces)
{
foreach (var fieldAdapter in classInfoAdapter.SerializedFields)
CollectFieldData(fieldAdapter,
classInfoAdapter.ElementId!.Value,
resultInfoTypeToInterfaces);
}
private static void CollectFieldData(FieldAdapter field, ElementId classId
, ClassMetaInfoDictionary resultInfoTypeToInterfaces)
{
if (!field.IsValid) return;
if (!field.IsUnityFieldType) return;
//object fields won't be serialized in Unity
if (field.IsObjectTypeField) return;
var fieldTypeId = field.CalculateElementId;
if (fieldTypeId == null)
return;
if (field.IsTypeParameterType)
{
if (resultInfoTypeToInterfaces.TryGetValue(classId, out var containingTypeMetaInfo))
{
if (containingTypeMetaInfo.TypeParameters.TryGetValue(fieldTypeId.Value, out var typeParameter))
typeParameter.SerializeReferenceHolders.Add(classId);
else //- class is already processed with all type parameters
Assertion.Fail(
$"TypeParameter should already exists, {nameof(field.FieldName)}:{field.FieldName}");
}
else
{
Assertion.Fail($"ClassId '{classId.Value}' should already exists in index");
}
}
else if (resultInfoTypeToInterfaces.TryGetValue(fieldTypeId.Value, out var originalClassMetaInfo))
{
originalClassMetaInfo.SerializeReferenceHolders.Add(classId);
}
else
{
var fieldClassMetaInfo = new ClassMetaInfo(field.GetTypeFullName);
fieldClassMetaInfo.SerializeReferenceHolders.Add(classId);
resultInfoTypeToInterfaces.Add(fieldTypeId.Value, fieldClassMetaInfo);
}
}
private static FieldAdapter ToAdapter(this IMetadataField? metadataField, IPsiAssemblyFile assemblyFile,
IUnityElementIdProvider unityElementIdProvider)
{
//Property with backing field will be represented as IMetadataField
var isValid = metadataField is { IsStatic: false, IsLiteral: false, IsInitOnly: false, NotSerialized: false };
if (!isValid)
return FieldAdapter.InValidFieldAdapter;
var metadataFieldType = GetUnityFieldType(metadataField);
var isUnityFieldType = metadataFieldType != null;
var isObjectTypeField = metadataFieldType != null &&
Equals(metadataFieldType.FullName, PredefinedType.OBJECT_FQN.FullName);
var elementId = metadataFieldType != null
? unityElementIdProvider.GetElementId(metadataFieldType, assemblyFile)
: null;
var typeFullName = GetTypeFullNameDescription(metadataFieldType);
var fieldName = GetMetadataFieldNameDescription(metadataField);
var isTypeParameterType = metadataFieldType is IMetadataTypeParameterReferenceType;
return new FieldAdapter(isValid, isUnityFieldType, isObjectTypeField, elementId, typeFullName,
isTypeParameterType, fieldName);
}
private static IMetadataType? GetUnityFieldType(IMetadataField? metadataField)
{
if (metadataField == null)
return null;
var fieldType = metadataField.Type;
switch (fieldType)
{
case IMetadataTypeParameterReferenceType:
return fieldType;
case IMetadataArrayType { Rank: 1 } metadataArrayType:
return metadataArrayType.ElementType;
case IMetadataClassType metadataClassType:
{
var typeParameters = metadataClassType.Arguments;
if (typeParameters.Length == 0)
return metadataClassType;
if (metadataClassType.Type.IsList()) //List<>
return typeParameters[0];
break;
}
}
return null;
}
public static bool HasFieldAttribute(this IProperty property, IClrTypeName clrTypeName)
{
Assertion.Require(property != null, "property != null");
var attributes = property.GetDeclarations<IPropertyDeclaration>()
.SelectMany(declaration => declaration.AttributesEnumerable);
foreach (var attribute in attributes)
{
if (attribute is not { Target: AttributeTarget.Field })
continue;
var result = attribute.Name.Reference.Resolve();
if (result.ResolveErrorType != ResolveErrorType.OK || result.DeclaredElement is not ITypeElement typeElement)
continue;
if (Equals(typeElement.GetClrName(), clrTypeName))
return true;
}
return false;
}
private static FieldAdapter ToAdapter(this ITypeOwner? typeOwner, ITypeElement ownerTypeElement,
IUnityElementIdProvider provider)
{
Assertion.Require(typeOwner is IField or IProperty);
var isValid = typeOwner is IProperty { IsStatic: false, IsReadonly: false, IsAuto: true, IsWritable: true } property
&& !property.HasFieldAttribute(PredefinedType.NONSERIALIZED_ATTRIBUTE_CLASS)
||
typeOwner is IField { IsStatic: false, IsConstant: false, IsReadonly: false } field
&& !field.HasAttributeInstance(PredefinedType.NONSERIALIZED_ATTRIBUTE_CLASS, false);
if (!isValid)
return FieldAdapter.InValidFieldAdapter;
var declaredType = GetUnityTypeOwnerType(typeOwner);
var isUnityFieldType = declaredType != null;
var isObjectTypeField =
declaredType != null && Equals(declaredType.GetClrName(), PredefinedType.OBJECT_FQN);
var elementId = declaredType != null
? provider.GetElementId(declaredType.GetTypeElement(), ownerTypeElement)
: null;
var typeFullName = GetTypeFullNameDescription(declaredType);
var fieldName = GetFieldNameDescription(typeOwner);
var isTypeParameterType = declaredType?.IsOpenType ?? false;
return new FieldAdapter(isValid, isUnityFieldType, isObjectTypeField, elementId, typeFullName,
isTypeParameterType, fieldName);
}
// for some reason built-in IsValueType returns false
private static bool IsValueType(IMetadataTypeInfo metadataTypeInfo)
{
return metadataTypeInfo.FullyQualifiedName.Equals("System.ValueType");
}
internal static ClassInfoAdapter ToAdapter(this IMetadataTypeInfo classType, IPsiAssemblyFile assemblyFile,
IUnityElementIdProvider provider)
{
var elementId = provider.GetElementId(classType, assemblyFile);
var metadataSuperClassTypes =
classType.InterfaceImplementations.Select(i => i.Interface)
.Concat(new[] { classType.Base }).Where(t => t != null).ToList();
var superClasses =
metadataSuperClassTypes
.Where(t => t != null && !t.IsObject() && !IsValueType(t.Type) && !t.Type.IsArray() && !t.Type.IsList())
.Select(t => provider.GetElementId(t!.Type, assemblyFile))
.Select(id => new KeyValuePair<ElementId, int>(id!.Value, 1));
var fullyQualifiedName = GetClassTypeFullyQualifiedNameDescription(classType);
var typeParametersDictionary =
GetTypeParametersDict(classType, assemblyFile, provider);
var serializedFields = classType.GetFields()
.Where(f => f.HasCustomAttribute(KnownTypes.SerializeReference.FullName))
.Select(f => f.ToAdapter(assemblyFile, provider)).ToArray();
var typeResolves = GetTypeParameterResolves(assemblyFile, provider, metadataSuperClassTypes);
return new ClassInfoAdapter(elementId, CreateCountingSet(superClasses), fullyQualifiedName,
typeParametersDictionary, serializedFields, typeResolves);
}
private static List<TypeParameterResolve> GetTypeParameterResolves(IPsiAssemblyFile assemblyFile,
IUnityElementIdProvider provider,
IEnumerable<IMetadataClassType> metadataSuperClassTypes)
{
var typeResolves = new List<TypeParameterResolve>();
foreach (var superClassType in metadataSuperClassTypes)
{
if (superClassType == null || Equals(superClassType.FullName, PredefinedType.OBJECT_FQN.FullName))
continue;
for (var index = 0; index < superClassType.Arguments.Length; index++)
{
var metadataType = superClassType.Arguments[index];
var parameterId =
provider.GetElementId(superClassType.Type.TypeParameters[index], assemblyFile);
if (parameterId == null)
continue;
ElementId? resolvedId = null;
var typeParameterName = string.Empty;
switch (metadataType)
{
case IMetadataTypeParameterReferenceType parameterReferenceType:
resolvedId = provider.GetElementId(parameterReferenceType.TypeParameter, assemblyFile);
typeParameterName = GetTypeParameterNameDescription(parameterReferenceType);
break;
case IMetadataClassType parameterClassType:
resolvedId = provider.GetElementId(parameterClassType.Type, assemblyFile);
typeParameterName = GetTypeParameterNameDescription(parameterClassType);
break;
}
if (resolvedId == null)
continue;
typeResolves.Add(new TypeParameterResolve(
GetResolutionDescription(superClassType, index, metadataType, typeParameterName),
parameterId.Value,
resolvedId.Value
));
}
}
return typeResolves;
}
internal static ClassInfoAdapter ToAdapter(this ITypeElement typeElement,
IUnityElementIdProvider provider)
{
Assertion.Require(typeElement != null, "typeElement != null");
Assertion.Require(provider != null, "provider != null");
var elementId = provider.GetElementId(typeElement);
var superTypes = typeElement.GetSuperTypes().Where(t => !t.IsObject() && !t.IsSystemValueType()).ToList();
var superClassesEnumerable = superTypes
.Select(i => i.GetTypeElement())
.Where(i => i != null
&& !i.IsObjectClass()
&& i.Type() is not IArrayType { Rank: 1 }
&& !i.Type().IsGenericList())
.Select(i => provider.GetElementId(i))
.Where(id => id != null)
.Select(id => new KeyValuePair<ElementId, int>(id!.Value, 1));
var fullyQualifiedName = GetFullyQualifiedNameDescription(typeElement);
var typeParametersDictionary = GetTypeParametersDict(typeElement, provider);
var serializedRefFields = typeElement.Fields
.Where(field => field != null && field.HasAttributeInstance(KnownTypes.SerializeReference, false))
.Select(f => f.ToAdapter(typeElement, provider));
var serializedRefProperties = typeElement.Properties
.Where(property => property != null)
.Where(property => property.HasFieldAttribute(KnownTypes.SerializeReference))
.Select(f => f.ToAdapter(typeElement, provider));
var typeResolves = GetTypeParameterResolves(provider, superTypes);
return new ClassInfoAdapter(elementId, CreateCountingSet(superClassesEnumerable), fullyQualifiedName,
typeParametersDictionary, serializedRefFields.Concat(serializedRefProperties).ToArray(), typeResolves);
}
private static List<TypeParameterResolve> GetTypeParameterResolves(IUnityElementIdProvider provider,
List<IDeclaredType> superTypes)
{
var typeResolves = new List<TypeParameterResolve>();
foreach (var superClass in superTypes)
{
var resolveResult = superClass.Resolve();
var superClassTypeElement = superClass.GetTypeElement();
if (superClassTypeElement == null)
continue;
var resolveResultSubstitution = resolveResult.Substitution;
var typeParameters = resolveResultSubstitution.Domain;
for (var index = 0; index < typeParameters.Count; index++)
{
var typeParameter = typeParameters[index];
var type = resolveResultSubstitution[typeParameter];
var typeParamElementId = provider.GetElementId(typeParameter, superClassTypeElement, index);
if (typeParamElementId == null)
continue;
var declaredElement = type.GetTypeElement();
if (declaredElement == null)
continue;
var resolvedTypeElementId = provider.GetElementId(declaredElement);
if (resolvedTypeElementId == null)
continue;
typeResolves.Add(new TypeParameterResolve(
GetResolutionDescription(typeParameter, declaredElement),
typeParamElementId.Value, resolvedTypeElementId.Value
));
}
}
return typeResolves;
}
#region Additiona debug info
private static bool IsDetailedInfoEnabled()
{
return ourLogger.IsEnabled(LoggingLevel.TRACE);
}
private static string GetResolutionDescription(ITypeParameter typeParameter, ITypeElement declaredElement)
{
if (IsDetailedInfoEnabled())
return $"{typeParameter.OwnerType?.ShortName}:[{typeParameter.Index}]{typeParameter.ShortName}->{declaredElement.ShortName}";
return string.Empty;
}
private static string GetFullyQualifiedNameDescription(ITypeElement typeElement)
{
if (IsDetailedInfoEnabled())
return typeElement.GetClrName().FullName;
return string.Empty;
}
private static string GetResolutionDescription(IMetadataClassType superClassType, int index,
IMetadataType metadataType, string typeParameterName)
{
if (IsDetailedInfoEnabled())
return $"{superClassType.FullName}:[{index}]{metadataType.FullName}->{typeParameterName}";
return string.Empty;
}
private static string GetTypeParameterNameDescription(IMetadataClassType parameterClassType)
{
if (IsDetailedInfoEnabled())
return parameterClassType.FullName;
return string.Empty;
}
private static string GetTypeParameterNameDescription(
IMetadataTypeParameterReferenceType parameterReferenceType)
{
if (IsDetailedInfoEnabled())
return parameterReferenceType.TypeParameter.Name;
return string.Empty;
}
private static string GetDeclaredElementNameDescription(ITypeElement classLikeDeclaration,
ITypeParameter typeParameterOfTypeDeclaration)
{
if (IsDetailedInfoEnabled())
return $"{classLikeDeclaration.GetClrName()}<{typeParameterOfTypeDeclaration.ShortName}[{typeParameterOfTypeDeclaration.Index}]>";
return string.Empty;
}
private static string GetParameterNameDescription(IMetadataTypeParameter typeParameter)
{
if (IsDetailedInfoEnabled())
return $"{typeParameter.TypeOwner.FullyQualifiedName}<{typeParameter.Name}[{typeParameter.Index}]>";
return string.Empty;
}
private static string GetMetadataFieldNameDescription(IMetadataField? metadataField)
{
if (IsDetailedInfoEnabled())
return metadataField?.Name ?? string.Empty;
return string.Empty;
}
private static string GetTypeFullNameDescription(IMetadataType? metadataFieldType)
{
if (IsDetailedInfoEnabled())
return metadataFieldType != null ? metadataFieldType.FullName : string.Empty;
return string.Empty;
}
private static string GetFieldNameDescription(ITypeOwner? typeOwner)
{
if (IsDetailedInfoEnabled())
return typeOwner?.ShortName ?? string.Empty;
return string.Empty;
}
private static string GetTypeFullNameDescription(IDeclaredType? declaredType)
{
if (IsDetailedInfoEnabled())
return declaredType != null ? declaredType.GetClrName().FullName : string.Empty;
return string.Empty;
}
private static string GetClassTypeFullyQualifiedNameDescription(IMetadataTypeInfo classType)
{
if (IsDetailedInfoEnabled())
return classType.FullyQualifiedName;
return string.Empty;
}
#endregion
}