diff --git a/src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs index d1444d0fa1984..ce88215f839ae 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs @@ -53,11 +53,13 @@ protected override bool IsValidSnippetLocationCore(SnippetContext context, Cance protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) { // Having a property with `set` accessor in a readonly struct leads to a compiler error. - // So if user executes snippet inside a readonly struct the right thing to do is to not generate `set` accessor at all + // At the same time having a required property with no setter at all is also illegal. + // Thus out best guess here is to generate an `init` accessor. We can assume they are available + // as a language feature since `required` keyword has a higher minimal language version to use if (syntaxContext.ContainingTypeDeclaration is StructDeclarationSyntax structDeclaration && syntaxContext.SemanticModel.GetDeclaredSymbol(structDeclaration, cancellationToken) is { IsReadOnly: true }) { - return null; + return SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration).WithSemicolonToken(SemicolonToken); } return base.GenerateSetAccessorDeclaration(syntaxContext, generator, cancellationToken); diff --git a/src/Features/CSharpTest/Snippets/CSharpProprSnippetProviderTests.cs b/src/Features/CSharpTest/Snippets/CSharpProprSnippetProviderTests.cs index c1eed413d6c5a..53f18134bebba 100644 --- a/src/Features/CSharpTest/Snippets/CSharpProprSnippetProviderTests.cs +++ b/src/Features/CSharpTest/Snippets/CSharpProprSnippetProviderTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets; @@ -14,14 +15,16 @@ public sealed class CSharpProprSnippetProviderTests : AbstractCSharpAutoProperty protected override string DefaultPropertyBlockText => "{ get; set; }"; + [WorkItem("https://github.com/dotnet/roslyn/issues/79954")] public override Task InsertSnippetInReadonlyStructTest() => VerifyPropertyAsync(""" readonly struct MyStruct { $$ } - """, "public required {|0:int|} {|1:MyProperty|} { get; }"); + """, "public required {|0:int|} {|1:MyProperty|} { get; init; }"); + [WorkItem("https://github.com/dotnet/roslyn/issues/79954")] public override Task InsertSnippetInReadonlyStructTest_ReadonlyModifierInOtherPartialDeclaration() => VerifyPropertyAsync(""" partial struct MyStruct @@ -32,8 +35,9 @@ partial struct MyStruct readonly partial struct MyStruct { } - """, "public required {|0:int|} {|1:MyProperty|} { get; }"); + """, "public required {|0:int|} {|1:MyProperty|} { get; init; }"); + [WorkItem("https://github.com/dotnet/roslyn/issues/79954")] public override Task InsertSnippetInReadonlyStructTest_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier() => VerifyPropertyAsync(""" struct MyStruct @@ -44,7 +48,7 @@ struct MyStruct readonly partial struct MyStruct { } - """, "public required {|0:int|} {|1:MyProperty|} { get; }"); + """, "public required {|0:int|} {|1:MyProperty|} { get; init; }"); public override Task VerifySnippetInInterfaceTest() => VerifySnippetIsAbsentAsync("""