resharper/resharper-unity/src/Unity/CSharp/Feature/Services/SerializeReference/SerializeReferenceProviderDiffUtils.cs (327 lines of code) (raw):

#nullable enable using System; using System.Collections.Generic; using System.Linq; using JetBrains.Collections; using JetBrains.Diagnostics; using JetBrains.ReSharper.Daemon.UsageChecking; using JetBrains.Util; using JetBrains.Util.Collections; namespace JetBrains.ReSharper.Plugins.Unity.CSharp.Feature.Services.SerializeReference { internal static class SerializeReferenceProviderDiffUtils { public static List<TDIff> CalculateDiff<TDIff, TSetElement>(CountingSet<TSetElement>? oldSet, CountingSet<TSetElement>? newSet, Func<TSetElement, DiffType, int, TDIff> createDiff) { if (oldSet == null && newSet == null) return new List<TDIff>(0); if (oldSet == null && newSet != null) //all data was added return newSet.Select(pair => createDiff(pair.Key, DiffType.Added, pair.Value)).ToList(); if (oldSet != null && newSet == null) //all data was removed return oldSet.Select(pair => createDiff(pair.Key, DiffType.Removed, pair.Value)).ToList(); List<TDIff> result = new(Math.Max(oldSet!.Count, newSet!.Count)); //check if new elements were added or amount of existed changed foreach (var (newElementId, newCount) in newSet) { //newCount couldn't be <= 0 - in this case it wouldn't exists var oldCount = oldSet.GetCount(newElementId); if (oldCount == newCount) //nothing changed continue; //amount of elements changed, could be a new element var countDiff = newCount - oldCount; result.Add(createDiff( newElementId, countDiff > 0 ? DiffType.Added : DiffType.Removed, Math.Abs(countDiff) )); } //check if oldElements were removed foreach (var (oldElementId, oldCount) in oldSet) { var newCount = newSet.GetCount(oldElementId); if (newCount == 0) //check only removed elements { result.Add(createDiff( oldElementId, DiffType.Removed, oldCount )); } } return result; } private static void ApplyDiff(this CountingSet<ElementId> set, List<CountingSetDiff> diff) { foreach (var chunk in diff) { switch (chunk.DiffType) { case DiffType.Added: set.Add(chunk.Id, chunk.Count); break; case DiffType.Removed: set.Remove(chunk.Id, chunk.Count); break; default: throw new ArgumentOutOfRangeException(); } } } private static ClassMetaInfoDiff CalculateDiff(ClassMetaInfo? oldInfo, ClassMetaInfo? newInfo) { if (oldInfo == null && newInfo == null) return ClassMetaInfoDiff.EmptyDiff; if (oldInfo != null && newInfo != null && oldInfo.ClassName != newInfo.ClassName) { throw new ArgumentException( $"Building diff for different classes old:'{oldInfo.ClassName}', new:'{newInfo.ClassName}'"); } var superClassesDiff = CalculateDiff(oldInfo?.SuperClasses, newInfo?.SuperClasses, CreateDiff); var serializeReferenceHoldersDiff = CalculateDiff(oldInfo?.SerializeReferenceHolders, newInfo?.SerializeReferenceHolders, CreateDiff); var typeParametersDiff = CalculateDiff(oldInfo?.TypeParameters, newInfo?.TypeParameters); return new ClassMetaInfoDiff(oldInfo?.ClassName ?? newInfo?.ClassName ?? string.Empty, superClassesDiff, serializeReferenceHoldersDiff, typeParametersDiff); } private static CountingSetDiff CreateDiff(ElementId id, DiffType diffType, int count) { return new(id, diffType, count); } private static List<TypeParametersSetDiff> CalculateDiff(Dictionary<ElementId, TypeParameter>? oldDict, Dictionary<ElementId, TypeParameter>? newDict) { if (oldDict == null && newDict == null) return new List<TypeParametersSetDiff>(); if (oldDict == null && newDict != null) //all data was added return newDict.Select(pair => new TypeParametersSetDiff(pair.Value.ElementId, DiffType.Added, pair.Value.Index, pair.Value.Name, CalculateDiff(null, pair.Value.SerializeReferenceHolders, CreateDiff))) .ToList(); if (oldDict != null && newDict == null) //all data was removed return oldDict.Select(pair => new TypeParametersSetDiff(pair.Value.ElementId, DiffType.Removed, pair.Value.Index, pair.Value.Name , new List<CountingSetDiff>())) .ToList(); List<TypeParametersSetDiff> result = new(Math.Max(oldDict!.Count, newDict!.Count)); //check if new elements were added or amount of existed changed foreach (var (newId, newParameter) in newDict) { var contains = oldDict.TryGetValue(newId, out var oldParameter); var serializeReferenceHoldersDiff = CalculateDiff(oldParameter?.SerializeReferenceHolders, newParameter.SerializeReferenceHolders, CreateDiff); if (!contains || serializeReferenceHoldersDiff.Count > 0) //nothing changed result.Add(new TypeParametersSetDiff( newParameter.ElementId, DiffType.Added, newParameter.Index, newParameter.Name, serializeReferenceHoldersDiff)); } //check if oldElements were removed foreach (var (oldId, oldParameter) in oldDict) { var contains = newDict.TryGetValue(oldId, out _); if (!contains) //check only removed elements result.Add(new TypeParametersSetDiff( oldParameter.ElementId, DiffType.Removed, oldParameter.Index, oldParameter.Name, new List<CountingSetDiff>())); } return result; } private static void ApplyDiff(this IndexClassInfo data, ClassMetaInfoDiff diff, IndexClassInfoDictionary classInfo, ElementId diffElementId) { var dataNameIsEmpty = string.IsNullOrEmpty(data.ClassName); if (!dataNameIsEmpty && diff.ClassName != data.ClassName) throw new ArgumentException( $"Applying diff to wrong class diff.ClassName:'{diff.ClassName}', metaInfo:'{data.ClassName}'"); if (dataNameIsEmpty && !string.IsNullOrEmpty(diff.ClassName)) data.ReplaceEmptyName(diff.ClassName); if (diff.IsEmpty()) return; data.SuperClasses.ApplyDiff(diff.SuperClassesDiff); data.SerializeReferenceHolders.ApplyDiff(diff.SerializeReferenceHoldersDiff); foreach (var superClassesDiff in diff.SuperClassesDiff) { var superClassId = superClassesDiff.Id; if (classInfo.TryGetValue(superClassId, out var superClassInfo)) { switch (superClassesDiff.DiffType) { case DiffType.Added: superClassInfo.Inheritors.Add(diffElementId); break; case DiffType.Removed: superClassInfo.Inheritors.Remove(diffElementId); break; default: throw new ArgumentOutOfRangeException(); } } else { switch (superClassesDiff.DiffType) { case DiffType.Added: var indexClassInfo = new IndexClassInfo(string.Empty); //TODO - maybe names are useless - just for debug indexClassInfo.Inheritors.Add(diffElementId); classInfo.Add(superClassId, indexClassInfo); break; case DiffType.Removed: //Super class already removed break; default: throw new ArgumentOutOfRangeException(); } } } } internal static List<TypeToInterfaceDiff> CalculateDiff(ClassMetaInfoDictionary? oldData, ClassMetaInfoDictionary? newData) { if (oldData == null && newData == null) return new List<TypeToInterfaceDiff>(0); if (oldData == null && newData != null) //all data was added return newData.Select(pair => new TypeToInterfaceDiff(pair.Key, CalculateDiff(null, pair.Value), DiffType.Added)).ToList(); if (oldData != null && newData == null) //all data was removed return oldData.Select(pair => new TypeToInterfaceDiff(pair.Key, CalculateDiff(pair.Value, null), DiffType.Removed)).ToList(); List<TypeToInterfaceDiff> result = new(Math.Max(oldData!.Count, newData!.Count)); //check if new elements were added or changed foreach (var (newElementId, newMetaInfo) in newData) { oldData.TryGetValue(newElementId, out var oldMetaInfo); var typeToInterfaceDiffs = CalculateDiff(oldMetaInfo, newMetaInfo); result.Add(new TypeToInterfaceDiff(newElementId, typeToInterfaceDiffs, DiffType.Added)); } //check if oldElements were removed foreach (var (oldElementId, oldInfo) in oldData) { if (!newData.ContainsKey(oldElementId)) //check only removed elements { result.Add(new TypeToInterfaceDiff(oldElementId, CalculateDiff(oldInfo, null), DiffType.Removed)); } } return result; } internal static void ApplyDiff(IndexClassInfoDictionary classInfo, List<TypeToInterfaceDiff> diffs) { foreach (var diff in diffs) { var classMetaInfoDiff = diff.MetaInfoDiff; if (diff.DiffType == DiffType.None && classMetaInfoDiff.IsEmpty()) continue; var diffElementId = diff.ElementId; if (classInfo.TryGetValue(diffElementId, out var metaInfo)) { metaInfo.ApplyDiff(classMetaInfoDiff, classInfo, diffElementId); //TODO - not the optimal solution //even in case or 2 files with partial class - class will be removed from index //and this deletion will trigger update for another file with partial class - and this class will be added back if (diff.DiffType == DiffType.Removed) classInfo.Remove(diffElementId); } else { //Assertion.Require(!string.IsNullOrEmpty(classMetaInfoDiff.ClassName)); Assertion.Require(!classMetaInfoDiff.IsEmpty() || diff.DiffType == DiffType.Added, $"!classMetaInfoDiff.IsEmpty() || diff.DiffType == DiffType.Added [isEmpty:{classMetaInfoDiff.IsEmpty()}, diff.Type:{diff.DiffType}]"); metaInfo = new IndexClassInfo(classMetaInfoDiff.ClassName); metaInfo.ApplyDiff(classMetaInfoDiff, classInfo, diffElementId); classInfo.Add(diffElementId, metaInfo); } ProcessTypeParametersDiff(classInfo, classMetaInfoDiff); } } private static void ProcessTypeParametersDiff(IndexClassInfoDictionary classInfo, ClassMetaInfoDiff classMetaInfoDiff) { foreach (var diff in classMetaInfoDiff.TypeParametersSetDiffs) { var diffElementId = diff.Id; if (classInfo.TryGetValue(diffElementId, out var metaInfo)) { //class - removed or updated if (diff.DiffType == DiffType.Removed) classInfo.Remove(diffElementId); else metaInfo.SerializeReferenceHolders.ApplyDiff(diff.SerializeReferenceHoldersDiff); } else { // Assertion.Require(!string.IsNullOrEmpty(diff.ClassName)); Assertion.Require(diff.DiffType == DiffType.Added, $"diff.DiffType == DiffType.Added [diffType:{diff.DiffType}]"); metaInfo = new IndexClassInfo(diff.ClassName, true); metaInfo.SerializeReferenceHolders.ApplyDiff(diff.SerializeReferenceHoldersDiff); classInfo.Add(diffElementId, metaInfo); } } } private static void UnionWith(this TypeParameter typeParameter, TypeParameter other) { Assertion.Require(typeParameter.Index == other.Index, "typeParameter.Index == other.Index"); Assertion.Require(typeParameter.ElementId == other.ElementId, "typeParameter.ElementId == other.ElementId"); Assertion.Require(typeParameter.Name == other.Name, "typeParameter.Name == other.Name"); typeParameter.SerializeReferenceHolders.UnionWith(other.SerializeReferenceHolders); } public static void UnionWith(this Dictionary<ElementId, TypeParameter> dict, Dictionary<ElementId, TypeParameter> other) { foreach (var (key, value) in other) { if (dict.TryGetValue(key, out var existedValue)) existedValue.UnionWith(value); else dict.Add(key, value); } } public static void UnionWith<T>(this CountingSet<T> set, CountingSet<T> other) { foreach (var (key, value) in other) { set.Add(key, value); } } public static void ApplyDiff(IndexClassInfoDictionary classInfo, List<TypeParameterResolvesDiff> resolvesDiff) { foreach (var diff in resolvesDiff) { var resolution = diff.TypeParameterResolve; Assertion.Require(resolution != null, "resolution != null"); var openTypeExists = classInfo.TryGetValue(resolution.OpenTypeId, out var openTypeInfo); var resolvedTypeExists = classInfo.TryGetValue(resolution.ResolvedTypeId, out var resolvedInfo); switch (diff.DiffType) { case DiffType.Removed: if (openTypeExists) { Assertion.Require(openTypeInfo != null, $"openTypeInfo != null | {diff.DiffType}"); openTypeInfo.Inheritors.Remove(resolution.ResolvedTypeId); } if (resolvedTypeExists) { Assertion.Require(resolvedInfo != null, $"resolvedInfo != null | {diff.DiffType}"); resolvedInfo.SuperClasses.Remove(resolution.OpenTypeId); } break; case DiffType.Added: if (!openTypeExists) { openTypeInfo = new IndexClassInfo(resolution.ResolutionString, true); classInfo.Add(resolution.OpenTypeId, openTypeInfo); } Assertion.Require(openTypeInfo != null, $"openTypeInfo != null | {diff.DiffType}"); openTypeInfo.Inheritors.Add(resolution.ResolvedTypeId); if (!resolvedTypeExists) { resolvedInfo = new IndexClassInfo(string.Empty, true); //TODO: remove this extra validations after fix var resolutionResolvedTypeId = resolution.ResolvedTypeId; var alreadyHasSameKey = classInfo.TryGetValue(resolutionResolvedTypeId, out var doubleCheckVal); Assertion.Require(!alreadyHasSameKey, $"Dictionary {nameof(classInfo)} already has this key:{resolutionResolvedTypeId}, value:{doubleCheckVal}, attemption to add {resolvedInfo}"); if(!alreadyHasSameKey) classInfo.Add(resolutionResolvedTypeId, resolvedInfo); } Assertion.Require(resolvedInfo != null, $"resolvedInfo != null | {diff.DiffType}"); resolvedInfo.SuperClasses.Add(resolution.OpenTypeId); break; default: throw new ArgumentOutOfRangeException(); } } } } }