diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index c27edcc72..96e96cf66 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -1,4 +1,6 @@ -namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { internal class RefLocalsAndReturns { @@ -12,9 +14,68 @@ private readonly int dummy; } + public struct NormalStruct + { + private readonly int dummy; + + public void Method() + { + } + } + public readonly struct ReadOnlyStruct { private readonly int dummy; + + public void Method() + { + } + } + + public static ref T GetRef() + { + throw new NotImplementedException(); + } + + public static ref readonly T GetReadonlyRef() + { + throw new NotImplementedException(); + } + + public void CallOnRefReturn() + { + // Both direct calls: + GetRef().Method(); + GetRef().Method(); + + // call on a copy, not the original ref: + NormalStruct @ref = GetRef(); + @ref.Method(); + + ReadOnlyStruct ref2 = GetRef(); + ref2.Method(); + } + + public void CallOnReadOnlyRefReturn() + { + // uses implicit temporary: + GetReadonlyRef().Method(); + // direct call: + GetReadonlyRef().Method(); + // call on a copy, not the original ref: + ReadOnlyStruct readonlyRef = GetReadonlyRef(); + readonlyRef.Method(); + } + + public void CallOnInParam(in NormalStruct ns, in ReadOnlyStruct rs) + { + // uses implicit temporary: + ns.Method(); + // direct call: + rs.Method(); + // call on a copy, not the original ref: + ReadOnlyStruct readOnlyStruct = rs; + readOnlyStruct.Method(); } } } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index ed815dbef..729ca4b9e 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2322,6 +2322,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor if (composedType.HasRefSpecifier) { WriteKeyword(ComposedType.RefRole); } + if (composedType.HasReadOnlySpecifier) { + WriteKeyword(ComposedType.ReadonlyRole); + } composedType.BaseType.AcceptVisitor(this); if (composedType.HasNullableSpecifier) { WriteToken(ComposedType.NullableRole); diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs b/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs index ced22e011..185e626c0 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs @@ -232,6 +232,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); + bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; public bool IsStatic { get { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/ComposedType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/ComposedType.cs index 67c16913f..f4b768ad2 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/ComposedType.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/ComposedType.cs @@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public class ComposedType : AstType { public static readonly TokenRole RefRole = new TokenRole("ref"); + public static readonly TokenRole ReadonlyRole = new TokenRole("readonly"); public static readonly TokenRole NullableRole = new TokenRole("?"); public static readonly TokenRole PointerRole = new TokenRole("*"); public static readonly Role ArraySpecifierRole = new Role("ArraySpecifier"); @@ -54,6 +55,20 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + /// + /// Gets/sets whether this type has a 'readonly' specifier. + /// This is used for C# 7.2 'ref readonly' locals/ref return. + /// Parameters use ParameterDeclaration.ParameterModifier instead. + /// + public bool HasReadOnlySpecifier { + get { + return !GetChildByRole(ReadonlyRole).IsNull; + } + set { + SetChildByRole(ReadonlyRole, value ? new CSharpTokenNode(TextLocation.Empty, null) : null); + } + } + public AstType BaseType { get { return GetChildByRole(Roles.Type); } set { SetChildByRole(Roles.Type, value); } @@ -123,6 +138,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax && this.HasNullableSpecifier == o.HasNullableSpecifier && this.PointerRank == o.PointerRank && this.HasRefSpecifier == o.HasRefSpecifier + && this.HasReadOnlySpecifier == o.HasReadOnlySpecifier && this.BaseType.DoMatch(o.BaseType, match) && this.ArraySpecifiers.DoMatch(o.ArraySpecifiers, match); } @@ -132,6 +148,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax StringBuilder b = new StringBuilder(); if (this.HasRefSpecifier) b.Append("ref "); + if (this.HasReadOnlySpecifier) + b.Append("readonly "); b.Append(this.BaseType.ToString()); if (this.HasNullableSpecifier) b.Append('?'); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 85a2e1158..a0e650c85 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1602,6 +1602,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.AddAnnotation(new MemberResolveResult(null, method)); } decl.ReturnType = ConvertType(method.ReturnType); + if (method.ReturnTypeIsRefReadOnly && decl.ReturnType is ComposedType ct && ct.HasRefSpecifier) { + ct.HasReadOnlySpecifier = true; + } decl.Name = method.Name; if (this.ShowTypeParameters) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index de131891b..0625fb99f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -366,7 +366,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case LdLoc ldloc: return IsReadonlyRefLocal(ldloc.Variable); case Call call: - // TODO: calls with 'readonly ref' return + return call.Method.ReturnTypeIsRefReadOnly; default: return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index 24f550b5d..64c8b3303 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -35,6 +35,11 @@ namespace ICSharpCode.Decompiler.TypeSystem /// IEnumerable GetReturnTypeAttributes(); + /// + /// Gets whether the return type is 'ref readonly'. + /// + bool ReturnTypeIsRefReadOnly { get; } + /// /// Gets the type parameters of this method; or an empty list if the method is not generic. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index 54c8e6f26..02a84fa79 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -132,6 +132,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override SymbolKind SymbolKind => symbolKind; IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance; + bool IMethod.ReturnTypeIsRefReadOnly => false; public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index a3412cc4f..9cb6a308e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -47,6 +47,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation string name; IParameter[] parameters; IType returnType; + byte returnTypeIsRefReadonly = ThreeState.Unknown; internal MetadataMethod(MetadataModule module, MethodDefinitionHandle handle) { @@ -203,6 +204,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation i++; } Debug.Assert(i == parameters.Length); + bool isRefReadonly = false; + if (signature.ReturnType.Kind == TypeKind.ModReq && signature.ReturnType.SkipModifiers().Kind == TypeKind.ByReference) { + isRefReadonly = ((ModifiedType)signature.ReturnType).Modifier.IsKnownType(KnownAttribute.In); + } var returnType = ApplyAttributeTypeVisitor.ApplyAttributesToType(signature.ReturnType, module.Compilation, returnTypeAttributes, metadata, module.TypeSystemOptions); return (returnType, parameters); @@ -387,6 +392,26 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } return b.Build(); } + + public bool ReturnTypeIsRefReadOnly { + get { + if (returnTypeIsRefReadonly != ThreeState.Unknown) { + return returnTypeIsRefReadonly == ThreeState.True; + } + var metadata = module.metadata; + var methodDefinition = metadata.GetMethodDefinition(handle); + var parameters = methodDefinition.GetParameters(); + bool hasReadOnlyAttr = false; + if (parameters.Count > 0) { + var retParam = metadata.GetParameter(parameters.First()); + if (retParam.SequenceNumber == 0) { + hasReadOnlyAttr = retParam.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.IsReadOnly); + } + } + this.returnTypeIsRefReadonly = ThreeState.From(hasReadOnlyAttr); + return hasReadOnlyAttr; + } + } #endregion public Accessibility Accessibility => GetAccessibility(attributes); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index 3decf1e0f..8f9937d87 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -95,7 +95,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } public IEnumerable GetReturnTypeAttributes() => methodDefinition.GetReturnTypeAttributes(); - + public bool ReturnTypeIsRefReadOnly => methodDefinition.ReturnTypeIsRefReadOnly; + public IReadOnlyList TypeParameters { get { return specializedTypeParameters ?? methodDefinition.TypeParameters; diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index dc12f9423..0356f544a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -113,6 +113,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); + bool IMethod.ReturnTypeIsRefReadOnly => baseMethod.ReturnTypeIsRefReadOnly; public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; }