mirror of https://github.com/icsharpcode/ILSpy.git
3 changed files with 153 additions and 23 deletions
@ -0,0 +1,145 @@ |
|||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the member of the record type will be automatically generated by the compiler.
|
||||||
|
/// </summary>
|
||||||
|
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 "<Clone>$" 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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue