Browse Source

Merge pull request #4001 from icsharpcode/fix/init-accessor-comment

Print the init-only marker after the set keyword again
pull/4008/head
Daniel Grunwald 1 month ago committed by GitHub
parent
commit
7c5af0b7b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 18
      ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoInitAccessors.Expected.cs
  3. 36
      ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoInitAccessors.cs
  4. 15
      ICSharpCode.Decompiler.Tests/UglyTestRunner.cs
  5. 6
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  6. 10
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs
  7. 7
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -254,6 +254,8 @@ @@ -254,6 +254,8 @@
<None Include="TestCases\Ugly\NoFieldKeyword.Expected.cs" />
<Compile Remove="TestCases\Ugly\NoForEachStatement.Expected.cs" />
<None Include="TestCases\Ugly\NoForEachStatement.Expected.cs" />
<Compile Remove="TestCases\Ugly\NoInitAccessors.Expected.cs" />
<None Include="TestCases\Ugly\NoInitAccessors.Expected.cs" />
<Compile Remove="TestCases\Ugly\NoLocalFunctions.Expected.cs" />
<None Include="TestCases\Ugly\NoLocalFunctions.Expected.cs" />
<Compile Remove="TestCases\Ugly\NoNewOfT.Expected.cs" />

18
ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoInitAccessors.Expected.cs

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
{
internal class NoInitAccessors
{
private string name;
public int AutoInit { get; set/*init*/; }
public string Name {
get {
return name;
}
set/*init*/ {
name = value;
}
}
}
}

36
ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoInitAccessors.cs

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
// Copyright (c) 2026 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly
{
internal class NoInitAccessors
{
private string name;
public int AutoInit { get; init; }
public string Name {
get {
return name;
}
init {
name = value;
}
}
}
}

15
ICSharpCode.Decompiler.Tests/UglyTestRunner.cs

@ -76,6 +76,15 @@ namespace ICSharpCode.Decompiler.Tests @@ -76,6 +76,15 @@ namespace ICSharpCode.Decompiler.Tests
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
});
// init accessors require C# 9 and the IsExternalInit marker, which .NET Framework 4.0 lacks
static readonly CompilerOptions[] initAccessorOptions = Tester.SupportedOnCurrentPlatform(new[]
{
CompilerOptions.UseRoslyn4_14_0,
CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0,
CompilerOptions.UseRoslynLatest,
CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest,
});
// top-level statements require C# 9 and cannot target .NET Framework 4.0
static readonly CompilerOptions[] topLevelProgramOptions = Tester.SupportedOnCurrentPlatform(new[]
{
@ -127,6 +136,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -127,6 +136,12 @@ namespace ICSharpCode.Decompiler.Tests
});
}
[Test]
public async Task NoInitAccessors([ValueSource(nameof(initAccessorOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp8_0));
}
[Test]
public async Task NoForEachStatement([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{

6
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -2295,6 +2295,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2295,6 +2295,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
else
{
WriteKeyword("set");
if (accessor.IsInitOnly)
{
// The setter is init-only, but the output language version has no init accessor:
// mark the keyword, so that the difference is not lost silently.
writer.WriteComment(CommentType.MultiLine, "init");
}
}
style = policy.PropertySetBraceStyle;
}

10
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/Accessor.cs

@ -52,6 +52,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -52,6 +52,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public AccessorKind Kind { get; set; }
/// <summary>
/// True if the underlying setter is init-only. When <see cref="Kind"/> is not
/// <see cref="AccessorKind.Init"/>, because the output language version has no init accessors,
/// the accessor prints as "set" followed by an /*init*/ comment marking the difference.
/// Excluded from pattern matching: a pattern written for a setter has to match an init-only
/// setter just the same.
/// </summary>
[ExcludeFromMatch]
public bool IsInitOnly { get; set; }
// An accessor is printed as its keyword (get/set/init/add/remove), never an identifier, so it
// carries no name. The contract members are overridden to no-ops: shared decompiler code sets a
// name on every method-like entity (e.g. explicit interface implementations), which is irrelevant

7
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -228,7 +228,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -228,7 +228,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// <summary>
/// Controls whether C# 9 "init;" accessors are supported.
/// If disabled, emits "set /*init*/;" instead.
/// If disabled, emits "set/*init*/;" instead.
/// </summary>
public bool SupportInitAccessors { get; set; }
@ -2234,10 +2234,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -2234,10 +2234,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
accessorKind = AccessorKind.Init;
}
decl.Kind = accessorKind;
if (accessor.IsInitOnly && accessorKind != AccessorKind.Init)
{
decl.AddTrailingTrivia(new Comment("init", CommentType.MultiLine));
}
decl.IsInitOnly = accessor.IsInitOnly;
if (AddResolveResultAnnotations)
{
decl.AddAnnotation(new MemberResolveResult(null, accessor));

Loading…
Cancel
Save