using System; 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 CancellationToken cancellationToken; public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, CancellationToken cancellationToken) { this.typeSystem = dts; this.recordTypeDef = recordTypeDef; this.cancellationToken = cancellationToken; } bool IsRecordType(IType type) { return type.GetDefinition() == recordTypeDef && type.TypeArguments.SequenceEqual(recordTypeDef.TypeParameters); } /// /// Gets whether the member of the record type will be automatically generated by the compiler. /// public bool MethodIsGenerated(IMethod 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)) { // override bool Equals(object? obj): always generated return true; } else if (IsRecordType(paramType)) { // virtual bool Equals(R? other): generated unless user-declared return false; } else { return false; } } case "$" when method.Parameters.Count == 0: // Always generated; Method name cannot be expressed in C# return true; default: return false; } } internal bool PropertyIsGenerated(IProperty property) { switch (property.Name) { case "EqualityContract": return IsGeneratedEqualityContract(property); default: return false; } } private bool IsGeneratedEqualityContract(IProperty property) { // Generated member: // protected virtual Type EqualityContract { // [CompilerGenerated] get => typeof(R); // } Debug.Assert(property.Name == "EqualityContract"); if (property.Accessibility != Accessibility.Protected) return false; if (!(property.IsVirtual || property.IsOverride)) return false; var getter = property.Getter; if (!(getter != null && !property.CanSet)) return false; if (property.GetAttributes().Any()) return false; if (getter.GetReturnTypeAttributes().Any()) return false; var 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?.SingleInstruction() 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); } BlockContainer 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.CSharp2); il.RunTransforms(CSharpDecompiler.EarlyILTransforms(), new ILTransformContext(il, typeSystem, debugInfo: null, settings) { CancellationToken = cancellationToken }); return (BlockContainer)il.Body; } } }