mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1131 lines
						
					
					
						
							38 KiB
						
					
					
				
			
		
		
	
	
							1131 lines
						
					
					
						
							38 KiB
						
					
					
				// Copyright (c) 2020 Daniel Grunwald | 
						|
//  | 
						|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this | 
						|
// software and associated documentation files (the "Software"), to deal in the Software | 
						|
// without restriction, including without limitation the rights to use, copy, modify, merge, | 
						|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | 
						|
// to whom the Software is furnished to do so, subject to the following conditions: | 
						|
//  | 
						|
// The above copyright notice and this permission notice shall be included in all copies or | 
						|
// substantial portions of the Software. | 
						|
//  | 
						|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | 
						|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | 
						|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | 
						|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | 
						|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | 
						|
// DEALINGS IN THE SOFTWARE. | 
						|
 | 
						|
using System; | 
						|
using System.Collections.Generic; | 
						|
using System.Diagnostics; | 
						|
using System.Linq; | 
						|
using System.Reflection.Metadata; | 
						|
using System.Threading; | 
						|
 | 
						|
using ICSharpCode.Decompiler.IL; | 
						|
using ICSharpCode.Decompiler.IL.Transforms; | 
						|
using ICSharpCode.Decompiler.TypeSystem; | 
						|
 | 
						|
