context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using dnlib.DotNet;
  
  namespace dnSpy.AsmEditor.Compiler {
  	sealed class ImportSigComparerOptions {
  		public ModuleDef SourceModule { get; }
  		public ModuleDef TargetModule { get; }
  		public ImportSigComparerOptions(ModuleDef sourceModule, ModuleDef targetModule) {
  			SourceModule = sourceModule;
  			TargetModule = targetModule;
  		}
  	}
  
  	sealed class ImportTypeEqualityComparer : IEqualityComparer<TypeDef>, IEqualityComparer<ITypeDefOrRef>, IEqualityComparer<IType>, IEqualityComparer<TypeSig> {
  		/*readonly*/ ImportSigComparer comparer;
  		public ImportTypeEqualityComparer(ImportSigComparer comparer) => this.comparer = comparer;
  		public bool Equals(TypeDef x, TypeDef y) => comparer.Equals(x, y);
  		public int GetHashCode(TypeDef obj) => comparer.GetHashCode(obj);
  		public bool Equals(ITypeDefOrRef x, ITypeDefOrRef y) => comparer.Equals(x, y);
  		public int GetHashCode(ITypeDefOrRef obj) => comparer.GetHashCode(obj);
  		public bool Equals(IType x, IType y) => comparer.Equals(x, y);
  		public int GetHashCode(IType obj) => comparer.GetHashCode(obj);
  		public bool Equals(TypeSig x, TypeSig y) => comparer.Equals(x, y);
  		public int GetHashCode(TypeSig obj) => comparer.GetHashCode(obj);
  	}
  
  	sealed class ImportPropertyEqualityComparer : IEqualityComparer<PropertyDef> {
  		/*readonly*/ ImportSigComparer comparer;
  		public ImportPropertyEqualityComparer(ImportSigComparer comparer) => this.comparer = comparer;
  		public bool Equals(PropertyDef x, PropertyDef y) => comparer.Equals(x, y);
  		public int GetHashCode(PropertyDef obj) => comparer.GetHashCode(obj);
  	}
  
  	sealed class ImportEventEqualityComparer : IEqualityComparer<EventDef> {
  		/*readonly*/ ImportSigComparer comparer;
  		public ImportEventEqualityComparer(ImportSigComparer comparer) => this.comparer = comparer;
  		public bool Equals(EventDef x, EventDef y) => comparer.Equals(x, y);
  		public int GetHashCode(EventDef obj) => comparer.GetHashCode(obj);
  	}
  
  	sealed class ImportMethodEqualityComparer : IEqualityComparer<MethodDef>, IEqualityComparer<MemberRef>, IEqualityComparer<IMethod> {
  		/*readonly*/ ImportSigComparer comparer;
  		public ImportMethodEqualityComparer(ImportSigComparer comparer) => this.comparer = comparer;
  		public bool Equals(MethodDef x, MethodDef y) => comparer.Equals(x, y);
  		public int GetHashCode(MethodDef obj) => comparer.GetHashCode(obj);
  		public bool Equals(MemberRef x, MemberRef y) => comparer.Equals(x, y);
  		public int GetHashCode(MemberRef obj) => comparer.GetHashCode(obj);
  		public bool Equals(IMethod x, IMethod y) => comparer.Equals(x, y);
  		public int GetHashCode(IMethod obj) => comparer.GetHashCode(obj);
  	}
  
  	sealed class ImportFieldEqualityComparer : IEqualityComparer<FieldDef>, IEqualityComparer<MemberRef>, IEqualityComparer<IField> {
  		/*readonly*/ ImportSigComparer comparer;
  		public ImportFieldEqualityComparer(ImportSigComparer comparer) => this.comparer = comparer;
  		public bool Equals(FieldDef x, FieldDef y) => comparer.Equals(x, y);
  		public int GetHashCode(FieldDef obj) => comparer.GetHashCode(obj);
  		public bool Equals(MemberRef x, MemberRef y) => comparer.Equals(x, y);
  		public int GetHashCode(MemberRef obj) => comparer.GetHashCode(obj);
  		public bool Equals(IField x, IField y) => comparer.Equals(x, y);
  		public int GetHashCode(IField obj) => comparer.GetHashCode(obj);
  	}
  
  	// Most code is from dnlib.DotNet.SigComparer
  	struct ImportSigComparer {
  		const int HASHCODE_MAGIC_GLOBAL_TYPE = 1654396648;
  		const int HASHCODE_MAGIC_NESTED_TYPE = -1049070942;
  		const int HASHCODE_MAGIC_ET_MODULE = -299744851;
  		const int HASHCODE_MAGIC_ET_VALUEARRAY = -674970533;
  		const int HASHCODE_MAGIC_ET_GENERICINST = -2050514639;
  		const int HASHCODE_MAGIC_ET_VAR = 1288450097;
  		const int HASHCODE_MAGIC_ET_MVAR = -990598495;
  		const int HASHCODE_MAGIC_ET_ARRAY = -96331531;
  		const int HASHCODE_MAGIC_ET_SZARRAY = 871833535;
  		const int HASHCODE_MAGIC_ET_BYREF = -634749586;
  		const int HASHCODE_MAGIC_ET_PTR = 1976400808;
  		const int HASHCODE_MAGIC_ET_SENTINEL = 68439620;
  
  		RecursionCounter recursionCounter;
  		SigComparerOptions options;
  		readonly ImportSigComparerOptions importOptions;
  		readonly ModuleDef sourceModule;
  
  		bool DontCompareTypeScope => (options & SigComparerOptions.DontCompareTypeScope) != 0;
  		bool CompareMethodFieldDeclaringType => (options & SigComparerOptions.CompareMethodFieldDeclaringType) != 0;
  		bool ComparePropertyDeclaringType => (options & SigComparerOptions.ComparePropertyDeclaringType) != 0;
  		bool CompareEventDeclaringType => (options & SigComparerOptions.CompareEventDeclaringType) != 0;
  		bool CompareSentinelParams => (options & SigComparerOptions.CompareSentinelParams) != 0;
  		bool CompareAssemblyPublicKeyToken => (options & SigComparerOptions.CompareAssemblyPublicKeyToken) != 0;
  		bool CompareAssemblyVersion => (options & SigComparerOptions.CompareAssemblyVersion) != 0;
  		bool CompareAssemblyLocale => (options & SigComparerOptions.CompareAssemblyLocale) != 0;
  		bool TypeRefCanReferenceGlobalType => (options & SigComparerOptions.TypeRefCanReferenceGlobalType) != 0;
  		bool DontCompareReturnType => (options & SigComparerOptions.DontCompareReturnType) != 0;
  		bool CaseInsensitiveTypeNamespaces => (options & SigComparerOptions.CaseInsensitiveTypeNamespaces) != 0;
  		bool CaseInsensitiveTypeNames => (options & SigComparerOptions.CaseInsensitiveTypeNames) != 0;
  		bool CaseInsensitiveMethodFieldNames => (options & SigComparerOptions.CaseInsensitiveMethodFieldNames) != 0;
  		bool CaseInsensitivePropertyNames => (options & SigComparerOptions.CaseInsensitivePropertyNames) != 0;
  		bool CaseInsensitiveEventNames => (options & SigComparerOptions.CaseInsensitiveEventNames) != 0;
  		bool PrivateScopeFieldIsComparable => (options & SigComparerOptions.PrivateScopeFieldIsComparable) != 0;
  		bool PrivateScopeMethodIsComparable => (options & SigComparerOptions.PrivateScopeMethodIsComparable) != 0;
  		bool RawSignatureCompare => (options & SigComparerOptions.RawSignatureCompare) != 0;
  		bool IgnoreModifiers => (options & SigComparerOptions.IgnoreModifiers) != 0;
  		bool MscorlibIsNotSpecial => (options & SigComparerOptions.MscorlibIsNotSpecial) != 0;
  
  		public ImportSigComparer(ImportSigComparerOptions importOptions, SigComparerOptions options, ModuleDef sourceModule) {
  			recursionCounter = new RecursionCounter();
  			this.options = options;
  			this.importOptions = importOptions ?? throw new ArgumentNullException(nameof(importOptions));
  			this.sourceModule = sourceModule;
  		}
  
  		int GetHashCode_FnPtr_SystemIntPtr() =>
  			GetHashCode_TypeNamespace("System") +
  			GetHashCode_TypeName("IntPtr");
  
  		bool Equals_Names(bool caseInsensitive, UTF8String a, UTF8String b) {
  			if (caseInsensitive)
  				return UTF8String.ToSystemStringOrEmpty(a).Equals(UTF8String.ToSystemStringOrEmpty(b), StringComparison.OrdinalIgnoreCase);
  			return UTF8String.Equals(a, b);
  		}
  
  		bool Equals_Names(bool caseInsensitive, string a, string b) {
  			if (caseInsensitive)
  				return (a ?? string.Empty).Equals(b ?? string.Empty, StringComparison.OrdinalIgnoreCase);
  			return (a ?? string.Empty) == (b ?? string.Empty);
  		}
  
  		int GetHashCode_Name(bool caseInsensitive, string a) {
  			if (caseInsensitive)
  				return (a ?? string.Empty).ToUpperInvariant().GetHashCode();
  			return (a ?? string.Empty).GetHashCode();
  		}
  
  		bool Equals_TypeNamespaces(UTF8String a, UTF8String b) => Equals_Names(CaseInsensitiveTypeNamespaces, a, b);
  
  		bool Equals_TypeNamespaces(UTF8String a, string b) => Equals_Names(CaseInsensitiveTypeNamespaces, UTF8String.ToSystemStringOrEmpty(a), b);
  
  		int GetHashCode_TypeNamespace(UTF8String a) => GetHashCode_Name(CaseInsensitiveTypeNamespaces, UTF8String.ToSystemStringOrEmpty(a));
  
  		int GetHashCode_TypeNamespace(string a) => GetHashCode_Name(CaseInsensitiveTypeNamespaces, a);
  
  		bool Equals_TypeNames(UTF8String a, UTF8String b) => Equals_Names(CaseInsensitiveTypeNames, a, b);
  
  		bool Equals_TypeNames(UTF8String a, string b) => Equals_Names(CaseInsensitiveTypeNames, UTF8String.ToSystemStringOrEmpty(a), b);
  
  		int GetHashCode_TypeName(UTF8String a) => GetHashCode_Name(CaseInsensitiveTypeNames, UTF8String.ToSystemStringOrEmpty(a));
  
  		int GetHashCode_TypeName(string a) => GetHashCode_Name(CaseInsensitiveTypeNames, a);
  
  		bool Equals_MethodFieldNames(UTF8String a, UTF8String b) => Equals_Names(CaseInsensitiveMethodFieldNames, a, b);
  
  		bool Equals_MethodFieldNames(UTF8String a, string b) => Equals_Names(CaseInsensitiveMethodFieldNames, UTF8String.ToSystemStringOrEmpty(a), b);
  
  		int GetHashCode_MethodFieldName(UTF8String a) => GetHashCode_Name(CaseInsensitiveMethodFieldNames, UTF8String.ToSystemStringOrEmpty(a));
  
  		int GetHashCode_MethodFieldName(string a) => GetHashCode_Name(CaseInsensitiveMethodFieldNames, a);
  
  		bool Equals_PropertyNames(UTF8String a, UTF8String b) => Equals_Names(CaseInsensitivePropertyNames, a, b);
  
  		bool Equals_PropertyNames(UTF8String a, string b) => Equals_Names(CaseInsensitivePropertyNames, UTF8String.ToSystemStringOrEmpty(a), b);
  
  		int GetHashCode_PropertyName(UTF8String a) => GetHashCode_Name(CaseInsensitivePropertyNames, UTF8String.ToSystemStringOrEmpty(a));
  
  		int GetHashCode_PropertyName(string a) => GetHashCode_Name(CaseInsensitivePropertyNames, a);
  
  		bool Equals_EventNames(UTF8String a, UTF8String b) => Equals_Names(CaseInsensitiveEventNames, a, b);
  
  		bool Equals_EventNames(UTF8String a, string b) => Equals_Names(CaseInsensitiveEventNames, UTF8String.ToSystemStringOrEmpty(a), b);
  
  		int GetHashCode_EventName(UTF8String a) => GetHashCode_Name(CaseInsensitiveEventNames, UTF8String.ToSystemStringOrEmpty(a));
  
  		int GetHashCode_EventName(string a) => GetHashCode_Name(CaseInsensitiveEventNames, a);
  
  		static GenericInstSig? GetGenericInstanceType(IMemberRefParent parent) {
  			var ts = parent as TypeSpec;
  			if (ts is null)
  				return null;
  			return ts.TypeSig.RemoveModifiers() as GenericInstSig;
  		}
  
  		bool Equals(IAssembly aAsm, IAssembly bAsm, TypeRef b) {
  			if (Equals(aAsm, bAsm))
  				return true;
  
  			// Could be an exported type. Resolve it and check again.
  
  			var td = b.Resolve(sourceModule);
  			return !(td is null) && Equals(aAsm, td.Module.Assembly);
  		}
  
  		bool Equals(IAssembly aAsm, IAssembly bAsm, ExportedType b) {
  			if (Equals(aAsm, bAsm))
  				return true;
  
  			var td = b.Resolve();
  			return !(td is null) && Equals(aAsm, td.Module.Assembly);
  		}
  
  		bool Equals(IAssembly? aAsm, TypeRef a, IAssembly? bAsm, TypeRef b) {
  			if (Equals(aAsm, bAsm))
  				return true;
  
  			// Could be exported types. Resolve them and check again.
  
  			var tda = a.Resolve(sourceModule);
  			var tdb = b.Resolve(sourceModule);
  			return !(tda is null) && !(tdb is null) && Equals(tda.Module.Assembly, tdb.Module.Assembly);
  		}
  
  		bool Equals(IAssembly? aAsm, ExportedType a, IAssembly? bAsm, ExportedType b) {
  			if (Equals(aAsm, bAsm))
  				return true;
  
  			var tda = a.Resolve();
  			var tdb = b.Resolve();
  			return !(tda is null) && !(tdb is null) && Equals(tda.Module.Assembly, tdb.Module.Assembly);
  		}
  
  		bool Equals(IAssembly? aAsm, TypeRef a, IAssembly? bAsm, ExportedType b) {
  			if (Equals(aAsm, bAsm))
  				return true;
  
  			// Could be an exported type. Resolve it and check again.
  
  			var tda = a.Resolve(sourceModule);
  			var tdb = b.Resolve();
  			return !(tda is null) && !(tdb is null) && Equals(tda.Module.Assembly, tdb.Module.Assembly);
  		}
  
  		bool Equals(TypeDef a, IModule bMod, TypeRef b) {
  			if (Equals(a.Module, bMod) && Equals(a.DefinitionAssembly, b.DefinitionAssembly))
  				return true;
  
  			// Could be an exported type. Resolve it and check again.
  
  			var td = b.Resolve(sourceModule);
  			if (td is null)
  				return false;
  			return Equals(a.Module, td.Module) && Equals(a.DefinitionAssembly, td.DefinitionAssembly);
  		}
  
  		bool Equals(TypeDef a, FileDef bFile, ExportedType b) {
  			if (Equals(a.Module, bFile) && Equals(a.DefinitionAssembly, b.DefinitionAssembly))
  				return true;
  
  			var td = b.Resolve();
  			return !(td is null) && Equals(a.Module, td.Module) && Equals(a.DefinitionAssembly, td.DefinitionAssembly);
  		}
  
  		bool TypeDefScopeEquals(TypeDef? a, TypeDef? b) {
  			if (a is null || b is null)
  				return false;
  			return Equals(a.Module, b.Module);
  		}
  
  		bool Equals(TypeRef a, IModule? ma, TypeRef b, IModule? mb) {
  			if (Equals(ma, mb) && Equals(a.DefinitionAssembly, b.DefinitionAssembly))
  				return true;
  
  			// Could be exported types. Resolve them and check again.
  
  			var tda = a.Resolve(sourceModule);
  			var tdb = b.Resolve(sourceModule);
  			return !(tda is null) && !(tdb is null) &&
  				Equals(tda.Module, tdb.Module) && Equals(tda.DefinitionAssembly, tdb.DefinitionAssembly);
  		}
  
  		bool Equals(TypeRef a, IModule? ma, ExportedType b, FileDef? fb) {
  			if (Equals(ma, fb) && Equals(a.DefinitionAssembly, b.DefinitionAssembly))
  				return true;
  
  			// Could be an exported type. Resolve it and check again.
  
  			var tda = a.Resolve(sourceModule);
  			var tdb = b.Resolve();
  			return !(tda is null) && !(tdb is null) &&
  				Equals(tda.Module, tdb.Module) && Equals(tda.DefinitionAssembly, tdb.DefinitionAssembly);
  		}
  
  		public bool Equals(IMemberRef? a, IMemberRef? b) {
  			if (a == b)
  				return true;
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result;
  			IType? ta, tb;
  			IField? fa, fb;
  			IMethod? ma, mb;
  			PropertyDef? pa, pb;
  			EventDef? ea, eb;
  
  			if (!((ta = a as IType) is null) && !((tb = b as IType) is null))
  				result = Equals(ta, tb);
  			else if (!((fa = a as IField) is null) && !((fb = b as IField) is null) && fa.IsField && fb.IsField)
  				result = Equals(fa, fb);
  			else if (!((ma = a as IMethod) is null) && !((mb = b as IMethod) is null))
  				result = Equals(ma, mb);
  			else if (!((pa = a as PropertyDef) is null) && !((pb = b as PropertyDef) is null))
  				result = Equals(pa, pb);
  			else if (!((ea = a as EventDef) is null) && !((eb = b as EventDef) is null))
  				result = Equals(ea, eb);
  			else
  				result = false;
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public int GetHashCode(IMemberRef? a) {
  			if (a is null)
  				return 0;
  			if (!recursionCounter.Increment())
  				return 0;
  
  			int result;
  			IType? ta;
  			IField? fa;
  			IMethod? ma;
  			PropertyDef? pa;
  			EventDef? ea;
  
  			if (!((ta = a as IType) is null))
  				result = GetHashCode(ta);
  			else if (!((fa = a as IField) is null))
  				result = GetHashCode(fa);
  			else if (!((ma = a as IMethod) is null))
  				result = GetHashCode(ma);
  			else if (!((pa = a as PropertyDef) is null))
  				result = GetHashCode(pa);
  			else if (!((ea = a as EventDef) is null))
  				result = GetHashCode(ea);
  			else
  				result = 0;		// Should never be reached
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(ITypeDefOrRef a, ITypeDefOrRef b) => Equals((IType)a, (IType)b);
  
  		public int GetHashCode(ITypeDefOrRef a) => GetHashCode((IType)a);
  
  		public bool Equals(IType? a, IType? b) {
  			if (a == b)
  				return true;
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result;
  			TypeDef? tda, tdb;
  			TypeRef? tra, trb;
  			TypeSpec? tsa, tsb;
  			TypeSig? sa, sb;
  			ExportedType? eta, etb;
  
  			if (!((tda = a as TypeDef) is null) & !((tdb = b as TypeDef) is null))
  				result = Equals(tda, tdb);
  			else if (!((tra = a as TypeRef) is null) & !((trb = b as TypeRef) is null))
  				result = Equals(tra, trb);
  			else if (!((tsa = a as TypeSpec) is null) & !((tsb = b as TypeSpec) is null))
  				result = Equals(tsa, tsb);
  			else if (!((sa = a as TypeSig) is null) & !((sb = b as TypeSig) is null))
  				result = Equals(sa, sb);
  			else if (!((eta = a as ExportedType) is null) & !((etb = b as ExportedType) is null))
  				result = Equals(eta, etb);
  			else if (!(tda is null) && !(trb is null))
  				result = Equals(tda, trb);		// TypeDef vs TypeRef
  			else if (!(tra is null) && !(tdb is null))
  				result = Equals(tdb, tra);		// TypeDef vs TypeRef
  			else if (!(tda is null) && !(tsb is null))
  				result = Equals(tda, tsb);		// TypeDef vs TypeSpec
  			else if (!(tsa is null) && !(tdb is null))
  				result = Equals(tdb, tsa);		// TypeDef vs TypeSpec
  			else if (!(tda is null) && !(sb is null))
  				result = Equals(tda, sb);		// TypeDef vs TypeSig
  			else if (!(sa is null) && !(tdb is null))
  				result = Equals(tdb, sa);		// TypeDef vs TypeSig
  			else if (!(tda is null) && !(etb is null))
  				result = Equals(tda, etb);		// TypeDef vs ExportedType
  			else if (!(eta is null) && !(tdb is null))
  				result = Equals(tdb, eta);		// TypeDef vs ExportedType
  			else if (!(tra is null) && !(tsb is null))
  				result = Equals(tra, tsb);		// TypeRef vs TypeSpec
  			else if (!(tsa is null) && !(trb is null))
  				result = Equals(trb, tsa);		// TypeRef vs TypeSpec
  			else if (!(tra is null) && !(sb is null))
  				result = Equals(tra, sb);		// TypeRef vs TypeSig
  			else if (!(sa is null) && !(trb is null))
  				result = Equals(trb, sa);		// TypeRef vs TypeSig
  			else if (!(tra is null) && !(etb is null))
  				result = Equals(tra, etb);		// TypeRef vs ExportedType
  			else if (!(eta is null) && !(trb is null))
  				result = Equals(trb, eta);		// TypeRef vs ExportedType
  			else if (!(tsa is null) && !(sb is null))
  				result = Equals(tsa, sb);		// TypeSpec vs TypeSig
  			else if (!(sa is null) && !(tsb is null))
  				result = Equals(tsb, sa);		// TypeSpec vs TypeSig
  			else if (!(tsa is null) && !(etb is null))
  				result = Equals(tsa, etb);		// TypeSpec vs ExportedType
  			else if (!(eta is null) && !(tsb is null))
  				result = Equals(tsb, eta);		// TypeSpec vs ExportedType
  			else if (!(sa is null) && !(etb is null))
  				result = Equals(sa, etb);		// TypeSig vs ExportedType
  			else if (!(eta is null) && !(sb is null))
  				result = Equals(sb, eta);		// TypeSig vs ExportedType
  			else
  				result = false;	// Should never be reached
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public int GetHashCode(IType? a) {
  			if (a is null)
  				return 0;
  			if (!recursionCounter.Increment())
  				return 0;
  
  			int hash;
  			TypeDef? td;
  			TypeRef? tr;
  			TypeSpec? ts;
  			TypeSig? sig;
  			ExportedType? et;
  
  			if (!((td = a as TypeDef) is null))
  				hash = GetHashCode(td);
  			else if (!((tr = a as TypeRef) is null))
  				hash = GetHashCode(tr);
  			else if (!((ts = a as TypeSpec) is null))
  				hash = GetHashCode(ts);
  			else if (!((sig = a as TypeSig) is null))
  				hash = GetHashCode(sig);
  			else if (!((et = a as ExportedType) is null))
  				hash = GetHashCode(et);
  			else
  				hash = 0;	// Should never be reached
  
  			recursionCounter.Decrement();
  			return hash;
  		}
  
  		public bool Equals(TypeRef? a, TypeDef? b) => Equals(b, a);
  
  		public bool Equals(TypeDef? a, TypeRef? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result;
  			IModule? bMod;
  			AssemblyRef? bAsm;
  			TypeRef? dtb;
  
  			var scope = b.ResolutionScope;
  
  			if (!Equals_TypeNames(a.Name, b.Name) || !Equals_TypeNamespaces(a.Namespace, b.Namespace))
  				result = false;
  			else if (!((dtb = scope as TypeRef) is null))	// nested type
  				result = Equals(a.DeclaringType, dtb);	// Compare enclosing types
  			else if (!(a.DeclaringType is null)) {
  				// a is nested, b isn't
  				result = false;
  			}
  			else if (DontCompareTypeScope)
  				result = true;
  			else if (!((bMod = scope as IModule) is null))	// 'b' is defined in the same assembly as 'a'
  				result = Equals(a, bMod, b);
  			else if (!((bAsm = scope as AssemblyRef) is null)) {
  				var aMod = a.Module;
  				result = !(aMod is null) && Equals(aMod.Assembly, bAsm, b);
  			}
  			else {
  				result = false;
  				//TODO: Handle the case where scope is null
  			}
  
  			if (result && !TypeRefCanReferenceGlobalType && a.IsGlobalModuleType)
  				result = false;
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(ExportedType? a, TypeDef? b) => Equals(b, a);
  
  		public bool Equals(TypeDef? a, ExportedType? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result;
  			ExportedType? dtb;
  			FileDef? bFile;
  			AssemblyRef? bAsm;
  
  			var scope = b.Implementation;
  
  			if (!Equals_TypeNames(a.Name, b.TypeName) || !Equals_TypeNamespaces(a.Namespace, b.TypeNamespace))
  				result = false;
  			else if (!((dtb = scope as ExportedType) is null)) {	// nested type
  				result = Equals(a.DeclaringType, dtb);	// Compare enclosing types
  			}
  			else if (!(a.DeclaringType is null)) {
  				result = false;	// a is nested, b isn't
  			}
  			else if (DontCompareTypeScope)
  				result = true;
  			else {
  				if (!((bFile = scope as FileDef) is null))
  					result = Equals(a, bFile, b);
  				else if (!((bAsm = scope as AssemblyRef) is null)) {
  					var aMod = a.Module;
  					result = !(aMod is null) && Equals(aMod.Assembly, bAsm, b);
  				}
  				else
  					result = false;
  			}
  
  			if (result && !TypeRefCanReferenceGlobalType && a.IsGlobalModuleType)
  				result = false;
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(TypeSpec? a, TypeDef? b) => Equals(b, a);
  
  		public bool Equals(TypeDef? a, TypeSpec? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			return Equals(a, b.TypeSig);
  		}
  
  		public bool Equals(TypeSig? a, TypeDef? b) => Equals(b, a);
  
  		public bool Equals(TypeDef? a, TypeSig? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  			bool result;
  
  			if (b is TypeDefOrRefSig b2)
  				result = Equals(a, (IType)b2.TypeDefOrRef);
  			else if (b is ModifierSig || b is PinnedSig)
  				result = Equals(a, b.Next);
  			else
  				result = false;
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(TypeSpec? a, TypeRef? b) => Equals(b, a);
  
  		public bool Equals(TypeRef? a, TypeSpec? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			return Equals(a, b.TypeSig);
  		}
  
  		public bool Equals(ExportedType? a, TypeRef? b) => Equals(b, a);
  
  		public bool Equals(TypeRef? a, ExportedType? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result = Equals_TypeNames(a.Name, b.TypeName) &&
  					Equals_TypeNamespaces(a.Namespace, b.TypeNamespace) &&
  					EqualsScope(a, b);
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(TypeSig? a, TypeRef? b) => Equals(b, a);
  
  		public bool Equals(TypeRef? a, TypeSig? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  			bool result;
  
  			if (b is TypeDefOrRefSig b2)
  				result = Equals(a, (IType)b2.TypeDefOrRef);
  			else if (b is ModifierSig || b is PinnedSig)
  				result = Equals(a, b.Next);
  			else
  				result = false;
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public bool Equals(TypeSig? a, TypeSpec? b) => Equals(b, a);
  
  		public bool Equals(TypeSpec? a, TypeSig? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			return Equals(a.TypeSig, b);
  		}
  
  		public bool Equals(ExportedType? a, TypeSpec? b) => Equals(b, a);
  
  		public bool Equals(TypeSpec? a, ExportedType? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			return Equals(a.TypeSig, b);
  		}
  
  		public bool Equals(ExportedType? a, TypeSig? b) => Equals(b, a);
  
  		public bool Equals(TypeSig? a, ExportedType? b) {
  			if ((object?)a == b)
  				return true;	// both are null
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  			bool result;
  
  			if (a is TypeDefOrRefSig a2)
  				result = Equals(a2.TypeDefOrRef, b);
  			else if (a is ModifierSig || a is PinnedSig)
  				result = Equals(a.Next, b);
  			else
  				result = false;
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		int GetHashCodeGlobalType() =>
  			// We don't always know the name+namespace of the global type, eg. when it's
  			// referenced by a ModuleRef. Use the same hash for all global types.
  			HASHCODE_MAGIC_GLOBAL_TYPE;
  
  		public bool Equals(TypeRef? a, TypeRef? b) {
  			if (a == b)
  				return true;
  			if (a is null || b is null)
  				return false;
  			if (!recursionCounter.Increment())
  				return false;
  
  			bool result = Equals_TypeNames(a.Name, b.Name) &&
  					Equals_TypeNamespaces(a.Namespace, b.Namespace) &&
  					EqualsResolutionScope(a, b);
  
  			recursionCounter.Decrement();
  			return result;
  		}
  
  		public int GetHashCode(TypeRef? a) {
  			if (a is null)
  				return TypeRefCanReferenceGlobalType ? GetHashCodeGlobalType() : 0;
  
  			int hash;
  			hash = GetHashCode_TypeName(a.Name);
  			if (a.ResolutionScope is TypeRef)
  				hash += HASHCODE_MAGIC_NESTED_TYPE;
  			else
  				hash += GetHashCode_TypeNamespace(a.Namespace);
  			return hash;
  		}
  
  		public bool Equals(ExportedType? a, ExportedType? b) {
  
gt: 			if (a == b)