Browse Source

Omit EqualityContract if it's automatically generated.

pull/2251/head
Daniel Grunwald 5 years ago
parent
commit
90ce77f400
  1. 30
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 145
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

30
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -1169,6 +1169,7 @@ namespace ICSharpCode.Decompiler.CSharp
return entityDecl; return entityDecl;
} }
bool isRecord = settings.RecordClasses && typeDef.IsRecord; bool isRecord = settings.RecordClasses && typeDef.IsRecord;
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, CancellationToken) : null;
foreach (var type in typeDef.NestedTypes) foreach (var type in typeDef.NestedTypes)
{ {
if (!type.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, type.MetadataToken, settings)) if (!type.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, type.MetadataToken, settings))
@ -1190,6 +1191,10 @@ namespace ICSharpCode.Decompiler.CSharp
} }
foreach (var property in typeDef.Properties) foreach (var property in typeDef.Properties)
{ {
if (recordDecompiler?.PropertyIsGenerated(property) == true)
{
continue;
}
if (!property.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, property.MetadataToken, settings)) if (!property.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, property.MetadataToken, settings))
{ {
var propDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property)); var propDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property));
@ -1206,30 +1211,9 @@ namespace ICSharpCode.Decompiler.CSharp
} }
foreach (var method in typeDef.Methods) foreach (var method in typeDef.Methods)
{ {
if (isRecord) if (recordDecompiler?.MethodIsGenerated(method) == true)
{ {
// Some members in records are always compiler-generated and lead to a continue;
// "duplicate definition" error if we emit the generated code.
if ((method.Name == "op_Equality" || method.Name == "op_Inequality")
&& method.Parameters.Count == 2
&& method.Parameters.All(p => p.Type.GetDefinition() == typeDef))
{
// Don't emit comparison operators into C# record definition
// Note: user can declare additional operator== as long as they have
// different parameter types.
continue;
}
if (method.Name == "Equals" && method.Parameters.Count == 1 && method.Parameters[0].Type.IsKnownType(KnownTypeCode.Object))
{
// override bool Equals(object? obj)
// Note: the "virtual bool Equals(R? other)" overload can be customized
continue;
}
if (method.Name == "<Clone>$" && method.Parameters.Count == 0)
{
// Method name cannot be expressed in C#
continue;
}
} }
if (!method.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, method.MetadataToken, settings)) if (!method.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, method.MetadataToken, settings))
{ {

145
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -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;
}
}
}

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -67,6 +67,7 @@
<Compile Include="CSharp\ProjectDecompiler\IProjectInfoProvider.cs" /> <Compile Include="CSharp\ProjectDecompiler\IProjectInfoProvider.cs" />
<Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterSdkStyle.cs" /> <Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterSdkStyle.cs" />
<Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterDefault.cs" /> <Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterDefault.cs" />
<Compile Include="CSharp\RecordDecompiler.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" /> <Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" /> <Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetFramework.cs" /> <Compile Include="CSharp\ProjectDecompiler\TargetFramework.cs" />

Loading…
Cancel
Save