namespace ICSharpCode.Decompiler.CSharp | 
						|
{ | 
						|
	class RecordDecompiler | 
						|
	{ | 
						|
		readonly IDecompilerTypeSystem typeSystem; | 
						|
		readonly ITypeDefinition recordTypeDef; | 
						|
		readonly DecompilerSettings settings; | 
						|
		readonly CancellationToken cancellationToken; | 
						|
		readonly List<IMember> orderedMembers; | 
						|
		readonly bool isInheritedRecord; | 
						|
		readonly bool isStruct; | 
						|
		readonly bool isSealed; | 
						|
		readonly IMethod primaryCtor; | 
						|
		readonly IType baseClass; | 
						|
		readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>(); | 
						|
		readonly Dictionary<IProperty, IField> autoPropertyToBackingField = new Dictionary<IProperty, IField>(); | 
						|
		readonly Dictionary<IParameter, IProperty> primaryCtorParameterToAutoProperty = new Dictionary<IParameter, IProperty>(); | 
						|
		readonly Dictionary<IProperty, IParameter> autoPropertyToPrimaryCtorParameter = new Dictionary<IProperty, IParameter>(); | 
						|
 | 
						|
		public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, DecompilerSettings settings, CancellationToken cancellationToken) | 
						|
		{ | 
						|
			this.typeSystem = dts; | 
						|
			this.recordTypeDef = recordTypeDef; | 
						|
			this.settings = settings; | 
						|
			this.cancellationToken = cancellationToken; | 
						|
			this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class); | 
						|
			this.isStruct = baseClass.IsKnownType(KnownTypeCode.ValueType); | 
						|
			this.isInheritedRecord = !isStruct && !baseClass.IsKnownType(KnownTypeCode.Object); | 
						|
			this.isSealed = recordTypeDef.IsSealed; | 
						|
			DetectAutomaticProperties(); | 
						|
			this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty); | 
						|
			this.primaryCtor = DetectPrimaryConstructor(); | 
						|
		} | 
						|
 | 
						|
		void DetectAutomaticProperties() | 
						|
		{ | 
						|
			var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); | 
						|
			foreach (var property in recordTypeDef.Properties) | 
						|
			{ | 
						|
				cancellationToken.ThrowIfCancellationRequested(); | 
						|
				var p = (IProperty)property.Specialize(subst); | 
						|
				if (IsAutoProperty(p, out var field)) | 
						|
				{ | 
						|
					backingFieldToAutoProperty.Add(field, p); | 
						|
					autoPropertyToBackingField.Add(p, field); | 
						|
				} | 
						|
			} | 
						|
 | 
						|
			bool IsAutoProperty(IProperty p, out IField field) | 
						|
			{ | 
						|
				field = null; | 
						|
				if (p.Parameters.Count != 0) | 
						|
					return false; | 
						|
				if (p.Getter != null) | 
						|
				{ | 
						|
					if (!IsAutoGetter(p.Getter, out field)) | 
						|
						return false; | 
						|
				} | 
						|
				if (p.Setter != null) | 
						|
				{ | 
						|
					if (!IsAutoSetter(p.Setter, out var field2)) | 
						|
						return false; | 
						|
					if (field != null) | 
						|
					{ | 
						|
						if (!field.Equals(field2)) | 
						|
							return false; | 
						|
					} | 
						|
					else | 
						|
					{ | 
						|
						field = field2; | 
						|
					} | 
						|
				} | 
						|
				if (field == null) | 
						|
					return false; | 
						|
				if (!IsRecordType(field.DeclaringType)) | 
						|
					return false; | 
						|
				return field.Name == $"<{p.Name}>k__BackingField"; | 
						|
			} | 
						|
 | 
						|
			bool IsAutoGetter(IMethod method, out IField field) | 
						|
			{ | 
						|
				field = null; | 
						|
				var body = DecompileBody(method); | 
						|
				if (body == null) | 
						|
					return false; | 
						|
				// return this.field; | 
						|
				if (!body.Instructions[0].MatchReturn(out var retVal)) | 
						|
					return false; | 
						|
				if (method.IsStatic) | 
						|
				{ | 
						|
					return retVal.MatchLdsFld(out field); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					if (!retVal.MatchLdFld(out var target, out field)) | 
						|
						return false; | 
						|
					return target.MatchLdThis(); | 
						|
				} | 
						|
			} | 
						|
 | 
						|
			bool IsAutoSetter(IMethod method, out IField field) | 
						|
			{ | 
						|
				field = null; | 
						|
				Debug.Assert(!method.IsStatic); | 
						|
				var body = DecompileBody(method); | 
						|
				if (body == null) | 
						|
					return false; | 
						|
				// this.field = value; | 
						|
				ILInstruction valueInst; | 
						|
				if (method.IsStatic) | 
						|
				{ | 
						|
					if (!body.Instructions[0].MatchStsFld(out field, out valueInst)) | 
						|
						return false; | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					if (!body.Instructions[0].MatchStFld(out var target, out field, out valueInst)) | 
						|
						return false; | 
						|
					if (!target.MatchLdThis()) | 
						|
						return false; | 
						|
				} | 
						|
				if (!valueInst.MatchLdLoc(out var value)) | 
						|
					return false; | 
						|
				if (!(value.Kind == VariableKind.Parameter && value.Index == 0)) | 
						|
					return false; | 
						|
				return body.Instructions[1].MatchReturn(out var retVal) && retVal.MatchNop(); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		IMethod DetectPrimaryConstructor() | 
						|
		{ | 
						|
			if (!settings.UsePrimaryConstructorSyntax) | 
						|
				return null; | 
						|
 | 
						|
			var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); | 
						|
			foreach (var method in recordTypeDef.Methods) | 
						|
			{ | 
						|
				cancellationToken.ThrowIfCancellationRequested(); | 
						|
				if (method.IsStatic || !method.IsConstructor) | 
						|
					continue; | 
						|
				var m = method.Specialize(subst); | 
						|
				if (IsPrimaryConstructor(m, method)) | 
						|
					return method; | 
						|
				primaryCtorParameterToAutoProperty.Clear(); | 
						|
				autoPropertyToPrimaryCtorParameter.Clear(); | 
						|
			} | 
						|
 | 
						|
			return null; | 
						|
 | 
						|
			bool IsPrimaryConstructor(IMethod method, IMethod unspecializedMethod) | 
						|
			{ | 
						|
				Debug.Assert(method.IsConstructor); | 
						|
				var body = DecompileBody(method); | 
						|
				if (body == null) | 
						|
					return false; | 
						|
 | 
						|
				if (method.Parameters.Count == 0) | 
						|
					return false; | 
						|
 | 
						|
				var addonInst = isStruct ? 1 : 2; | 
						|
				if (body.Instructions.Count < method.Parameters.Count + addonInst) | 
						|
					return false; | 
						|
 | 
						|
				for (int i = 0; i < method.Parameters.Count; i++) | 
						|
				{ | 
						|
					if (!body.Instructions[i].MatchStFld(out var target, out var field, out var valueInst)) | 
						|
						return false; | 
						|
					if (!target.MatchLdThis()) | 
						|
						return false; | 
						|
					if (method.Parameters[i].IsIn) | 
						|
					{ | 
						|
						if (!valueInst.MatchLdObj(out valueInst, out _)) | 
						|
							return false; | 
						|
					} | 
						|
					if (!valueInst.MatchLdLoc(out var value)) | 
						|
						return false; | 
						|
					if (!(value.Kind == VariableKind.Parameter && value.Index == i)) | 
						|
						return false; | 
						|
					if (!backingFieldToAutoProperty.TryGetValue(field, out var property)) | 
						|
						return false; | 
						|
					primaryCtorParameterToAutoProperty.Add(unspecializedMethod.Parameters[i], property); | 
						|
					autoPropertyToPrimaryCtorParameter.Add(property, unspecializedMethod.Parameters[i]); | 
						|
				} | 
						|
 | 
						|
				if (!isStruct) | 
						|
				{ | 
						|
					var baseCtorCall = body.Instructions.SecondToLastOrDefault() as CallInstruction; | 
						|
					if (baseCtorCall == null) | 
						|
						return false; | 
						|
				} | 
						|
 | 
						|
				var returnInst = body.Instructions.LastOrDefault(); | 
						|
				return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop(); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		static List<IMember> DetectMemberOrder(ITypeDefinition recordTypeDef, Dictionary<IField, IProperty> backingFieldToAutoProperty) | 
						|
		{ | 
						|
			// For records, the order of members is important: | 
						|
			// Equals/GetHashCode/PrintMembers must agree on an order of fields+properties. | 
						|
			// The IL metadata has the order of fields and the order of properties, but we | 
						|
			// need to detect the correct interleaving. | 
						|
			// We could try to detect this from the PrintMembers body, but let's initially | 
						|
			// restrict ourselves to the common case where the record only uses properties. | 
						|
			var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); | 
						|
			return recordTypeDef.Properties.Select(p => p.Specialize(subst)).Concat( | 
						|
				recordTypeDef.Fields.Select(f => (IField)f.Specialize(subst)).Where(f => !backingFieldToAutoProperty.ContainsKey(f)) | 
						|
			).ToList(); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the fields and properties of the record type, interleaved as necessary to | 
						|
		/// maintain Equals/ToString/etc. semantics. | 
						|
		/// </summary> | 
						|
		public IEnumerable<IMember> FieldsAndProperties => orderedMembers; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the detected primary constructor. Returns null, if there was no primary constructor detected. | 
						|
		/// </summary> | 
						|
		public IMethod PrimaryConstructor => primaryCtor; | 
						|
 | 
						|
		public bool IsInheritedRecord => isInheritedRecord; | 
						|
 | 
						|
		bool IsRecordType(IType type) | 
						|
		{ | 
						|
			return type.GetDefinition() == recordTypeDef | 
						|
				&& type.TypeArguments.SequenceEqual(recordTypeDef.TypeParameters); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets whether the member of the record type will be automatically generated by the compiler. | 
						|
		/// </summary> | 
						|
		public bool MethodIsGenerated(IMethod method) | 
						|
		{ | 
						|
			if (IsCopyConstructor(method)) | 
						|
			{ | 
						|
				return IsGeneratedCopyConstructor(method); | 
						|
			} | 
						|
 | 
						|
			switch (method.Name) | 
						|
			{ | 
						|
				// Some members in records are always compiler-generated and lead to a | 
						|
				// "duplicate definition" error if we emit the generated code. | 
						|
				case "op_Equality": | 
						|
				case "op_Inequality": | 
						|
				{ | 
						|
					// Don't emit comparison operators into C# record definition | 
						|
					// Note: user can declare additional operator== as long as they have | 
						|
					// different parameter types. | 
						|
					return method.Parameters.Count == 2 | 
						|
						&& method.Parameters.All(p => IsRecordType(p.Type)); | 
						|
				} | 
						|
				case "Equals" when method.Parameters.Count == 1: | 
						|
				{ | 
						|
					IType paramType = method.Parameters[0].Type; | 
						|
					if (paramType.IsKnownType(KnownTypeCode.Object) && method.IsOverride) | 
						|
					{ | 
						|
						// override bool Equals(object? obj): always generated | 
						|
						return true; | 
						|
					} | 
						|
					else if (IsRecordType(paramType)) | 
						|
					{ | 
						|
						// virtual bool Equals(R? other): generated unless user-declared | 
						|
						return IsGeneratedEquals(method); | 
						|
					} | 
						|
					else if (isInheritedRecord && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(paramType, baseClass) && method.IsOverride) | 
						|
					{ | 
						|
						// override bool Equals(BaseClass? obj): always generated | 
						|
						return true; | 
						|
					} | 
						|
					else | 
						|
					{ | 
						|
						return false; | 
						|
					} | 
						|
				} | 
						|
				case "GetHashCode": | 
						|
					return IsGeneratedGetHashCode(method); | 
						|
				case "<Clone>$" when method.Parameters.Count == 0: | 
						|
					// Always generated; Method name cannot be expressed in C# | 
						|
					return true; | 
						|
				case "PrintMembers": | 
						|
					return IsGeneratedPrintMembers(method); | 
						|
				case "ToString" when method.Parameters.Count == 0: | 
						|
					return IsGeneratedToString(method); | 
						|
				case "Deconstruct" when primaryCtor != null && method.Parameters.Count == primaryCtor.Parameters.Count: | 
						|
					return IsGeneratedDeconstruct(method); | 
						|
				default: | 
						|
					return false; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		internal bool PropertyIsGenerated(IProperty property) | 
						|
		{ | 
						|
			switch (property.Name) | 
						|
			{ | 
						|
				case "EqualityContract" when !isStruct: | 
						|
					return IsGeneratedEqualityContract(property); | 
						|
				default: | 
						|
					return IsPropertyDeclaredByPrimaryConstructor(property); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		public bool IsPropertyDeclaredByPrimaryConstructor(IProperty property) | 
						|
		{ | 
						|
			var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); | 
						|
			return primaryCtor != null | 
						|
				&& autoPropertyToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst)); | 
						|
		} | 
						|
 | 
						|
		internal (IProperty prop, IField field) GetPropertyInfoByPrimaryConstructorParameter(IParameter parameter) | 
						|
		{ | 
						|
			var prop = primaryCtorParameterToAutoProperty[parameter]; | 
						|
			return (prop, autoPropertyToBackingField[prop]); | 
						|
		} | 
						|
 | 
						|
		public bool IsCopyConstructor(IMethod method) | 
						|
		{ | 
						|
			if (method == null) | 
						|
				return false; | 
						|
 | 
						|
			Debug.Assert(method.DeclaringTypeDefinition == recordTypeDef); | 
						|
 | 
						|
			return method.IsConstructor | 
						|
				&& method.Parameters.Count == 1 | 
						|
				&& IsRecordType(method.Parameters[0].Type); | 
						|
		} | 
						|
 | 
						|
		private bool IsAllowedAttribute(IAttribute attribute) | 
						|
		{ | 
						|
			switch (attribute.AttributeType.ReflectionName) | 
						|
			{ | 
						|
				case "System.Runtime.CompilerServices.CompilerGeneratedAttribute": | 
						|
					return true; | 
						|
				default: | 
						|
					return false; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		private bool IsGeneratedCopyConstructor(IMethod method) | 
						|
		{ | 
						|
			/*  | 
						|
				call BaseClass..ctor(ldloc this, ldloc original) | 
						|
				stfld <X>k__BackingField(ldloc this, ldfld <X>k__BackingField(ldloc original)) | 
						|
				leave IL_0000 (nop) | 
						|
			 */ | 
						|
			Debug.Assert(method.IsConstructor && method.Parameters.Count == 1); | 
						|
			if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) | 
						|
				return false; | 
						|
			if (orderedMembers == null) | 
						|
				return false; | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null) | 
						|
				return false; | 
						|
			var variables = body.Ancestors.OfType<ILFunction>().Single().Variables; | 
						|
			var other = variables.Single(v => v.Kind == VariableKind.Parameter && v.Index == 0); | 
						|
			Debug.Assert(IsRecordType(other.Type)); | 
						|
			int pos = 0; | 
						|
			// First instruction is the base constructor call | 
						|
			if (!(body.Instructions[pos] is Call { Method: { IsConstructor: true } } baseCtorCall)) | 
						|
				return false; | 
						|
			if (!object.Equals(baseCtorCall.Method.DeclaringType, baseClass)) | 
						|
				return false; | 
						|
			if (baseCtorCall.Arguments.Count != (isInheritedRecord ? 2 : 1)) | 
						|
				return false; | 
						|
			if (!baseCtorCall.Arguments[0].MatchLdThis()) | 
						|
				return false; | 
						|
			if (isInheritedRecord) | 
						|
			{ | 
						|
				if (!baseCtorCall.Arguments[1].MatchLdLoc(other)) | 
						|
					return false; | 
						|
			} | 
						|
			pos++; | 
						|
			// Then all the fields are copied over | 
						|
			foreach (var member in orderedMembers) | 
						|
			{ | 
						|
				if (!(member is IField field)) | 
						|
				{ | 
						|
					if (!autoPropertyToBackingField.TryGetValue((IProperty)member, out field)) | 
						|
						continue; | 
						|
				} | 
						|
				if (pos >= body.Instructions.Count) | 
						|
					return false; | 
						|
				if (!body.Instructions[pos].MatchStFld(out var lhsTarget, out var lhsField, out var valueInst)) | 
						|
					return false; | 
						|
				if (!lhsTarget.MatchLdThis()) | 
						|
					return false; | 
						|
				if (!lhsField.Equals(field)) | 
						|
					return false; | 
						|
 | 
						|
				if (!valueInst.MatchLdFld(out var rhsTarget, out var rhsField)) | 
						|
					return false; | 
						|
				if (!rhsTarget.MatchLdLoc(other)) | 
						|
					return false; | 
						|
				if (!rhsField.Equals(field)) | 
						|
					return false; | 
						|
				pos++; | 
						|
			} | 
						|
			return body.Instructions[pos] is Leave; | 
						|
		} | 
						|
 | 
						|
		private bool IsGeneratedEqualityContract(IProperty property) | 
						|
		{ | 
						|
			// Generated member: | 
						|
			// protected virtual Type EqualityContract { | 
						|
			//    [CompilerGenerated] get => typeof(R); | 
						|
			// } | 
						|
			Debug.Assert(!isStruct && property.Name == "EqualityContract"); | 
						|
			if (property.Accessibility != Accessibility.Protected && (!isSealed || property.Accessibility != Accessibility.Private)) | 
						|
				return false; | 
						|
			if (!(isSealed || property.IsVirtual || property.IsOverride)) | 
						|
				return false; | 
						|
			if (property.IsSealed) | 
						|
				return false; | 
						|
			var getter = property.Getter; | 
						|
			if (!(getter != null && !property.CanSet)) | 
						|
				return false; | 
						|
			var attrs = property.GetAttributes().ToList(); | 
						|
			switch (attrs.Count) | 
						|
			{ | 
						|
				case 0: | 
						|
					// Roslyn 3.x does not emit a CompilerGeneratedAttribute on the property itself. | 
						|
					break; | 
						|
				case 1: | 
						|
					// Roslyn 4.4 started doing so. | 
						|
					if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) | 
						|
						return false; | 
						|
					break; | 
						|
				default: | 
						|
					return false; | 
						|
			} | 
						|
			if (getter.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			attrs = getter.GetAttributes().ToList(); | 
						|
			if (attrs.Count != 1) | 
						|
				return false; | 
						|
			if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) | 
						|
				return false; | 
						|
			var body = DecompileBody(getter); | 
						|
			if (body == null || body.Instructions.Count != 1) | 
						|
				return false; | 
						|
			if (!(body.Instructions.Single() is Leave leave)) | 
						|
				return false; | 
						|
			// leave IL_0000 (call GetTypeFromHandle(ldtypetoken R)) | 
						|
			if (!TransformExpressionTrees.MatchGetTypeFromHandle(leave.Value, out IType ty)) | 
						|
				return false; | 
						|
			return IsRecordType(ty); | 
						|
		} | 
						|
 | 
						|
		private bool IsGeneratedPrintMembers(IMethod method) | 
						|
		{ | 
						|
			Debug.Assert(method.Name == "PrintMembers"); | 
						|
			if (method.Parameters.Count != 1) | 
						|
				return false; | 
						|
			if (!isSealed && !method.IsOverridable) | 
						|
				return false; | 
						|
			if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) | 
						|
				return false; | 
						|
			if (orderedMembers == null) | 
						|
				return false; | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null) | 
						|
				return false; | 
						|
			var variables = body.Ancestors.OfType<ILFunction>().Single().Variables; | 
						|
			var builder = variables.Single(v => v.Kind == VariableKind.Parameter && v.Index == 0); | 
						|
			if (builder.Type.ReflectionName != "System.Text.StringBuilder") | 
						|
				return false; | 
						|
			int pos = 0; | 
						|
			//Roslyn 4.0.0-3.final start to insert an call to RuntimeHelpers.EnsureSufficientExecutionStack() | 
						|
			if (!isStruct && !isInheritedRecord && body.Instructions[pos] is Call { | 
						|
				Arguments: { Count: 0 }, | 
						|
				Method: { Name: "EnsureSufficientExecutionStack", DeclaringType: { Namespace: "System.Runtime.CompilerServices", Name: "RuntimeHelpers" } } | 
						|
			}) | 
						|
			{ | 
						|
				pos++; | 
						|
			} | 
						|
			if (isInheritedRecord) | 
						|
			{ | 
						|
				// Special case: inherited record adding no new members | 
						|
				if (body.Instructions[pos].MatchReturn(out var returnValue) | 
						|
					&& IsBaseCall(returnValue) && !orderedMembers.Any(IsPrintedMember)) | 
						|
				{ | 
						|
					return true; | 
						|
				} | 
						|
				// if (call PrintMembers(ldloc this, ldloc builder)) Block IL_000f { | 
						|
				//   callvirt Append(ldloc builder, ldstr ", ") | 
						|
				// } | 
						|
				if (!body.Instructions[pos].MatchIfInstruction(out var condition, out var trueInst)) | 
						|
					return false; | 
						|
				if (!IsBaseCall(condition)) | 
						|
					return false; | 
						|
				// trueInst = callvirt Append(ldloc builder, ldstr ", ") | 
						|
				trueInst = Block.Unwrap(trueInst); | 
						|
				if (!MatchStringBuilderAppend(trueInst, builder, out var val)) | 
						|
					return false; | 
						|
				if (!(val.MatchLdStr(out string text) && text == ", ")) | 
						|
					return false; | 
						|
				pos++; | 
						|
 | 
						|
				bool IsBaseCall(ILInstruction inst) | 
						|
				{ | 
						|
					if (!(inst is CallInstruction { Method: { Name: "PrintMembers" } } call)) | 
						|
						return false; | 
						|
					if (call.Arguments.Count != 2) | 
						|
						return false; | 
						|
					if (!call.Arguments[0].MatchLdThis()) | 
						|
						return false; | 
						|
					if (!call.Arguments[1].MatchLdLoc(builder)) | 
						|
						return false; | 
						|
					return true; | 
						|
				} | 
						|
			} | 
						|
			bool needsComma = false; | 
						|
			foreach (var member in orderedMembers) | 
						|
			{ | 
						|
				if (!IsPrintedMember(member)) | 
						|
					continue; | 
						|
				cancellationToken.ThrowIfCancellationRequested(); | 
						|
				/*  | 
						|
				callvirt Append(ldloc builder, ldstr "A") | 
						|
				callvirt Append(ldloc builder, ldstr " = ") | 
						|
				callvirt Append(ldloc builder, constrained[System.Int32].callvirt ToString(addressof System.Int32(call get_A(ldloc this)))) | 
						|
				callvirt Append(ldloc builder, ldstr ", ") | 
						|
				callvirt Append(ldloc builder, ldstr "B") | 
						|
				callvirt Append(ldloc builder, ldstr " = ") | 
						|
				callvirt Append(ldloc builder, constrained[System.Int32].callvirt ToString(ldflda B(ldloc this))) | 
						|
				leave IL_0000 (ldc.i4 1) */ | 
						|
				if (!MatchStringBuilderAppendConstant(out string text)) | 
						|
					return false; | 
						|
				string expectedText = (needsComma ? ", " : "") + member.Name + " = "; | 
						|
				if (text != expectedText) | 
						|
					return false; | 
						|
				if (!MatchStringBuilderAppend(body.Instructions[pos], builder, out var val)) | 
						|
					return false; | 
						|
				if (val is CallInstruction { Method: { Name: "ToString", IsStatic: false } } toStringCall) | 
						|
				{ | 
						|
					if (toStringCall.Arguments.Count != 1) | 
						|
						return false; | 
						|
					val = toStringCall.Arguments[0]; | 
						|
					if (val is AddressOf addressOf) | 
						|
					{ | 
						|
						val = addressOf.Value; | 
						|
					} | 
						|
				} | 
						|
				else if (val is Box box) | 
						|
				{ | 
						|
					if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(box.Type, member.ReturnType)) | 
						|
						return false; | 
						|
					val = box.Argument; | 
						|
				} | 
						|
				if (val is CallInstruction getterCall && member is IProperty property) | 
						|
				{ | 
						|
					if (!getterCall.Method.Equals(property.Getter)) | 
						|
						return false; | 
						|
					if (getterCall.Arguments.Count != 1) | 
						|
						return false; | 
						|
					if (!getterCall.Arguments[0].MatchLdThis()) | 
						|
						return false; | 
						|
				} | 
						|
				else if (val.MatchLdFld(out var target, out var field) || val.MatchLdFlda(out target, out field)) | 
						|
				{ | 
						|
					if (!target.MatchLdThis()) | 
						|
						return false; | 
						|
					if (!field.Equals(member)) | 
						|
						return false; | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					return false; | 
						|
				} | 
						|
				pos++; | 
						|
				needsComma = true; | 
						|
			} | 
						|
			// leave IL_0000 (ldc.i4 1) | 
						|
			return body.Instructions[pos].MatchReturn(out var retVal) | 
						|
				&& retVal.MatchLdcI4(needsComma ? 1 : 0); | 
						|
 | 
						|
			bool IsPrintedMember(IMember member) | 
						|
			{ | 
						|
				if (member.IsStatic) | 
						|
				{ | 
						|
					return false; // static fields/properties are not printed | 
						|
				} | 
						|
				if (!isStruct && member.Name == "EqualityContract") | 
						|
				{ | 
						|
					return false; // EqualityContract is never printed | 
						|
				} | 
						|
				if (member.IsExplicitInterfaceImplementation) | 
						|
				{ | 
						|
					return false; // explicit interface impls are not printed | 
						|
				} | 
						|
				if (member.IsOverride) | 
						|
				{ | 
						|
					return false; // override is not printed (again), the virtual base property was already printed | 
						|
				} | 
						|
				return true; | 
						|
			} | 
						|
 | 
						|
			bool MatchStringBuilderAppendConstant(out string text) | 
						|
			{ | 
						|
				text = null; | 
						|
				while (MatchStringBuilderAppend(body.Instructions[pos], builder, out var val) && val.MatchLdStr(out string valText)) | 
						|
				{ | 
						|
					text += valText; | 
						|
					pos++; | 
						|
				} | 
						|
				return text != null; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		private bool MatchStringBuilderAppend(ILInstruction inst, ILVariable sb, out ILInstruction val) | 
						|
		{ | 
						|
			val = null; | 
						|
			if (!(inst is CallVirt { Method: { Name: "Append", DeclaringType: { Namespace: "System.Text", Name: "StringBuilder" } } } call)) | 
						|
				return false; | 
						|
			if (call.Arguments.Count != 2) | 
						|
				return false; | 
						|
			if (!call.Arguments[0].MatchLdLoc(sb)) | 
						|
				return false; | 
						|
			val = call.Arguments[1]; | 
						|
			return true; | 
						|
		} | 
						|
 | 
						|
		private bool IsGeneratedToString(IMethod method) | 
						|
		{ | 
						|
			Debug.Assert(method.Name == "ToString" && method.Parameters.Count == 0); | 
						|
			if (!method.IsOverride) | 
						|
				return false; | 
						|
			if (method.IsSealed) | 
						|
				return false; | 
						|
			if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null) | 
						|
				return false; | 
						|
			// stloc stringBuilder(newobj StringBuilder..ctor()) | 
						|
			if (!body.Instructions[0].MatchStLoc(out var stringBuilder, out var stringBuilderInit)) | 
						|
				return false; | 
						|
			if (!(stringBuilderInit is NewObj { Arguments: { Count: 0 }, Method: { DeclaringTypeDefinition: { Name: "StringBuilder", Namespace: "System.Text" } } })) | 
						|
				return false; | 
						|
			// callvirt Append(ldloc stringBuilder, ldstr "R") | 
						|
			if (!MatchAppendCallWithValue(body.Instructions[1], recordTypeDef.Name)) | 
						|
				return false; | 
						|
			// callvirt Append(ldloc stringBuilder, ldstr " { ") | 
						|
			if (!MatchAppendCallWithValue(body.Instructions[2], " { ")) | 
						|
				return false; | 
						|
			// if (callvirt PrintMembers(ldloc this, ldloc stringBuilder)) { trueInst } | 
						|
			if (!body.Instructions[3].MatchIfInstruction(out var condition, out var trueInst)) | 
						|
				return true; | 
						|
			if (!((condition is CallInstruction { Method: { Name: "PrintMembers" } } printMembersCall) && | 
						|
				(condition is CallVirt || (isSealed && condition is Call)))) | 
						|
				return false; | 
						|
			if (printMembersCall.Arguments.Count != 2) | 
						|
				return false; | 
						|
			if (!printMembersCall.Arguments[0].MatchLdThis()) | 
						|
				return false; | 
						|
			if (!printMembersCall.Arguments[1].MatchLdLoc(stringBuilder)) | 
						|
				return false; | 
						|
			// trueInst: callvirt Append(ldloc stringBuilder, ldstr " ") | 
						|
			if (!MatchAppendCallWithValue(Block.Unwrap(trueInst), " ")) | 
						|
				return false; | 
						|
			// callvirt Append(ldloc stringBuilder, ldstr "}") | 
						|
			if (!MatchAppendCallWithValue(body.Instructions[4], "}")) | 
						|
				return false; | 
						|
			// leave IL_0000 (callvirt ToString(ldloc stringBuilder)) | 
						|
			if (!(body.Instructions[5] is Leave leave)) | 
						|
				return false; | 
						|
			if (!(leave.Value is CallVirt { Method: { Name: "ToString" } } toStringCall)) | 
						|
				return false; | 
						|
			if (toStringCall.Arguments.Count != 1) | 
						|
				return false; | 
						|
			return toStringCall.Arguments[0].MatchLdLoc(stringBuilder); | 
						|
 | 
						|
			bool MatchAppendCallWithValue(ILInstruction inst, string val) | 
						|
			{ | 
						|
				if (!(inst is CallVirt { Method: { Name: "Append" } } call)) | 
						|
					return false; | 
						|
				if (call.Arguments.Count != 2) | 
						|
					return false; | 
						|
				if (!call.Arguments[0].MatchLdLoc(stringBuilder)) | 
						|
					return false; | 
						|
				//Roslyn 4.0.0-3.final start to use char for 1 length string | 
						|
				if (call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Char)) | 
						|
				{ | 
						|
					return val != null && val.Length == 1 && call.Arguments[1].MatchLdcI4(val[0]); | 
						|
				} | 
						|
				return call.Arguments[1].MatchLdStr(out string val1) && val1 == val; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		private bool IsGeneratedEquals(IMethod method) | 
						|
		{ | 
						|
			// virtual bool Equals(R? other) { | 
						|
			//    return other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(A, other.A) && ...; | 
						|
			// } | 
						|
			// Starting with Roslyn 3.10, it's: | 
						|
			// virtual bool Equals(R? other) { | 
						|
			//    return this == other || other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(A, other.A) && ...; | 
						|
			// } | 
						|
			Debug.Assert(method.Name == "Equals" && method.Parameters.Count == 1); | 
						|
			if (method.Parameters.Count != 1) | 
						|
				return false; | 
						|
			if (!isSealed && !method.IsOverridable) | 
						|
				return false; | 
						|
			if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			if (orderedMembers == null) | 
						|
				return false; | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null) | 
						|
				return false; | 
						|
			if (!body.Instructions[0].MatchReturn(out var returnValue)) | 
						|
				return false; | 
						|
			// special case for empty record struct; always returns true; | 
						|
			if (returnValue.MatchLdcI4(1)) | 
						|
				return true; | 
						|
			var variables = body.Ancestors.OfType<ILFunction>().Single().Variables; | 
						|
			var other = variables.Single(v => v.Kind == VariableKind.Parameter && v.Index == 0); | 
						|
			Debug.Assert(IsRecordType(other.Type)); | 
						|
			if (returnValue.MatchLogicOr(out var lhs, out var rhs)) | 
						|
			{ | 
						|
				// this == other || ... | 
						|
				if (!lhs.MatchCompEquals(out var compLeft, out var compRight)) | 
						|
					return false; | 
						|
				if (!compLeft.MatchLdThis()) | 
						|
					return false; | 
						|
				if (!compRight.MatchLdLoc(other)) | 
						|
					return false; | 
						|
				returnValue = rhs; | 
						|
			} | 
						|
			var conditions = UnpackLogicAndChain(returnValue); | 
						|
			Debug.Assert(conditions.Count >= 1); | 
						|
			int pos = 0; | 
						|
			if (!isStruct) | 
						|
			{ | 
						|
				if (isInheritedRecord) | 
						|
				{ | 
						|
					// call BaseClass::Equals(ldloc this, ldloc other) | 
						|
					if (pos >= conditions.Count) | 
						|
						return false; | 
						|
					if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call)) | 
						|
						return false; | 
						|
					if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass)) | 
						|
						return false; | 
						|
					if (call.Arguments.Count != 2) | 
						|
						return false; | 
						|
					if (!call.Arguments[0].MatchLdThis()) | 
						|
						return false; | 
						|
					if (!call.Arguments[1].MatchLdLoc(other)) | 
						|
						return false; | 
						|
					pos++; | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					// comp.o(ldloc other != ldnull) | 
						|
					if (pos >= conditions.Count) | 
						|
						return false; | 
						|
					if (!conditions[pos].MatchCompNotEqualsNull(out var arg)) | 
						|
						return false; | 
						|
					if (!arg.MatchLdLoc(other)) | 
						|
						return false; | 
						|
					pos++; | 
						|
					// call op_Equality(callvirt get_EqualityContract(ldloc this), callvirt get_EqualityContract(ldloc other)) | 
						|
					// Special-cased because Roslyn isn't using EqualityComparer<T> here. | 
						|
					if (pos >= conditions.Count) | 
						|
						return false; | 
						|
					if (!(conditions[pos] is Call { Method: { IsOperator: true, Name: "op_Equality" } } opEqualityCall)) | 
						|
						return false; | 
						|
					if (!opEqualityCall.Method.DeclaringType.IsKnownType(KnownTypeCode.Type)) | 
						|
						return false; | 
						|
					if (opEqualityCall.Arguments.Count != 2) | 
						|
						return false; | 
						|
					if (!MatchGetEqualityContract(opEqualityCall.Arguments[0], out var target1)) | 
						|
						return false; | 
						|
					if (!MatchGetEqualityContract(opEqualityCall.Arguments[1], out var target2)) | 
						|
						return false; | 
						|
					if (!target1.MatchLdThis()) | 
						|
						return false; | 
						|
					if (!target2.MatchLdLoc(other)) | 
						|
						return false; | 
						|
					pos++; | 
						|
				} | 
						|
			} | 
						|
			foreach (var member in orderedMembers) | 
						|
			{ | 
						|
				if (!MemberConsideredForEquality(member)) | 
						|
					continue; | 
						|
				if (!isStruct && member.Name == "EqualityContract") | 
						|
				{ | 
						|
					continue; // already special-cased | 
						|
				} | 
						|
				// EqualityComparer<int>.Default.Equals(A, other.A) | 
						|
				// callvirt Equals(call get_Default(), ldfld <A>k__BackingField(ldloc this), ldfld <A>k__BackingField(ldloc other)) | 
						|
				if (pos >= conditions.Count) | 
						|
					return false; | 
						|
				if (!(conditions[pos] is CallVirt { Method: { Name: "Equals" } } equalsCall)) | 
						|
					return false; | 
						|
				if (equalsCall.Arguments.Count != 3) | 
						|
					return false; | 
						|
				if (!IsEqualityComparerGetDefaultCall(equalsCall.Arguments[0], member.ReturnType)) | 
						|
					return false; | 
						|
				if (!MatchMemberAccess(equalsCall.Arguments[1], out var target1, out var member1)) | 
						|
					return false; | 
						|
				if (!MatchMemberAccess(equalsCall.Arguments[2], out var target2, out var member2)) | 
						|
					return false; | 
						|
				if (!target1.MatchLdThis()) | 
						|
					return false; | 
						|
				if (!member1.Equals(member)) | 
						|
					return false; | 
						|
				if (!(isStruct ? target2.MatchLdLoca(other) : target2.MatchLdLoc(other))) | 
						|
					return false; | 
						|
				if (!member2.Equals(member)) | 
						|
					return false; | 
						|
				pos++; | 
						|
			} | 
						|
			return pos == conditions.Count; | 
						|
		} | 
						|
 | 
						|
		static List<ILInstruction> UnpackLogicAndChain(ILInstruction rootOfChain) | 
						|
		{ | 
						|
			var result = new List<ILInstruction>(); | 
						|
			Visit(rootOfChain); | 
						|
			return result; | 
						|
 | 
						|
			void Visit(ILInstruction inst) | 
						|
			{ | 
						|
				if (inst.MatchLogicAnd(out var lhs, out var rhs)) | 
						|
				{ | 
						|
					Visit(lhs); | 
						|
					Visit(rhs); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					result.Add(inst); | 
						|
				} | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		private bool MatchGetEqualityContract(ILInstruction inst, out ILInstruction target) | 
						|
		{ | 
						|
			target = null; | 
						|
			if (!(inst is CallInstruction { Method: { Name: "get_EqualityContract" } } call)) | 
						|
				return false; | 
						|
			if (!(inst is CallVirt || (isSealed && inst is Call))) | 
						|
				return false; | 
						|
			if (call.Arguments.Count != 1) | 
						|
				return false; | 
						|
			target = call.Arguments[0]; | 
						|
			return true; | 
						|
		} | 
						|
 | 
						|
		private static bool IsEqualityComparerGetDefaultCall(ILInstruction inst, IType type) | 
						|
		{ | 
						|
			if (!(inst is Call { Method: { Name: "get_Default", IsStatic: true } } call)) | 
						|
				return false; | 
						|
			if (!(call.Method.DeclaringType is { Name: "EqualityComparer", Namespace: "System.Collections.Generic" })) | 
						|
				return false; | 
						|
			if (call.Method.DeclaringType.TypeArguments.Count != 1) | 
						|
				return false; | 
						|
			if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType.TypeArguments[0], type)) | 
						|
				return false; | 
						|
			return call.Arguments.Count == 0; | 
						|
		} | 
						|
 | 
						|
		bool MemberConsideredForEquality(IMember member) | 
						|
		{ | 
						|
			if (member.IsStatic) | 
						|
				return false; | 
						|
			if (member is IProperty property) | 
						|
			{ | 
						|
				if (!isStruct && property.Name == "EqualityContract") | 
						|
					return !isInheritedRecord; | 
						|
				return autoPropertyToBackingField.ContainsKey(property); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				return member is IField; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		bool IsGeneratedGetHashCode(IMethod method) | 
						|
		{ | 
						|
			/* | 
						|
			 return ( | 
						|
				( | 
						|
					EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(A) | 
						|
				) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(B) | 
						|
			 ) * -1521134295 + EqualityComparer<object>.Default.GetHashCode(C); | 
						|
			*/ | 
						|
			Debug.Assert(method.Name == "GetHashCode"); | 
						|
			if (method.Parameters.Count != 0) | 
						|
				return false; | 
						|
			if (!method.IsOverride || method.IsSealed) | 
						|
				return false; | 
						|
			if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) | 
						|
				return false; | 
						|
			if (orderedMembers == null) | 
						|
				return false; | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null) | 
						|
				return false; | 
						|
			if (!body.Instructions[0].MatchReturn(out var returnValue)) | 
						|
				return false; | 
						|
			// special case for empty record struct; always returns false; | 
						|
			if (returnValue.MatchLdcI4(0)) | 
						|
				return true; | 
						|
			var hashedMembers = new List<IMember>(); | 
						|
			bool foundBaseClassHash = false; | 
						|
			if (!Visit(returnValue)) | 
						|
				return false; | 
						|
			if (foundBaseClassHash != isInheritedRecord) | 
						|
				return false; | 
						|
			return orderedMembers.Where(MemberConsideredForEquality).SequenceEqual(hashedMembers); | 
						|
 | 
						|
			bool Visit(ILInstruction inst) | 
						|
			{ | 
						|
				if (inst is BinaryNumericInstruction { | 
						|
					Operator: BinaryNumericOperator.Add, | 
						|
					CheckForOverflow: false, | 
						|
					Left: BinaryNumericInstruction { | 
						|
						Operator: BinaryNumericOperator.Mul, | 
						|
						CheckForOverflow: false, | 
						|
						Left: var left, | 
						|
						Right: LdcI4 { Value: -1521134295 } | 
						|
					}, | 
						|
					Right: var right | 
						|
				}) | 
						|
				{ | 
						|
					if (!Visit(left)) | 
						|
						return false; | 
						|
					return ProcessIndividualHashCode(right); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					return ProcessIndividualHashCode(inst); | 
						|
				} | 
						|
			} | 
						|
 | 
						|
			bool ProcessIndividualHashCode(ILInstruction inst) | 
						|
			{ | 
						|
				// base.GetHashCode(): call GetHashCode(ldloc this) | 
						|
				if (inst is Call { Method: { Name: "GetHashCode" } } baseHashCodeCall) | 
						|
				{ | 
						|
					if (baseHashCodeCall.Arguments.Count != 1) | 
						|
						return false; | 
						|
					if (!baseHashCodeCall.Arguments[0].MatchLdThis()) | 
						|
						return false; | 
						|
					if (foundBaseClassHash || hashedMembers.Count > 0) | 
						|
						return false; // must be first | 
						|
					foundBaseClassHash = true; | 
						|
					return baseHashCodeCall.Method.DeclaringType.Equals(baseClass); | 
						|
				} | 
						|
				// callvirt GetHashCode(call get_Default(), callvirt get_EqualityContract(ldloc this)) | 
						|
				// callvirt GetHashCode(call get_Default(), ldfld <A>k__BackingField(ldloc this))) | 
						|
				if (!(inst is CallVirt { Method: { Name: "GetHashCode" } } getHashCodeCall)) | 
						|
					return false; | 
						|
				if (getHashCodeCall.Arguments.Count != 2) | 
						|
					return false; | 
						|
				// getHashCodeCall.Arguments[0] checked later | 
						|
				if (!MatchMemberAccess(getHashCodeCall.Arguments[1], out var target, out var member)) | 
						|
					return false; | 
						|
				if (!target.MatchLdThis()) | 
						|
					return false; | 
						|
				if (!IsEqualityComparerGetDefaultCall(getHashCodeCall.Arguments[0], member.ReturnType)) | 
						|
					return false; | 
						|
				hashedMembers.Add(member); | 
						|
				return true; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		bool IsGeneratedDeconstruct(IMethod method) | 
						|
		{ | 
						|
			Debug.Assert(method.Name == "Deconstruct" && method.Parameters.Count == primaryCtor.Parameters.Count); | 
						|
 | 
						|
			if (!method.ReturnType.IsKnownType(KnownTypeCode.Void)) | 
						|
				return false; | 
						|
 | 
						|
			for (int i = 0; i < method.Parameters.Count; i++) | 
						|
			{ | 
						|
				var deconstruct = method.Parameters[i]; | 
						|
				var ctor = primaryCtor.Parameters[i]; | 
						|
 | 
						|
				if (!deconstruct.IsOut) | 
						|
					return false; | 
						|
 | 
						|
				IType ctorType = ctor.Type; | 
						|
				if (ctor.IsIn) | 
						|
					ctorType = ((ByReferenceType)ctorType).ElementType; | 
						|
				if (!ctorType.Equals(((ByReferenceType)deconstruct.Type).ElementType)) | 
						|
					return false; | 
						|
 | 
						|
				if (ctor.Name != deconstruct.Name) | 
						|
					return false; | 
						|
			} | 
						|
 | 
						|
			var body = DecompileBody(method); | 
						|
			if (body == null || body.Instructions.Count != method.Parameters.Count + 1) | 
						|
				return false; | 
						|
 | 
						|
			for (int i = 0; i < body.Instructions.Count - 1; i++) | 
						|
			{ | 
						|
				// stobj T(ldloc parameter, call getter(ldloc this)) | 
						|
				if (!body.Instructions[i].MatchStObj(out var targetInst, out var getter, out _)) | 
						|
					return false; | 
						|
				if (!targetInst.MatchLdLoc(out var target)) | 
						|
					return false; | 
						|
				if (!(target.Kind == VariableKind.Parameter && target.Index == i)) | 
						|
					return false; | 
						|
 | 
						|
				if (getter is not Call call || call.Arguments.Count != 1) | 
						|
					return false; | 
						|
				if (!call.Arguments[0].MatchLdThis()) | 
						|
					return false; | 
						|
 | 
						|
				if (!call.Method.IsAccessor) | 
						|
					return false; | 
						|
				var autoProperty = (IProperty)call.Method.AccessorOwner; | 
						|
				if (!autoPropertyToBackingField.ContainsKey(autoProperty)) | 
						|
					return false; | 
						|
			} | 
						|
 | 
						|
			var returnInst = body.Instructions.LastOrDefault(); | 
						|
			return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop(); | 
						|
		} | 
						|
 | 
						|
		bool MatchMemberAccess(ILInstruction inst, out ILInstruction target, out IMember member) | 
						|
		{ | 
						|
			target = null; | 
						|
			member = null; | 
						|
			if (inst is CallInstruction { | 
						|
				Method: { | 
						|
					AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter, | 
						|
					AccessorOwner: IProperty property | 
						|
				} | 
						|
			} call && (call is CallVirt || (isSealed && call is Call))) | 
						|
			{ | 
						|
				if (call.Arguments.Count != 1) | 
						|
					return false; | 
						|
				target = call.Arguments[0]; | 
						|
				member = property; | 
						|
				return true; | 
						|
			} | 
						|
			else if (inst.MatchLdFld(out target, out IField field)) | 
						|
			{ | 
						|
				if (backingFieldToAutoProperty.TryGetValue(field, out property)) | 
						|
					member = property; | 
						|
				else | 
						|
					member = field; | 
						|
				return true; | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				return false; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		Block DecompileBody(IMethod method) | 
						|
		{ | 
						|
			if (method == null || method.MetadataToken.IsNil) | 
						|
				return null; | 
						|
			var metadata = typeSystem.MainModule.metadata; | 
						|
 | 
						|
			var methodDefHandle = (MethodDefinitionHandle)method.MetadataToken; | 
						|
			var methodDef = metadata.GetMethodDefinition(methodDefHandle); | 
						|
			if (!methodDef.HasBody()) | 
						|
				return null; | 
						|
 | 
						|
			var genericContext = new GenericContext( | 
						|
				classTypeParameters: recordTypeDef.TypeParameters, | 
						|
				methodTypeParameters: null); | 
						|
			var body = typeSystem.MainModule.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); | 
						|
			var ilReader = new ILReader(typeSystem.MainModule); | 
						|
			var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken); | 
						|
			var settings = new DecompilerSettings(LanguageVersion.CSharp1); | 
						|
			var transforms = CSharpDecompiler.GetILTransforms(); | 
						|
			// Remove the last couple transforms -- we don't need variable names etc. here | 
						|
			int lastBlockTransform = transforms.FindLastIndex(t => t is BlockILTransform); | 
						|
			transforms.RemoveRange(lastBlockTransform + 1, transforms.Count - (lastBlockTransform + 1)); | 
						|
			// Use CombineExitsTransform so that "return other != null && ...;" is a single statement even in release builds | 
						|
			transforms.Add(new CombineExitsTransform()); | 
						|
			il.RunTransforms(transforms, | 
						|
				new ILTransformContext(il, typeSystem, debugInfo: null, settings) { | 
						|
					CancellationToken = cancellationToken | 
						|
				}); | 
						|
			if (il.Body is BlockContainer container) | 
						|
			{ | 
						|
				return container.EntryPoint; | 
						|
			} | 
						|
			else if (il.Body is Block block) | 
						|
			{ | 
						|
				return block; | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				return null; | 
						|
			} | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |