Skip to content

Commit

Permalink
Add support for C# 11 scoped parameter modifier.
Browse files Browse the repository at this point in the history
  • Loading branch information
siegfriedpammer committed Jul 16, 2022
1 parent 5f324de commit 2ed9ad6
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 14 deletions.
10 changes: 10 additions & 0 deletions ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2565,6 +2565,11 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl
WriteKeyword(ParameterDeclaration.ThisModifierRole);
Space();
}
if (parameterDeclaration.IsRefScoped)
{
WriteKeyword(ParameterDeclaration.RefScopedRole);
Space();
}
switch (parameterDeclaration.ParameterModifier)
{
case ParameterModifier.Ref:
Expand All @@ -2584,6 +2589,11 @@ public virtual void VisitParameterDeclaration(ParameterDeclaration parameterDecl
Space();
break;
}
if (parameterDeclaration.IsValueScoped)
{
WriteKeyword(ParameterDeclaration.ValueScopedRole);
Space();
}
parameterDeclaration.Type.AcceptVisitor(this);
if (!parameterDeclaration.Type.IsNull && !string.IsNullOrEmpty(parameterDeclaration.Name))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,20 @@ public enum ParameterModifier
Ref,
Out,
Params,
In
In,
Scoped
}

public class ParameterDeclaration : AstNode
{
public static readonly Role<AttributeSection> AttributeRole = EntityDeclaration.AttributeRole;
public static readonly TokenRole ThisModifierRole = new TokenRole("this");
public static readonly TokenRole RefScopedRole = new TokenRole("scoped");
public static readonly TokenRole RefModifierRole = new TokenRole("ref");
public static readonly TokenRole OutModifierRole = new TokenRole("out");
public static readonly TokenRole ParamsModifierRole = new TokenRole("params");
public static readonly TokenRole ThisModifierRole = new TokenRole("this");
public static readonly TokenRole InModifierRole = new TokenRole("in");
public static readonly TokenRole ValueScopedRole = new TokenRole("scoped");
public static readonly TokenRole ParamsModifierRole = new TokenRole("params");

#region PatternPlaceholder
public static implicit operator ParameterDeclaration?(PatternMatching.Pattern pattern)
Expand Down Expand Up @@ -92,17 +95,14 @@ bool PatternMatching.INode.DoMatchCollection(Role role, PatternMatching.INode po
}
#endregion

public override NodeType NodeType {
get {
return NodeType.Unknown;
}
}
public override NodeType NodeType => NodeType.Unknown;

public AstNodeCollection<AttributeSection> Attributes {
get { return GetChildrenByRole(AttributeRole); }
}

bool hasThisModifier;
bool isRefScoped, isValueScoped;

public CSharpTokenNode ThisKeyword {
get {
Expand All @@ -122,6 +122,22 @@ public bool HasThisModifier {
}
}

public bool IsRefScoped {
get { return isRefScoped; }
set {
ThrowIfFrozen();
isRefScoped = value;
}
}

public bool IsValueScoped {
get { return isValueScoped; }
set {
ThrowIfFrozen();
isValueScoped = value;
}
}

ParameterModifier parameterModifier;

public ParameterModifier ParameterModifier {
Expand Down
2 changes: 2 additions & 0 deletions ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,8 @@ public ParameterDeclaration ConvertParameter(IParameter parameter)
{
decl.ParameterModifier = ParameterModifier.Params;
}
decl.IsRefScoped = parameter.Lifetime.RefScoped;
decl.IsValueScoped = parameter.Lifetime.ValueScoped;
if (ShowAttributes)
{
decl.Attributes.AddRange(ConvertAttributes(parameter.GetAttributes()));
Expand Down
22 changes: 21 additions & 1 deletion ICSharpCode.Decompiler/DecompilerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,13 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
if (languageVersion < CSharp.LanguageVersion.CSharp11_0)
{
parameterNullCheck = false;
lifetimeAnnotations = false;
}
}

public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (parameterNullCheck)
if (parameterNullCheck || lifetimeAnnotations)
return CSharp.LanguageVersion.CSharp11_0;
if (fileScopedNamespaces || recordStructs)
return CSharp.LanguageVersion.CSharp10_0;
Expand Down Expand Up @@ -336,6 +337,25 @@ public bool FunctionPointers {
}
}

bool lifetimeAnnotations = true;

/// <summary>
/// Use C# 9 <c>delegate* unmanaged</c> types.
/// If this option is disabled, function pointers will instead be decompiled with type `IntPtr`.
/// </summary>
[Category("C# 11.0 / VS 2022.4")]
[Description("DecompilerSettings.LifetimeAnnotations")]
public bool LifetimeAnnotations {
get { return lifetimeAnnotations; }
set {
if (lifetimeAnnotations != value)
{
lifetimeAnnotations = value;
OnPropertyChanged();
}
}
}

bool switchExpressions = true;

/// <summary>
Expand Down
9 changes: 8 additions & 1 deletion ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ public enum TypeSystemOptions
/// </summary>
FunctionPointers = 0x2000,
/// <summary>
/// Allow C# 11 scoped annotation. If this option is not enabled, LifetimeAnnotationAttribute
/// will be reported as custom attribute.
/// </summary>
LifetimeAnnotations = 0x4000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# languages features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers
| NativeIntegers | FunctionPointers | LifetimeAnnotations
}

/// <summary>
Expand Down Expand Up @@ -160,6 +165,8 @@ public static TypeSystemOptions GetOptions(DecompilerSettings settings)
typeSystemOptions |= TypeSystemOptions.NativeIntegers;
if (settings.FunctionPointers)
typeSystemOptions |= TypeSystemOptions.FunctionPointers;
if (settings.LifetimeAnnotations)
typeSystemOptions |= TypeSystemOptions.LifetimeAnnotations;
return typeSystemOptions;
}

