diff --git a/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt b/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt index b79a079b6d63e..a03401416d958 100644 --- a/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt +++ b/src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt @@ -25,6 +25,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Parameter = 20 -> Micros Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Property = 13 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RaiseAccessor = 28 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RecordClass = 29 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind +Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RecordStruct = 30 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RemoveAccessor = 27 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.SetAccessor = 25 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Struct = 3 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 23f86ad16c197..26416a850248f 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -348,87 +348,6 @@ private static bool IsInAStringOrCharacter(SyntaxNode currentNode, SnapshotPoint && caret.Position < currentNode.Span.End && caret.Position > currentNode.SpanStart; - private static bool SemicolonIsMissing(SyntaxNode currentNode) - { - switch (currentNode.Kind()) - { - case SyntaxKind.LocalDeclarationStatement: - return ((LocalDeclarationStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ReturnStatement: - return ((ReturnStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.VariableDeclaration: - return SemicolonIsMissing(currentNode.Parent); - case SyntaxKind.ThrowStatement: - return ((ThrowStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DoStatement: - return ((DoStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.GetAccessorDeclaration: - case SyntaxKind.SetAccessorDeclaration: - return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.FieldDeclaration: - return ((FieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ForStatement: - return ((ForStatementSyntax)currentNode).FirstSemicolonToken.IsMissing; - case SyntaxKind.ExpressionStatement: - return ((ExpressionStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EmptyStatement: - return ((EmptyStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.GotoStatement: - return ((GotoStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.BreakStatement: - return ((BreakStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ContinueStatement: - return ((ContinueStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.YieldReturnStatement: - case SyntaxKind.YieldBreakStatement: - return ((YieldStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.LocalFunctionStatement: - return ((LocalFunctionStatementSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.NamespaceDeclaration: - return ((NamespaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.UsingDirective: - return ((UsingDirectiveSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ExternAliasDirective: - return ((ExternAliasDirectiveSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ClassDeclaration: - return ((ClassDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.StructDeclaration: - return ((StructDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.InterfaceDeclaration: - return ((InterfaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EnumDeclaration: - return ((EnumDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DelegateDeclaration: - return ((DelegateDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.EventFieldDeclaration: - return ((EventFieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.MethodDeclaration: - return ((MethodDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.OperatorDeclaration: - return ((OperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ConversionOperatorDeclaration: - return ((ConversionOperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.ConstructorDeclaration: - return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.BaseConstructorInitializer: - case SyntaxKind.ThisConstructorInitializer: - return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.DestructorDeclaration: - return ((DestructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.IndexerDeclaration: - return ((IndexerDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - case SyntaxKind.AddAccessorDeclaration: - return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing; - default: - // At this point, the node should be empty or its children should not end with a semicolon. - Debug.Assert(!currentNode.ChildNodesAndTokens().Any() - || !currentNode.ChildNodesAndTokens().Last().IsKind(SyntaxKind.SemicolonToken)); - return false; - } - } - /// /// Determines if a statement ends with a closing delimiter, and that closing delimiter exists. /// diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index c99915b6ec525..8d76e7dce789b 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -4443,5 +4443,33 @@ class C testHost, Record("R")); } + + [Theory] + [CombinatorialData] + public async Task BasicRecordClassClassification(TestHost testHost) + { + await TestAsync( +@"record class R +{ + R r; + + R() { } +}", + testHost, + Record("R")); + } + + [Theory] + [CombinatorialData] + public async Task BasicRecordStructClassification(TestHost testHost) + { + await TestAsync( +@"record struct R +{ + R property { get; set; } +}", + testHost, + RecordStruct("R")); + } } } diff --git a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs index ee85fc5dac98a..51ef31753f7fd 100644 --- a/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/TotalClassifierTests.cs @@ -175,6 +175,56 @@ await TestAsync( Punctuation.CloseCurly); } + [Theory] + [CombinatorialData] + public async Task TestRecordClass(TestHost testHost) + { + await TestAsync( +@"record class R +{ + R() + { + } +}", + testHost, + Keyword("record"), + Keyword("class"), + Record("R"), + Punctuation.OpenCurly, + Record("R"), + Punctuation.OpenParen, + Punctuation.CloseParen, + Punctuation.OpenCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } + + [Theory] + [CombinatorialData] + public async Task TestRecordStruct(TestHost testHost) + { + await TestAsync( +@"record struct R +{ + R(int i) + { + } +}", + testHost, + Keyword("record"), + Keyword("struct"), + RecordStruct("R"), + Punctuation.OpenCurly, + RecordStruct("R"), + Punctuation.OpenParen, + Keyword("int"), + Parameter("i"), + Punctuation.CloseParen, + Punctuation.OpenCurly, + Punctuation.CloseCurly, + Punctuation.CloseCurly); + } + [Theory] [CombinatorialData] public async Task UsingAliasGlobalNamespace(TestHost testHost) diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs index 41475a258b766..5cf6db0f41970 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs @@ -4206,24 +4206,50 @@ void Test() } [WorkItem(48453, "https://github.com/dotnet/roslyn/issues/48453")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] + [InlineData("record")] + [InlineData("record class")] + public async Task TestInRecord(string record) + { + await TestInRegularAndScript1Async($@" +{record} Program +{{ + int field; + + public int this[int i] => [|this.field|]; +}}", +$@" +{record} Program +{{ + int field; + + public int this[int i] => {{|Rename:GetField|}}(); + + private int GetField() + {{ + return this.field; + }} +}}"); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] - public async Task TestInRecord() + public async Task TestInRecordStruct() { await TestInRegularAndScript1Async(@" -record Program +record struct Program { int field; public int this[int i] => [|this.field|]; }", @" -record Program +record struct Program { int field; public int this[int i] => {|Rename:GetField|}(); - private int GetField() + private readonly int GetField() { return this.field; } diff --git a/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs b/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs index 8f43e4be2e8df..efb3df175f11f 100644 --- a/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs @@ -215,35 +215,38 @@ public class A await RunMethodReferenceTest(input); } - [Fact, Trait(Traits.Feature, Traits.Features.CodeLens)] - public async Task TestFullyQualifiedName() + [Theory, Trait(Traits.Feature, Traits.Features.CodeLens)] + [InlineData("class")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestFullyQualifiedName(string typeKind) { - const string input = @" + var input = $@" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs index c9fccc90daae5..21bec8998c987 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProviderTests.cs @@ -35,19 +35,22 @@ class C : IList await VerifyItemExistsAsync(markup, "IList"); } - [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestAtStartOfRecord() + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestAtStartOfRecord(string record) { - var markup = @" + var markup = $@" using System.Collections; -record C : IList -{ +{record} C : IList +{{ int $$ -} +}} "; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs index 9607425cfaf0a..de1176e685356 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs @@ -412,7 +412,10 @@ public async Task SuggestEventAfterReadonlyInStruct() [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [WorkItem(39265, "https://github.com/dotnet/roslyn/issues/39265")] [InlineData("struct", true)] + [InlineData("record struct", true)] [InlineData("class", false)] + [InlineData("record", false)] + [InlineData("record class", false)] [InlineData("interface", false)] public async Task SuggestReadonlyPropertyAccessor(string declarationType, bool present) { diff --git a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs index 55566b6cc9e86..8e20dbcb817b8 100644 --- a/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/ImplementInterface/ImplementInterfaceTests.cs @@ -8724,30 +8724,33 @@ public void M1() } [WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] - public async Task TestImplementOnRecord_WithSemiColonAndTrivia() + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestImplementOnRecord_WithSemiColonAndTrivia(string record) { - await TestInRegularAndScriptAsync(@" + await TestInRegularAndScriptAsync($@" interface I -{ +{{ void M1(); -} +}} -record C : [|I|]; // hello +{record} C : [|I|]; // hello ", -@" +$@" interface I -{ +{{ void M1(); -} +}} -record C : [|I|] // hello -{ +{record} C : [|I|] // hello +{{ public void M1() - { + {{ throw new System.NotImplementedException(); - } -} + }} +}} "); } diff --git a/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs b/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs index d9c1ae3943071..4ab058e0cb278 100644 --- a/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs +++ b/src/EditorFeatures/CSharpTest/MakeRefStruct/MakeRefStructTests.cs @@ -61,6 +61,18 @@ ref struct S await TestInRegularAndScriptAsync(text, expected, parseOptions: s_parseOptions); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeRefStruct)] + public async Task FieldInRecordStruct() + { + var text = CreateTestSource(@" +record struct S +{ + Span[||] m; +} +"); + await TestMissingInRegularAndScriptAsync(text, new TestParameters(CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview))); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeRefStruct)] public async Task FieldInNestedClassInsideNotRefStruct() { diff --git a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs index 6a382abadd4b5..99ca6817b62fd 100644 --- a/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs +++ b/src/EditorFeatures/CSharpTest/Organizing/OrganizeTypeDeclarationTests.cs @@ -24,6 +24,8 @@ public class OrganizeTypeDeclarationTests : AbstractOrganizerTests [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] public async Task TestFieldsWithoutInitializers1(string typeKind) { var initial = @@ -42,6 +44,30 @@ public async Task TestFieldsWithoutInitializers1(string typeKind) await CheckAsync(initial, final); } + [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestNestedTypes(string typeKind) + { + var initial = +$@"class C {{ + {typeKind} Nested1 {{ }} + {typeKind} Nested2 {{ }} + int A; +}}"; + + var final = +$@"class C {{ + int A; + {typeKind} Nested1 {{ }} + {typeKind} Nested2 {{ }} +}}"; + await CheckAsync(initial, final); + } + [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] @@ -66,6 +92,7 @@ public async Task TestFieldsWithoutInitializers2(string typeKind) [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record struct")] public async Task TestFieldsWithInitializers1(string typeKind) { var initial = @@ -287,6 +314,7 @@ public async Task TestStaticInstance(string typeKind) [Theory, Trait(Traits.Feature, Traits.Features.Organizing)] [InlineData("class")] [InlineData("record")] + [InlineData("record struct")] public async Task TestAccessibility(string typeKind) { var initial = diff --git a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs index c5d7ae8e19b59..5e29cd7b93a29 100644 --- a/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs +++ b/src/EditorFeatures/CSharpTest/Structure/MetadataAsSource/TypeDeclarationStructureTests.cs @@ -74,6 +74,23 @@ public async Task RecordWithCommentsAndAttributes() // This is a doc comment. [Bar, Baz] |}{|#0:public record $$C|}{|textspan2: +{ + void M(); +}|}|#0}"; + + await VerifyBlockSpansAsync(code, + Region("textspan", "hint", CSharpStructureHelpers.Ellipsis, autoCollapse: true), + Region("textspan2", "#0", CSharpStructureHelpers.Ellipsis, autoCollapse: false)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.MetadataAsSource)] + public async Task RecordStructWithCommentsAndAttributes() + { + const string code = @" +{|hint:{|textspan:// Summary: +// This is a doc comment. +[Bar, Baz] +|}{|#0:public record struct $$C|}{|textspan2: { void M(); }|}|#0}"; diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 25271dd32928b..63f7c60a79858 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -66,13 +66,16 @@ await VerifyKeywordAsync( [$$"); } - [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public async Task TestInAttributeInsideRecord() + [Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestInAttributeInsideRecord(string record) { // The recommender doesn't work in record in script // Tracked by https://github.com/dotnet/roslyn/issues/44865 await VerifyWorkerAsync( -@"record C { +$@"{record} C {{ [$$", absent: false, TestOptions.RegularPreview); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs index b3e32c3a2955a..33a3e93a1ea3e 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FixedKeywordRecommenderTests.cs @@ -97,6 +97,14 @@ public async Task TestNotInStruct() { await VerifyAbsenceAsync( @"struct S { + $$"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInRecordStruct() + { + await VerifyAbsenceAsync( +@"record struct S { $$"); } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs index 0e2ff6ade57c3..ba23401ec98d5 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VoidKeywordRecommenderTests.cs @@ -785,6 +785,15 @@ struct S public readonly $$"); } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestAfterReadonlyInRecordStruct() + { + await VerifyKeywordAsync(@" +record struct S +{ + public readonly $$"); + } + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] [WorkItem(43295, "https://github.com/dotnet/roslyn/issues/43295")] public async Task TestNotAfterReadonlyInClass() diff --git a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs index 995659f5580e7..07003fb2ad364 100644 --- a/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs +++ b/src/EditorFeatures/Core.Wpf/Classification/ClassificationTypeFormatDefinitions.cs @@ -204,6 +204,25 @@ public UserTypeRecordsFormatDefinition() } } #endregion + #region User Types - Record structs + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.RecordStructName)] + [Name(ClassificationTypeNames.RecordStructName)] + [Order(After = PredefinedClassificationTypeNames.Identifier)] + [Order(After = PredefinedClassificationTypeNames.Keyword)] + [Order(Before = ClassificationTypeNames.StaticSymbol)] + [UserVisible(true)] + [ExcludeFromCodeCoverage] + private class UserTypeRecordStructsFormatDefinition : ClassificationFormatDefinition + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public UserTypeRecordStructsFormatDefinition() + { + this.DisplayName = EditorFeaturesResources.User_Types_Record_Structs; + } + } + #endregion #region User Types - Delegates [Export(typeof(EditorFormatDefinition))] [ClassificationType(ClassificationTypeNames = ClassificationTypeNames.DelegateName)] diff --git a/src/EditorFeatures/Core/EditorFeaturesResources.resx b/src/EditorFeatures/Core/EditorFeaturesResources.resx index 2f6899e590dce..655e59d820ec5 100644 --- a/src/EditorFeatures/Core/EditorFeaturesResources.resx +++ b/src/EditorFeatures/Core/EditorFeaturesResources.resx @@ -939,6 +939,9 @@ Do you want to proceed? User Types - Records + + User Types - Record Structs + Get help for '{0}' diff --git a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs index 122bee88aa0fb..5f5227bff4bdd 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/ClassificationTypeDefinitions.cs @@ -58,6 +58,12 @@ internal sealed class ClassificationTypeDefinitions [BaseDefinition(ClassificationTypeNames.ClassName)] internal readonly ClassificationTypeDefinition UserTypeRecordsTypeDefinition; #endregion + #region User Types - Record Structs + [Export] + [Name(ClassificationTypeNames.RecordStructName)] + [BaseDefinition(ClassificationTypeNames.StructName)] + internal readonly ClassificationTypeDefinition UserTypeRecordStructsTypeDefinition; + #endregion #region User Types - Delegates [Export] [Name(ClassificationTypeNames.DelegateName)] diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf index 6539fa8dcfff7..20a0010739633 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.cs.xlf @@ -457,6 +457,11 @@ Uživatelské typy – rozhraní + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Typy uživatelů – Záznamy diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf index 7dab8aa18dd1f..e73d787ccc8f1 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.de.xlf @@ -457,6 +457,11 @@ Benutzertypen - Schnittstellen + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Benutzertypen – Datensätze diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf index 91f0299c0d109..ed9f89eb81174 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.es.xlf @@ -457,6 +457,11 @@ Tipos de usuario: interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipos de usuario: registros diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf index 734ca3ccf60db..2d3c94fd04137 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.fr.xlf @@ -457,6 +457,11 @@ Types d'utilisateurs - Interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Types d'utilisateur - Enregistrements diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf index ca26b60ca9ecd..155f6d1fd0cb8 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.it.xlf @@ -457,6 +457,11 @@ Tipi utente - Interfacce + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipi utente - Record diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf index 5508feabfa76b..5a1c9d998aaf7 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ja.xlf @@ -457,6 +457,11 @@ ユーザー タイプ - インターフェイス + + User Types - Record Structs + User Types - Record Structs + + User Types - Records ユーザー タイプ - レコード diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf index 295e6f802a05b..5fac4f47d28c8 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ko.xlf @@ -457,6 +457,11 @@ 사용자 형식 - 인터페이스 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 사용자 유형 - 레코드 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf index 823fd239c3d54..362f172ecbc79 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pl.xlf @@ -457,6 +457,11 @@ Typy użytkownika — interfejsy + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Typy użytkownika — rekordy diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf index 00eb77d4bcf95..63992526de0d0 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.pt-BR.xlf @@ -457,6 +457,11 @@ Tipos de Usuário - Interfaces + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Tipos de Usuário – Registros diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf index e797ae4690a91..0971262a56d41 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.ru.xlf @@ -457,6 +457,11 @@ Пользовательские типы — интерфейсы + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Пользовательские типы — записи diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf index 42dd9347671f5..e9ee571938d91 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.tr.xlf @@ -457,6 +457,11 @@ Kullanıcı Türleri - Arabirimler + + User Types - Record Structs + User Types - Record Structs + + User Types - Records Kullanıcı Türleri - Kayıtlar diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf index 01cd292694cf0..5e265f962febb 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hans.xlf @@ -457,6 +457,11 @@ 用户类型 - 接口 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 用户类型 - 记录 diff --git a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf index 285e7d9493df4..9cbcd387ae693 100644 --- a/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf +++ b/src/EditorFeatures/Core/xlf/EditorFeaturesResources.zh-Hant.xlf @@ -457,6 +457,11 @@ 使用者類型 - 介面 + + User Types - Record Structs + User Types - Record Structs + + User Types - Records 使用者類型 - 記錄 diff --git a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb index b03efe20b2bda..6a896dc783796 100644 --- a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb @@ -1158,5 +1158,107 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense ToolTipAssert.EqualContent(expected, container) End Function + + + Public Async Function QuickInfoForRecordClass() As Task + Dim workspace = + + + + public sealed record class TestRecord(int X, int Y) { } + + class C + { + void M() + { + var x = new Test$$Record(1, 2); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.RecordClassName, "TestRecord", navigationAction:=Sub() Return, "TestRecord"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.RecordClassName, "TestRecord", navigationAction:=Sub() Return, "TestRecord.TestRecord(int X, int Y)"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "X", navigationAction:=Sub() Return, "int X"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ","), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "Y", navigationAction:=Sub() Return, "int Y"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")")))) + + ToolTipAssert.EqualContent(expected, container) + End Function + + + Public Async Function QuickInfoForRecordStructs() As Task + Dim workspace = + + + + public sealed record struct TestRecord(int X, int Y) { } + + class C + { + void M() + { + var x = new Test$$Record(1, 2); + } + } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.RecordStructName, "TestRecord", navigationAction:=Sub() Return, "TestRecord"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."), + New ClassifiedTextRun(ClassificationTypeNames.RecordStructName, "TestRecord", navigationAction:=Sub() Return, "TestRecord.TestRecord(int X, int Y)"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "X", navigationAction:=Sub() Return, "int X"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ","), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int", navigationAction:=Sub() Return, "int"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.ParameterName, "Y", navigationAction:=Sub() Return, "int Y"), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "+"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Text, "1"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Text, FeaturesResources.overload), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")")))) + + ToolTipAssert.EqualContent(expected, container) + End Function End Class End Namespace diff --git a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs index a1488330e57be..0a05fc8cdad27 100644 --- a/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs +++ b/src/EditorFeatures/TestUtilities/Classification/FormattedClassifications.cs @@ -34,6 +34,10 @@ public static FormattedClassification Class(string text) public static FormattedClassification Record(string text) => New(text, ClassificationTypeNames.RecordClassName); + [DebuggerStepThrough] + public static FormattedClassification RecordStruct(string text) + => New(text, ClassificationTypeNames.RecordStructName); + [DebuggerStepThrough] public static FormattedClassification Delegate(string text) => New(text, ClassificationTypeNames.DelegateName); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs index 754cb0310009c..fd435e1bf5f3f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ExplicitInterfaceTypeCompletionProvider.cs @@ -142,7 +142,7 @@ private static bool IsPreviousTokenValid(SyntaxToken tokenBeforeType) } private static bool IsClassOrStructOrInterfaceOrRecord(SyntaxNode node) - => node.Kind() == SyntaxKind.ClassDeclaration || node.Kind() == SyntaxKind.StructDeclaration || - node.Kind() == SyntaxKind.InterfaceDeclaration || node.Kind() == SyntaxKind.RecordDeclaration; + => node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or + SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; } } diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 26afaf5f39551..e098eb5de0c35 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -18,6 +18,7 @@ internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommend SyntaxKind.StructDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.EnumDeclaration, }; diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs index 18a5aae547cf4..4f52472c83899 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/ReadOnlyKeywordRecommender.cs @@ -52,7 +52,11 @@ private static bool IsValidContextForType(CSharpSyntaxContext context, Cancellat } private static bool IsStructAccessorContext(CSharpSyntaxContext context) - => context.ContainingTypeDeclaration.IsKind(SyntaxKind.StructDeclaration) && + { + var type = context.ContainingTypeDeclaration; + return type is not null && + type.Kind() is SyntaxKind.StructDeclaration or SyntaxKind.RecordStructDeclaration && context.TargetToken.IsAnyAccessorDeclarationContext(context.Position, SyntaxKind.ReadOnlyKeyword); + } } } diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs index 3011dacc7a5ad..d625cd68869d3 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs @@ -39,6 +39,7 @@ protected override bool IsIgnoredCodeBlock(SyntaxNode codeBlock) SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.EnumDeclaration); diff --git a/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs b/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs index 079e46c889145..a908916e98648 100644 --- a/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs +++ b/src/Features/CSharp/Portable/DocumentationComments/DocumentationCommentSnippetService.cs @@ -39,8 +39,10 @@ protected override bool SupportsDocumentationComments(MemberDeclarationSyntax me switch (member.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMemberDeclaration: diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 2517806df62f5..4f1c785724200 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -564,6 +564,7 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, out bool isLeaf) return Label.NamespaceDeclaration; // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) + // Need to add support for record structs (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: @@ -1343,6 +1344,7 @@ private static double CombineOptional( return ((NamespaceDeclarationSyntax)node).Name; // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) + // Need to add support for record structs (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs index ae496e28361cf..56739f2c2e9c7 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.CallSiteContainerRewriter.cs @@ -386,6 +386,16 @@ public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) return GetUpdatedTypeDeclaration(node); } + public override SyntaxNode VisitRecordStructDeclaration(RecordStructDeclarationSyntax node) + { + if (node != ContainerOfStatementsOrFieldToReplace) + { + return base.VisitRecordStructDeclaration(node); + } + + return GetUpdatedTypeDeclaration(node); + } + public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { if (node != ContainerOfStatementsOrFieldToReplace) diff --git a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs index 2bbebe4eb7164..89dfa3f31afc5 100644 --- a/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs +++ b/src/Features/CSharp/Portable/GenerateType/CSharpGenerateTypeService.cs @@ -100,9 +100,7 @@ expression.Parent is BaseTypeSyntax baseType && // If it's in the base list of an interface or struct, then it's definitely an // interface. - return - baseList.IsParentKind(SyntaxKind.InterfaceDeclaration) || - baseList.IsParentKind(SyntaxKind.StructDeclaration); + return baseList.IsParentKind(SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration); } if (expression is TypeSyntax && @@ -673,7 +671,7 @@ internal override bool TryGetBaseList(ExpressionSyntax expression, out TypeKindO { if (node is BaseListSyntax) { - if (node.Parent != null && (node.Parent is InterfaceDeclarationSyntax || node.Parent is StructDeclarationSyntax)) + if (node.Parent is InterfaceDeclarationSyntax or StructDeclarationSyntax or RecordStructDeclarationSyntax) { typeKindValue = TypeKindOptions.Interface; return true; diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs index 74bdae4b8143a..644850fb6aa4f 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementInterfaceService.cs @@ -41,7 +41,7 @@ protected override bool TryInitializeState( baseType.IsParentKind(SyntaxKind.BaseList) && baseType.Type == interfaceNode) { - if (interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration)) + if (interfaceNode.Parent.Parent.IsParentKind(SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration)) { var interfaceSymbolInfo = model.GetSymbolInfo(interfaceNode, cancellationToken); if (interfaceSymbolInfo.CandidateReason != CandidateReason.WrongArity) diff --git a/src/Features/CSharp/Portable/Organizing/Organizers/MemberDeclarationsOrganizer.Comparer.cs b/src/Features/CSharp/Portable/Organizing/Organizers/MemberDeclarationsOrganizer.Comparer.cs index 72c40758ffd84..c362b4b3d1338 100644 --- a/src/Features/CSharp/Portable/Organizing/Organizers/MemberDeclarationsOrganizer.Comparer.cs +++ b/src/Features/CSharp/Portable/Organizing/Organizers/MemberDeclarationsOrganizer.Comparer.cs @@ -171,6 +171,7 @@ private static OuterOrdering GetOuterOrdering(MemberDeclarationSyntax x) case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return OuterOrdering.Types; default: return OuterOrdering.Remaining; diff --git a/src/Features/CSharp/Portable/Organizing/Organizers/RecordDeclarationOrganizer.cs b/src/Features/CSharp/Portable/Organizing/Organizers/RecordDeclarationOrganizer.cs index 91202cd480449..b8775103c79fe 100644 --- a/src/Features/CSharp/Portable/Organizing/Organizers/RecordDeclarationOrganizer.cs +++ b/src/Features/CSharp/Portable/Organizing/Organizers/RecordDeclarationOrganizer.cs @@ -28,6 +28,7 @@ protected override RecordDeclarationSyntax Organize( syntax.AttributeLists, ModifiersOrganizer.Organize(syntax.Modifiers), syntax.Keyword, + syntax.ClassKeyword, syntax.Identifier, syntax.TypeParameterList, syntax.ParameterList, diff --git a/src/Features/CSharp/Portable/Organizing/Organizers/RecordStructDeclarationOrganizer.cs b/src/Features/CSharp/Portable/Organizing/Organizers/RecordStructDeclarationOrganizer.cs new file mode 100644 index 0000000000000..f8527cb732bae --- /dev/null +++ b/src/Features/CSharp/Portable/Organizing/Organizers/RecordStructDeclarationOrganizer.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Organizing.Organizers; + +namespace Microsoft.CodeAnalysis.CSharp.Organizing.Organizers +{ + [ExportSyntaxNodeOrganizer(LanguageNames.CSharp), Shared] + internal class RecordStructDeclarationOrganizer : AbstractSyntaxNodeOrganizer + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RecordStructDeclarationOrganizer() + { + } + + protected override RecordStructDeclarationSyntax Organize( + RecordStructDeclarationSyntax syntax, + CancellationToken cancellationToken) + { + return syntax.Update( + syntax.AttributeLists, + ModifiersOrganizer.Organize(syntax.Modifiers), + syntax.Keyword, + syntax.StructKeyword, + syntax.Identifier, + syntax.TypeParameterList, + syntax.ParameterList, + syntax.BaseList, + syntax.ConstraintClauses, + syntax.OpenBraceToken, + MemberDeclarationsOrganizer.Organize(syntax.Members, cancellationToken), + syntax.CloseBraceToken, + syntax.SemicolonToken); + } + } +} diff --git a/src/Features/CSharp/Portable/Structure/CSharpBlockStructureProvider.cs b/src/Features/CSharp/Portable/Structure/CSharpBlockStructureProvider.cs index 241a13e5e895c..9b168d5d91d46 100644 --- a/src/Features/CSharp/Portable/Structure/CSharpBlockStructureProvider.cs +++ b/src/Features/CSharp/Portable/Structure/CSharpBlockStructureProvider.cs @@ -42,6 +42,7 @@ private static ImmutableDictionary(); builder.Add(); builder.Add(); + builder.Add(); builder.Add(); builder.Add(); builder.Add(); diff --git a/src/Features/CSharp/Portable/Structure/CSharpStructureHelpers.cs b/src/Features/CSharp/Portable/Structure/CSharpStructureHelpers.cs index cc20341acbd9a..6b23a4f9ae71e 100644 --- a/src/Features/CSharp/Portable/Structure/CSharpStructureHelpers.cs +++ b/src/Features/CSharp/Portable/Structure/CSharpStructureHelpers.cs @@ -352,11 +352,10 @@ static SyntaxToken GetEndToken(SyntaxNode node) { return propertyDeclaration.Modifiers.FirstOrNull() ?? propertyDeclaration.Type.GetFirstToken(); } - else if (node.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) - || node.IsKind(SyntaxKind.RecordDeclaration, out typeDeclaration) - || node.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration) - || node.IsKind(SyntaxKind.InterfaceDeclaration, out typeDeclaration)) + else if (node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.RecordDeclaration or + SyntaxKind.RecordStructDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration) { + var typeDeclaration = (TypeDeclarationSyntax)node; return typeDeclaration.Modifiers.FirstOrNull() ?? typeDeclaration.Keyword; } else @@ -371,11 +370,10 @@ static SyntaxToken GetHintTextEndToken(SyntaxNode node) { return enumDeclaration.OpenBraceToken.GetPreviousToken(); } - else if (node.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) - || node.IsKind(SyntaxKind.RecordDeclaration, out typeDeclaration) - || node.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration) - || node.IsKind(SyntaxKind.InterfaceDeclaration, out typeDeclaration)) + else if (node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.RecordDeclaration or + SyntaxKind.RecordStructDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.InterfaceDeclaration) { + var typeDeclaration = (TypeDeclarationSyntax)node; return typeDeclaration.OpenBraceToken.GetPreviousToken(); } else diff --git a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs index 662d91d4751f0..5d9fdf7a35864 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs @@ -312,6 +312,7 @@ public async Task GetFullyQualifiedNameAsync(Solution solution, Document case SymbolDisplayPartKind.ErrorTypeName: case SymbolDisplayPartKind.InterfaceName: case SymbolDisplayPartKind.StructName: + case SymbolDisplayPartKind.RecordStructName: actualBuilder.Append('+'); break; @@ -328,7 +329,8 @@ public async Task GetFullyQualifiedNameAsync(Solution solution, Document previousWasClass = part.Kind == SymbolDisplayPartKind.ClassName || part.Kind == SymbolDisplayPartKind.RecordClassName || part.Kind == SymbolDisplayPartKind.InterfaceName || - part.Kind == SymbolDisplayPartKind.StructName; + part.Kind == SymbolDisplayPartKind.StructName || + part.Kind == SymbolDisplayPartKind.RecordStructName; } return actualBuilder.ToString(); diff --git a/src/Features/Core/Portable/Common/SymbolDisplayPartKindTags.cs b/src/Features/Core/Portable/Common/SymbolDisplayPartKindTags.cs index 54a95beca287f..410a0b7fb928f 100644 --- a/src/Features/Core/Portable/Common/SymbolDisplayPartKindTags.cs +++ b/src/Features/Core/Portable/Common/SymbolDisplayPartKindTags.cs @@ -43,6 +43,7 @@ public static string GetTag(SymbolDisplayPartKind kind) SymbolDisplayPartKind.ExtensionMethodName => TextTags.ExtensionMethod, SymbolDisplayPartKind.ConstantName => TextTags.Constant, SymbolDisplayPartKind.RecordClassName => TextTags.Record, + SymbolDisplayPartKind.RecordStructName => TextTags.RecordStruct, _ => string.Empty, }; } diff --git a/src/Features/Core/Portable/Common/TaggedText.cs b/src/Features/Core/Portable/Common/TaggedText.cs index dba173e80f714..ac1560445d897 100644 --- a/src/Features/Core/Portable/Common/TaggedText.cs +++ b/src/Features/Core/Portable/Common/TaggedText.cs @@ -230,6 +230,9 @@ public static string ToClassificationTypeName(this string taggedTextTag) case TextTags.Record: return ClassificationTypeNames.RecordClassName; + case TextTags.RecordStruct: + return ClassificationTypeNames.RecordStructName; + case TextTags.ContainerStart: case TextTags.ContainerEnd: // These tags are not visible so classify them as whitespace diff --git a/src/Features/Core/Portable/Common/TextTags.cs b/src/Features/Core/Portable/Common/TextTags.cs index db749099cc6ea..6b726768ad041 100644 --- a/src/Features/Core/Portable/Common/TextTags.cs +++ b/src/Features/Core/Portable/Common/TextTags.cs @@ -44,6 +44,7 @@ public static class TextTags public const string ExtensionMethod = nameof(ExtensionMethod); public const string Constant = nameof(Constant); public const string Record = nameof(Record); + public const string RecordStruct = nameof(RecordStruct); /// /// Indicates the start of a text container. The elements after through (but not diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index d62edd3868059..5542acfb736be 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ const Microsoft.CodeAnalysis.TextTags.Record = "Record" -> string +const Microsoft.CodeAnalysis.TextTags.RecordStruct = "RecordStruct" -> string Microsoft.CodeAnalysis.Completion.CompletionItem.IsComplexTextEdit.get -> bool Microsoft.CodeAnalysis.Completion.CompletionItem.WithIsComplexTextEdit(bool isComplexTextEdit) -> Microsoft.CodeAnalysis.Completion.CompletionItem static Microsoft.CodeAnalysis.Completion.CompletionChange.Create(Microsoft.CodeAnalysis.Text.TextChange textChange, System.Collections.Immutable.ImmutableArray textChanges, int? newPosition = null, bool includesCommitCharacter = false) -> Microsoft.CodeAnalysis.Completion.CompletionChange diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs index f9f39b3d1d5d5..c987308a0c697 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeLocator.cs @@ -41,8 +41,10 @@ private class NodeLocator : AbstractNodeLocator case SyntaxKind.AttributeArgument: return GetStartPoint(text, (AttributeArgumentSyntax)node, part); case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.EnumDeclaration: return GetStartPoint(text, (BaseTypeDeclarationSyntax)node, part); case SyntaxKind.MethodDeclaration: @@ -89,8 +91,10 @@ private class NodeLocator : AbstractNodeLocator case SyntaxKind.AttributeArgument: return GetEndPoint(text, (AttributeArgumentSyntax)node, part); case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.EnumDeclaration: return GetEndPoint(text, (BaseTypeDeclarationSyntax)node, part); case SyntaxKind.MethodDeclaration: diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs index 26716a4b8856d..cc5a3f2436740 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.NodeNameGenerator.cs @@ -169,7 +169,9 @@ protected override void AppendNodeName(StringBuilder builder, SyntaxNode node) break; case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: // PROTOTYPE(record-structs): not sure how to test case SyntaxKind.InterfaceDeclaration: var typeDeclaration = (TypeDeclarationSyntax)node; builder.Append(typeDeclaration.Identifier.ValueText); diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs index 1c41e712d2b92..c8df01b305fec 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -88,6 +88,7 @@ private static bool IsNameableNode(SyntaxNode node) switch (node.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.DelegateDeclaration: @@ -102,6 +103,7 @@ private static bool IsNameableNode(SyntaxNode node) case SyntaxKind.OperatorDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return true; case SyntaxKind.VariableDeclarator: @@ -139,7 +141,7 @@ public override bool MatchesScope(SyntaxNode node, EnvDTE.vsCMElement scope) break; - case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassDeclaration: // PROTOTYPE(record-structs): no sure what this is for (and elsewhere in this file) if (scope == EnvDTE.vsCMElement.vsCMElementClass) { return true; diff --git a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs index 79bff73c599db..f96a666c8a024 100644 --- a/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs +++ b/src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs @@ -301,6 +301,7 @@ private static bool IsVerbatimStringToken(SyntaxToken token) SyntaxKind.ClassDeclaration => ClassificationTypeNames.ClassName, SyntaxKind.RecordDeclaration => ClassificationTypeNames.RecordClassName, SyntaxKind.StructDeclaration => ClassificationTypeNames.StructName, + SyntaxKind.RecordStructDeclaration => ClassificationTypeNames.RecordStructName, _ => null }; @@ -354,6 +355,7 @@ private static bool IsExtensionMethod(MethodDeclarationSyntax methodDeclaration) SyntaxKind.StructDeclaration => ClassificationTypeNames.StructName, SyntaxKind.InterfaceDeclaration => ClassificationTypeNames.InterfaceName, SyntaxKind.RecordDeclaration => ClassificationTypeNames.RecordClassName, + SyntaxKind.RecordStructDeclaration => ClassificationTypeNames.RecordStructName, _ => null, }; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs index 8d2ba875c3ba7..232b470c730b7 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpDeclarationComparer.cs @@ -118,7 +118,9 @@ public int Compare(SyntaxNode x, SyntaxNode y) case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: return Compare((BaseTypeDeclarationSyntax)x, (BaseTypeDeclarationSyntax)y); case SyntaxKind.ConversionOperatorDeclaration: @@ -358,7 +360,7 @@ private static int GetAccessibilityPrecedence(SyntaxTokenList modifiers, SyntaxN // All interface members are public return (int)Accessibility.Public; } - else if (node.Kind() == SyntaxKind.StructDeclaration || node.Kind() == SyntaxKind.ClassDeclaration) + else if (node.Kind() is SyntaxKind.StructDeclaration or SyntaxKind.ClassDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration) { // Members and nested types default to private return (int)Accessibility.Private; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 36f009884ee5c..b397349d6d43f 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -150,6 +150,7 @@ private static SyntaxNode AsNamespaceMember(SyntaxNode declaration) case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.RecordDeclaration: + case SyntaxKind.RecordStructDeclaration: return declaration; default: return null; @@ -1282,6 +1283,13 @@ private static SyntaxNode EnsureRecordDeclarationHasBody(SyntaxNode declaration) .WithOpenBraceToken(recordDeclaration.OpenBraceToken == default ? SyntaxFactory.Token(SyntaxKind.OpenBraceToken) : recordDeclaration.OpenBraceToken) .WithCloseBraceToken(recordDeclaration.CloseBraceToken == default ? SyntaxFactory.Token(SyntaxKind.CloseBraceToken) : recordDeclaration.CloseBraceToken); } + else if (declaration is RecordStructDeclarationSyntax recordStructDeclaration) + { + return recordStructDeclaration + .WithSemicolonToken(default) + .WithOpenBraceToken(recordStructDeclaration.OpenBraceToken == default ? SyntaxFactory.Token(SyntaxKind.OpenBraceToken) : recordStructDeclaration.OpenBraceToken) + .WithCloseBraceToken(recordStructDeclaration.CloseBraceToken == default ? SyntaxFactory.Token(SyntaxKind.CloseBraceToken) : recordStructDeclaration.CloseBraceToken); + } return declaration; } @@ -1473,6 +1481,7 @@ private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) return s_interfaceModifiers; case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return s_structModifiers; case SyntaxKind.MethodDeclaration: @@ -1967,6 +1976,7 @@ private SyntaxNode AsNodeLike(SyntaxNode existingNode, SyntaxNode newNode) case DeclarationKind.RecordClass: case DeclarationKind.Interface: case DeclarationKind.Struct: + case DeclarationKind.RecordStruct: case DeclarationKind.Enum: case DeclarationKind.Namespace: case DeclarationKind.CompilationUnit: diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs index 08baff5438364..38dcd702b90ff 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs @@ -79,7 +79,7 @@ public static MemberDeclarationSyntax GenerateNamedTypeDeclaration( var members = GetMembers(namedType).Where(s => s.Kind != SymbolKind.Property || PropertyGenerator.CanBeGenerated((IPropertySymbol)s)) .ToImmutableArray(); - if (namedType.IsRecord) + if (namedType.IsRecord && namedType.TypeKind is TypeKind.Class) { declaration = GenerateRecordMembers(service, options, (RecordDeclarationSyntax)declaration, members, cancellationToken); } @@ -170,8 +170,10 @@ private static MemberDeclarationSyntax RemoveAllMembers(MemberDeclarationSyntax return ((EnumDeclarationSyntax)declaration).WithMembers(default); case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: return ((TypeDeclarationSyntax)declaration).WithMembers(default); default: @@ -196,7 +198,9 @@ private static MemberDeclarationSyntax GetDeclarationSyntaxWithoutMembersWorker( TypeDeclarationSyntax typeDeclaration; if (namedType.IsRecord) { - typeDeclaration = SyntaxFactory.RecordDeclaration(SyntaxFactory.Token(SyntaxKind.RecordKeyword), namedType.Name.ToIdentifierToken()); + typeDeclaration = namedType.TypeKind is TypeKind.Class + ? SyntaxFactory.RecordDeclaration(SyntaxFactory.Token(SyntaxKind.RecordKeyword), namedType.Name.ToIdentifierToken()) + : SyntaxFactory.RecordStructDeclaration(SyntaxFactory.Token(SyntaxKind.RecordKeyword), namedType.Name.ToIdentifierToken()); } else { diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index 90e00243ec042..f28de456bd124 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -508,7 +508,9 @@ private static Accessibility GetAccessibility(SyntaxNode node, SyntaxTokenList m switch (node.Parent.Kind()) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: // Anything without modifiers is private if it's in a class/struct declaration. return Accessibility.Private; case SyntaxKind.InterfaceDeclaration: diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs index ca02f36c95ac4..a6457f3d5ab50 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs @@ -402,7 +402,7 @@ private static bool AccessMethodWithDynamicArgumentInsideStructConstructor(Membe { var constructor = memberAccess.Ancestors().OfType().SingleOrDefault(); - if (constructor == null || constructor.Parent.Kind() != SyntaxKind.StructDeclaration) + if (constructor == null || !constructor.Parent.IsKind(SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration)) { return false; } diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index 102627684df1c..feb5c07f7689a 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -3023,11 +3023,13 @@ public void TestMultiFieldDeclarations() }"); } - [Fact, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] - public void TestInsertMembersOnRecord_SemiColon() + [Theory, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] + [InlineData("record")] + [InlineData("record class")] + public void TestInsertMembersOnRecord_SemiColon(string typeKind) { var comp = Compile( -@"public record C; +$@"public {typeKind} C; "); var symbolC = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("C").First(); @@ -3035,7 +3037,28 @@ public void TestInsertMembersOnRecord_SemiColon() VerifySyntax( Generator.InsertMembers(declC, 0, Generator.FieldDeclaration("A", Generator.IdentifierName("T"))), -@"public record C +$@"public {typeKind} C +{{ + T A; +}}"); + } + + [Fact, WorkItem(48789, "https://github.com/dotnet/roslyn/issues/48789")] + public void TestInsertMembersOnRecordStruct_SemiColon() + { + var src = +@"public record struct C; +"; + var comp = CSharpCompilation.Create("test") + .AddReferences(TestMetadata.Net451.mscorlib) + .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(src, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview))); + + var symbolC = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("C").First(); + var declC = Generator.GetDeclaration(symbolC.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).First()); + + VerifySyntax( + Generator.InsertMembers(declC, 0, Generator.FieldDeclaration("A", Generator.IdentifierName("T"))), +@"public record struct C { T A; }"); diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs b/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs index acfdc86125492..7d7e6bdbe3061 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationExtensions.cs @@ -11,7 +11,7 @@ internal static class ClassificationExtensions { TypeKind.Class => type.IsRecord ? ClassificationTypeNames.RecordClassName : ClassificationTypeNames.ClassName, TypeKind.Module => ClassificationTypeNames.ModuleName, - TypeKind.Struct => ClassificationTypeNames.StructName, + TypeKind.Struct => type.IsRecord ? ClassificationTypeNames.RecordStructName : ClassificationTypeNames.StructName, TypeKind.Interface => ClassificationTypeNames.InterfaceName, TypeKind.Enum => ClassificationTypeNames.EnumName, TypeKind.Delegate => ClassificationTypeNames.DelegateName, diff --git a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs index cd41916f2b2a8..3f58888422123 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassificationTypeNames.cs @@ -44,6 +44,7 @@ public static class ClassificationTypeNames public const string InterfaceName = "interface name"; public const string ModuleName = "module name"; public const string StructName = "struct name"; + public const string RecordStructName = "record struct name"; public const string TypeParameterName = "type parameter name"; public const string FieldName = "field name"; diff --git a/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs b/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs index 1f7b3dc181bfc..c3426c37a9976 100644 --- a/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs +++ b/src/Workspaces/Core/Portable/Editing/DeclarationKind.cs @@ -42,5 +42,6 @@ public enum DeclarationKind RemoveAccessor, RaiseAccessor, RecordClass, + RecordStruct, } } diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt index 86a95dde75144..d38ec6de5d767 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt @@ -1,9 +1,11 @@ abstract Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.FixAllAsync(Microsoft.CodeAnalysis.CodeFixes.FixAllContext fixAllContext, Microsoft.CodeAnalysis.Document document, System.Collections.Immutable.ImmutableArray diagnostics) -> System.Threading.Tasks.Task const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordClassName = "record class name" -> string +const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.RecordStructName = "record struct name" -> string Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.CodeActions.CodeAction.CustomTags.set -> void Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider.DocumentBasedFixAllProvider() -> void +Microsoft.CodeAnalysis.Editing.DeclarationKind.RecordStruct = 30 -> Microsoft.CodeAnalysis.Editing.DeclarationKind Microsoft.CodeAnalysis.Host.IPersistentStorageService.GetStorageAsync(Microsoft.CodeAnalysis.Solution solution, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask Microsoft.CodeAnalysis.Project.GetSourceGeneratedDocumentAsync(Microsoft.CodeAnalysis.DocumentId documentId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask Microsoft.CodeAnalysis.Project.GetSourceGeneratedDocumentsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask> diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs index f2ef822ad2493..fdcee439963e4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/MemberDeclarationSyntaxExtensions.cs @@ -44,6 +44,7 @@ public static SyntaxToken GetNameToken(this MemberDeclarationSyntax member) case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).Identifier; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).Identifier; @@ -82,6 +83,7 @@ public static int GetArity(this MemberDeclarationSyntax member) case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).Arity; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).Arity; @@ -103,6 +105,7 @@ public static TypeParameterListSyntax GetTypeParameterList(this MemberDeclaratio case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).TypeParameterList; case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).TypeParameterList; @@ -189,6 +192,7 @@ public static MemberDeclarationSyntax WithAttributeLists( case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: return ((TypeDeclarationSyntax)member).WithAttributeLists(attributeLists); case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)member).WithAttributeLists(attributeLists); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index abf316d7a1e17..b245786d4db86 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1136,6 +1136,9 @@ private void AppendConstructors(SyntaxList members, Lis case StructDeclarationSyntax @struct: AppendConstructors(@struct.Members, constructors, cancellationToken); break; + case RecordStructDeclarationSyntax recordStruct: + AppendConstructors(recordStruct.Members, constructors, cancellationToken); + break; } } } @@ -1778,6 +1781,7 @@ public override bool CanHaveAccessibility(SyntaxNode declaration) case SyntaxKind.ClassDeclaration: case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: @@ -1914,6 +1918,8 @@ public override DeclarationKind GetDeclarationKind(SyntaxNode declaration) return DeclarationKind.RecordClass; case SyntaxKind.StructDeclaration: return DeclarationKind.Struct; + case SyntaxKind.RecordStructDeclaration: + return DeclarationKind.RecordStruct; case SyntaxKind.InterfaceDeclaration: return DeclarationKind.Interface; case SyntaxKind.EnumDeclaration: diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs index a946ffa7d3426..ef4cbce90fe0c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs @@ -73,6 +73,7 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, SyntaxKind.EnumDeclaration, }; @@ -82,6 +83,7 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; public static readonly ISet ClassInterfaceRecordTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) @@ -96,11 +98,13 @@ internal class SyntaxKindSet SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; public static readonly ISet StructOnlyTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.StructDeclaration, + SyntaxKind.RecordStructDeclaration, }; } }