Expand Down
13 changes: 12 additions & 1 deletion ICSharpCode.Decompiler/TypeSystem/IParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// <remarks>
/// Should match order in <see cref="CSharp.Syntax.FieldDirection"/>.
/// </remarks>
public enum ReferenceKind
public enum ReferenceKind : byte
{
None,
Out,
Ref,
In
}

public struct LifetimeAnnotation
{
public bool RefScoped;
public bool ValueScoped;
}

public interface IParameter : IVariable
{
/// <summary>
Expand All @@ -46,6 +52,11 @@ public interface IParameter : IVariable
/// </summary>
ReferenceKind ReferenceKind { get; }

/// <summary>
/// C# 11 scoped annotation.
/// </summary>
LifetimeAnnotation Lifetime { get; }

/// <summary>
/// Gets whether this parameter is a C# 'ref' parameter.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ bool IgnoreAttribute(IType attributeType, SymbolKind target)
case "NullableContextAttribute":
return (options & TypeSystemOptions.NullabilityAnnotations) != 0
&& (target == SymbolKind.TypeDefinition || IsMethodLike(target));
case "LifetimeAnnotationAttribute":
return (options & TypeSystemOptions.LifetimeAnnotations) != 0
&& (target == SymbolKind.Parameter);
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public bool HasConstantValueInSignature {
get { return IsOptional; }
}

public LifetimeAnnotation Lifetime => default;

public object GetConstantValue(bool throwOnInvalidMetadata)
{
return defaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public enum KnownAttribute
CallerMemberName,
CallerFilePath,
CallerLineNumber,
LifetimeAnnotation,

// Type parameter attributes:
IsUnmanaged,
Expand Down Expand Up @@ -162,6 +163,7 @@ static class KnownAttributes
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerMemberNameAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerFilePathAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", nameof(CallerLineNumberAttribute)),
new TopLevelTypeName("System.Runtime.CompilerServices", "LifetimeAnnotationAttribute"),
// Type parameter attributes:
new TopLevelTypeName("System.Runtime.CompilerServices", "IsUnmanagedAttribute"),
// Marshalling attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,38 @@ ReferenceKind DetectRefKind()
return ReferenceKind.Ref;
}

public LifetimeAnnotation Lifetime {
get {
if ((module.TypeSystemOptions & TypeSystemOptions.LifetimeAnnotations) == 0)
{
return default;
}

var metadata = module.metadata;
var parameterDef = metadata.GetParameter(handle);
foreach (var h in parameterDef.GetCustomAttributes())
{
var custom = metadata.GetCustomAttribute(h);
if (!custom.IsKnownAttribute(metadata, KnownAttribute.LifetimeAnnotation))
continue;

var value = custom.DecodeValue(module.TypeProvider);
if (value.FixedArguments.Length != 2)
continue;
if (value.FixedArguments[0].Value is bool refScoped
&& value.FixedArguments[1].Value is bool valueScoped)
{
return new LifetimeAnnotation {
RefScoped = refScoped,
ValueScoped = valueScoped
};
}
}

return default;
}
}

public bool IsParams {
get {
if (Type.Kind != TypeKind.Array)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public SpecializedParameter(IParameter baseParameter, IType newType, IParameteri
object IVariable.GetConstantValue(bool throwOnInvalidMetadata) => baseParameter.GetConstantValue(throwOnInvalidMetadata);
SymbolKind ISymbol.SymbolKind => SymbolKind.Parameter;

public LifetimeAnnotation Lifetime => baseParameter.Lifetime;

public override string ToString()
{
return DefaultParameter.ToString(this);
Expand Down
1 change: 1 addition & 0 deletions ILSpy/Languages/CSharpHighlightingTokenWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ public override void WriteKeyword(Role role, string keyword)
case "params":
case "ref":
case "out":
case "scoped":
color = parameterModifierColor;
break;
case "break":
Expand Down
2 changes: 1 addition & 1 deletion ILSpy/Languages/CSharpLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public override IReadOnlyList<LanguageVersion> LanguageVersions {
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp8_0.ToString(), "C# 8.0 / VS 2019"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp9_0.ToString(), "C# 9.0 / VS 2019.8"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp10_0.ToString(), "C# 10.0 / VS 2022"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.1"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"),
};
}
return versions;
Expand Down
20 changes: 19 additions & 1 deletion ILSpy/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion ILSpy/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.IsUnmanagedAttributeOnTypeParametersShouldBeReplacedWithUnmanagedConstraints" xml:space="preserve">
<value>IsUnmanagedAttribute on type parameters should be replaced with 'unmanaged' constraints</value>
</data>
<data name="DecompilerSettings.LifetimeAnnotations" xml:space="preserve">
<value>'scoped' lifetime annotation</value>
</data>
<data name="DecompilerSettings.NativeIntegers" xml:space="preserve">
<value>Use nint/nuint types</value>
</data>
Expand Down Expand Up @@ -412,7 +415,10 @@ Are you sure you want to continue?</value>
<value>Read-only methods</value>
</data>
<data name="DecompilerSettings.RecordClasses" xml:space="preserve">
<value>Records</value>
<value>Record classes</value>
</data>
<data name="DecompilerSettings.RecordStructs" xml:space="preserve">
<value>Record structs</value>
</data>
<data name="DecompilerSettings.RemoveDeadAndSideEffectFreeCodeUseWithCaution" xml:space="preserve">
<value>Remove dead and side effect free code (use with caution!)</value>
Expand Down

0 comments on commit 2ed9ad6

Please sign in to comment.