diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 629edf6934c73..e3d71b5799150 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -18,9 +18,9 @@ 78da7776965b428ff31da8f1ff2cb073506212b7 - + https://github.com/dotnet/roslyn - 03a07d1dd606ce11d62c9a595041c4c2d44c39e3 + ca27d128f3533dc41a46b010b8e878916328f2e4 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index 1b6de43e50b2a..a252ec69f85ca 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,7 +23,7 @@ - 4.0.0-2.21327.4 + 4.0.0-2.21359.14 diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index 8dc8d46656081..14f0a90213d07 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -132,21 +132,24 @@ private BoundInterpolatedString BindUnconvertedInterpolatedStringToString(BoundU { // We have 4 possible lowering strategies, dependent on the contents of the string, in this order: // 1. The string is a constant value. We can just use the final value. - // 2. The WellKnownType DefaultInterpolatedStringHandler is available, and none of the interpolation holes contain an await expression. + // 2. The string is composed of 4 or fewer components that are all strings, we can lower to a call to string.Concat without a + // params array. This is very efficient as the runtime can allocate a buffer for the string with exactly the correct length and + // make no intermediate allocations. + // 3. The WellKnownType DefaultInterpolatedStringHandler is available, and none of the interpolation holes contain an await expression. // The builder is a ref struct, and we can guarantee the lifetime won't outlive the stack if the string doesn't contain any // awaits, but if it does we cannot use it. This builder is the only way that ref structs can be directly used as interpolation // hole components, which means that ref structs components and await expressions cannot be combined. It is already illegal for // the user to use ref structs in an async method today, but if that were to ever change, this would still need to be respected. // We also cannot use this method if the interpolated string appears within a catch filter, as the builder is disposable and we // cannot put a try/finally inside a filter block. - // 3. The string is composed entirely of components that are strings themselves. We can turn this into a single call to string.Concat. - // We prefer the builder over this because the builder can use pooling to avoid new allocations, while this call will potentially - // need to allocate a param array. - // 4. The string has heterogeneous data and either InterpolatedStringHandler is unavailable, or one of the holes contains an await + // 4. The string is composed of more than 4 components that are all strings themselves. We can turn this into a single + // call to string.Concat. We prefer the builder over this because the builder can use pooling to avoid new allocations, while this + // call will need to allocate a param array. + // 5. The string has heterogeneous data and either InterpolatedStringHandler is unavailable, or one of the holes contains an await // expression. This is turned into a call to string.Format. // - // We need to do the determination of 1, 2, or 3/4 up front, rather than in lowering, as it affects diagnostics (ref structs not being - // able to be used, for example). However, between 3 and 4, we don't need to know at this point, so that logic is deferred for lowering. + // We need to do the determination of 1, 2, 3, or 4/5 up front, rather than in lowering, as it affects diagnostics (ref structs not being + // able to be used, for example). However, between 4 and 5, we don't need to know at this point, so that logic is deferred for lowering. if (unconvertedInterpolatedString.ConstantValue is not null) { @@ -155,13 +158,21 @@ private BoundInterpolatedString BindUnconvertedInterpolatedStringToString(BoundU return constructWithData(BindInterpolatedStringParts(unconvertedInterpolatedString, diagnostics), data: null); } + // Case 2. Attempt to see if all parts are strings. + if (unconvertedInterpolatedString.Parts.Length <= 4 && + unconvertedInterpolatedString.Parts.All(p => p is BoundLiteral + or BoundStringInsert { Value: { Type: { SpecialType: SpecialType.System_String } }, Alignment: null, Format: null })) + { + return constructWithData(BindInterpolatedStringParts(unconvertedInterpolatedString, diagnostics), data: null); + } + if (tryBindAsHandlerType(out var result)) { - // Case 2 + // Case 3 return result; } - // The specifics of 3 vs 4 aren't necessary for this stage of binding. The only thing that matters is that every part needs to be convertible + // The specifics of 4 vs 5 aren't necessary for this stage of binding. The only thing that matters is that every part needs to be convertible // object. return constructWithData(BindInterpolatedStringParts(unconvertedInterpolatedString, diagnostics), data: null); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs index d9d88ab88e963..dc156979c39e3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs @@ -282,7 +282,10 @@ public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) _factory.Binary(BinaryOperatorKind.StringConcatenation, node.Type, result, part); } - if (length == 1) + // We need to ensure that the result of the interpolated string is not null. If the single part has a non-null constant value + // or is itself an interpolated string (which by proxy cannot be null), then there's nothing else that needs to be done. Otherwise, + // we need to test for null and ensure "" if it is. + if (length == 1 && result is not ({ Kind: BoundKind.InterpolatedString } or { ConstantValue: { IsString: true } })) { result = _factory.Coalesce(result!, _factory.StringLiteral("")); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 4159c6173ff92..1d37a8c5c1083 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -213,6 +213,61 @@ static void Main(string[] args) CompileAndVerify(source, expectedOutput: expectedOutput); } + [Fact] + public void OneLiteral() + { + string source = +@"using System; +class Program +{ + static void Main(string[] args) + { + Console.WriteLine( $""Hello"" ); + } +}"; + string expectedOutput = @"Hello"; + var verifier = CompileAndVerify(source, expectedOutput: expectedOutput); + verifier.VerifyIL("Program.Main", @" +{ + // Code size 11 (0xb) + .maxstack 1 + IL_0000: ldstr ""Hello"" + IL_0005: call ""void System.Console.WriteLine(string)"" + IL_000a: ret +} +"); + } + + [Fact] + public void OneInsert() + { + string source = +@"using System; +class Program +{ + static void Main(string[] args) + { + var hello = $""Hello""; + Console.WriteLine( $""{hello}"" ); + } +}"; + string expectedOutput = @"Hello"; + var verifier = CompileAndVerify(source, expectedOutput: expectedOutput); + verifier.VerifyIL("Program.Main", @" +{ + // Code size 20 (0x14) + .maxstack 2 + IL_0000: ldstr ""Hello"" + IL_0005: dup + IL_0006: brtrue.s IL_000e + IL_0008: pop + IL_0009: ldstr """" + IL_000e: call ""void System.Console.WriteLine(string)"" + IL_0013: ret +} +"); + } + [Fact] public void TwoInserts() { @@ -1202,6 +1257,164 @@ static void Main() ); } + [Fact, WorkItem(54702, "https://github.com/dotnet/roslyn/issues/54702")] + public void InterpolatedStringHandler_ConcatPreferencesForAllStringElements() + { + var code = @" +using System; +Console.WriteLine(TwoComponents()); +Console.WriteLine(ThreeComponents()); +Console.WriteLine(FourComponents()); +Console.WriteLine(FiveComponents()); + +string TwoComponents() +{ + string s1 = ""1""; + string s2 = ""2""; + return $""{s1}{s2}""; +} + +string ThreeComponents() +{ + string s1 = ""1""; + string s2 = ""2""; + string s3 = ""3""; + return $""{s1}{s2}{s3}""; +} + +string FourComponents() +{ + string s1 = ""1""; + string s2 = ""2""; + string s3 = ""3""; + string s4 = ""4""; + return $""{s1}{s2}{s3}{s4}""; +} + +string FiveComponents() +{ + string s1 = ""1""; + string s2 = ""2""; + string s3 = ""3""; + string s4 = ""4""; + string s5 = ""5""; + return $""{s1}{s2}{s3}{s4}{s5}""; +} +"; + + var handler = GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false); + + var verifier = CompileAndVerify(new[] { code, handler }, expectedOutput: @" +12 +123 +1234 +value:1 +value:2 +value:3 +value:4 +value:5 +"); + + verifier.VerifyIL("$.<
$>g__TwoComponents|0_0()", @" +{ + // Code size 18 (0x12) + .maxstack 2 + .locals init (string V_0) //s2 + IL_0000: ldstr ""1"" + IL_0005: ldstr ""2"" + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: call ""string string.Concat(string, string)"" + IL_0011: ret +} +"); + + verifier.VerifyIL("$.<
$>g__ThreeComponents|0_1()", @" +{ + // Code size 25 (0x19) + .maxstack 3 + .locals init (string V_0, //s2 + string V_1) //s3 + IL_0000: ldstr ""1"" + IL_0005: ldstr ""2"" + IL_000a: stloc.0 + IL_000b: ldstr ""3"" + IL_0010: stloc.1 + IL_0011: ldloc.0 + IL_0012: ldloc.1 + IL_0013: call ""string string.Concat(string, string, string)"" + IL_0018: ret +} +"); + + verifier.VerifyIL("$.<
$>g__FourComponents|0_2()", @" +{ + // Code size 32 (0x20) + .maxstack 4 + .locals init (string V_0, //s2 + string V_1, //s3 + string V_2) //s4 + IL_0000: ldstr ""1"" + IL_0005: ldstr ""2"" + IL_000a: stloc.0 + IL_000b: ldstr ""3"" + IL_0010: stloc.1 + IL_0011: ldstr ""4"" + IL_0016: stloc.2 + IL_0017: ldloc.0 + IL_0018: ldloc.1 + IL_0019: ldloc.2 + IL_001a: call ""string string.Concat(string, string, string, string)"" + IL_001f: ret +} +"); + + verifier.VerifyIL("$.<
$>g__FiveComponents|0_3()", @" +{ + // Code size 89 (0x59) + .maxstack 3 + .locals init (string V_0, //s1 + string V_1, //s2 + string V_2, //s3 + string V_3, //s4 + string V_4, //s5 + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_5) + IL_0000: ldstr ""1"" + IL_0005: stloc.0 + IL_0006: ldstr ""2"" + IL_000b: stloc.1 + IL_000c: ldstr ""3"" + IL_0011: stloc.2 + IL_0012: ldstr ""4"" + IL_0017: stloc.3 + IL_0018: ldstr ""5"" + IL_001d: stloc.s V_4 + IL_001f: ldloca.s V_5 + IL_0021: ldc.i4.0 + IL_0022: ldc.i4.5 + IL_0023: call ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" + IL_0028: ldloca.s V_5 + IL_002a: ldloc.0 + IL_002b: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" + IL_0030: ldloca.s V_5 + IL_0032: ldloc.1 + IL_0033: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" + IL_0038: ldloca.s V_5 + IL_003a: ldloc.2 + IL_003b: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" + IL_0040: ldloca.s V_5 + IL_0042: ldloc.3 + IL_0043: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" + IL_0048: ldloca.s V_5 + IL_004a: ldloc.s V_4 + IL_004c: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" + IL_0051: ldloca.s V_5 + IL_0053: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" + IL_0058: ret +} +"); + } + [Theory] [CombinatorialData] public void InterpolatedStringHandler_OverloadsAndBoolReturns(bool useDefaultParameters, bool useBoolReturns, bool constructorBoolArg) @@ -2948,15 +3161,14 @@ public void NestedInterpolatedStrings() var interpolatedStringBuilder = GetInterpolatedStringHandlerDefinition(includeSpanOverloads: false, useDefaultParameters: false, useBoolReturns: false); - var verifier = CompileAndVerify(new[] { source, interpolatedStringBuilder }, expectedOutput: @"value:value:1"); + var verifier = CompileAndVerify(new[] { source, interpolatedStringBuilder }, expectedOutput: @"value:1"); verifier.VerifyIL("", @" { - // Code size 55 (0x37) - .maxstack 4 + // Code size 32 (0x20) + .maxstack 3 .locals init (int V_0, //i - System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1, - System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_2) + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1) IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldloca.s V_1 @@ -2964,20 +3176,12 @@ .locals init (int V_0, //i IL_0005: ldc.i4.1 IL_0006: call ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" IL_000b: ldloca.s V_1 - IL_000d: ldloca.s V_2 - IL_000f: ldc.i4.0 - IL_0010: ldc.i4.1 - IL_0011: call ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" - IL_0016: ldloca.s V_2 - IL_0018: ldloc.0 - IL_0019: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" - IL_001e: ldloca.s V_2 - IL_0020: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_0025: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(string)"" - IL_002a: ldloca.s V_1 - IL_002c: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_0031: call ""void System.Console.WriteLine(string)"" - IL_0036: ret + IL_000d: ldloc.0 + IL_000e: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" + IL_0013: ldloca.s V_1 + IL_0015: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" + IL_001a: call ""void System.Console.WriteLine(string)"" + IL_001f: ret } "); } diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs index a996a41288d50..72efbf595644d 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.Cascading.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.ChangeSignature; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities.ChangeSignature; -using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ChangeSignature @@ -412,5 +411,26 @@ public override int M(string x, int newIntegerParameter, int y) }"; await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/53091"), Trait(Traits.Feature, Traits.Features.ChangeSignature)] + public async Task AddParameter_Cascade_Record() + { + var markup = @" +record $$BaseR(int A, int B); + +record DerivedR() : BaseR(0, 1);"; + var permutation = new AddedParameterOrExistingIndex[] + { + new(1), + new(new AddedParameter(null, "int", "C", CallSiteKind.Value, "3"), "int"), + new(0) + }; + var updatedCode = @" +record BaseR(int B, int C, int A); + +record DerivedR() : BaseR(1, 3, 0);"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs index 136592635bd9a..11fecb5f73ea8 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/AddParameterTests.cs @@ -1261,5 +1261,39 @@ public void M() }"; await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)] + [WorkItem(44558, "https://github.com/dotnet/roslyn/issues/44558")] + public async Task AddParameters_Record() + { + var markup = @" +/// +/// +/// +record $$R(int First, int Second, int Third) +{ + static R M() => new R(1, 2, 3); +} +"; + var updatedSignature = new AddedParameterOrExistingIndex[] + { + new(0), + new(2), + new(1), + new(new AddedParameter(null, "int", "Forth", CallSiteKind.Value, "12345"), "System.Int32") + }; + var updatedCode = @" +/// +/// +/// +/// +record R(int First, int Third, int Second, int Forth) +{ + static R M() => new R(1, 3, 2, 12345); +} +"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs b/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs index 90bf56923b963..0fb298efc3e11 100644 --- a/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs +++ b/src/EditorFeatures/CSharpTest/ChangeSignature/ReorderParametersTests.cs @@ -951,5 +951,29 @@ class D : C, I await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); } + + [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)] + public async Task ReorderParamTagsInDocComments_Record() + { + var markup = @" +/// +/// +/// +record $$R(int A, int B, int C) +{ + public static R Instance = new(0, 1, 2); +}"; + var permutation = new[] { 2, 1, 0 }; + var updatedCode = @" +/// +/// +/// +record R(int C, int B, int A) +{ + public static R Instance = new(2, 1, 0); +}"; + + await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: permutation, expectedUpdatedInvocationDocumentCode: updatedCode); + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs index 8a0a6aa11c25a..52cbea5299e13 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/XmlDocumentationCommentCompletionProviderTests.cs @@ -1012,5 +1012,27 @@ static void Goo() await VerifyItemExistsAsync(text, "term"); await VerifyItemExistsAsync(text, "description"); } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task RecordParam() + { + await VerifyItemsExistAsync(@" +/// $$ +public record Goo(string MyParameter); +", "param name=\"MyParameter\"", "typeparam name=\"T\""); + } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task RecordParamRef() + { + await VerifyItemsExistAsync(@" +/// +/// $$ +/// +public record Goo(string MyParameter); +", "paramref name=\"MyParameter\"", "typeparamref name=\"T\""); + } } } diff --git a/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs b/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs index eebb1502ff7bc..9f9adf39a2826 100644 --- a/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/DocumentationComments/CodeFixes/AddDocCommentNodesCodeFixProviderTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -785,5 +786,29 @@ public void Fizz(int i, int j, int k) {} await TestAsync(initial, expected); } + + [WorkItem(52738, "https://github.com/dotnet/roslyn/issues/52738")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)] + public async Task AddsParamTag_Record() + { + var initial = @" +/// +/// +/// +/// +record R(int [|First|], int Second, int Third); +"; + + var expected = @" +/// +/// +/// +/// +/// +/// +record R(int First, int Second, int Third); +"; + await TestAsync(initial, expected); + } } } diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb index ee1cc0729587b..c329a73fe8b35 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_XmlDoc.vb @@ -765,6 +765,25 @@ class c End Using End Function + + Public Async Function CommitParam_Record(showCompletionInArgumentLists As Boolean) As Task + Using state = TestStateFactory.CreateCSharpTestState( + , showCompletionInArgumentLists:=showCompletionInArgumentLists) + + state.SendInvokeCompletionList() + Await state.AssertCompletionSession() + Await state.AssertSelectedCompletionItem(displayText:="param name=""I""") + state.SendReturn() + Await state.AssertNoCompletionSession() + + ' /// Public Async Function CommitParamNoOpenAngle(showCompletionInArgumentLists As Boolean) As Task diff --git a/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb b/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb index 358f561f10ea0..0053746e98b52 100644 --- a/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ChangeSignature/AddParameterTests.vb @@ -825,5 +825,36 @@ End Class]]>.NormalizedValue() Await TestChangeSignatureViaCommandAsync(LanguageNames.VisualBasic, markup, updatedSignature:=permutation, expectedUpdatedInvocationDocumentCode:=updatedCode) End Function + + + + Public Async Function TestAddParameter_NoLastWhitespaceTrivia() As Task + + Dim markup = +''' +''' +Sub $$M(a As Integer) +End Sub +End Class]]>.NormalizedValue() + Dim permutation = + { + New AddedParameterOrExistingIndex(0), + New AddedParameterOrExistingIndex(New AddedParameter(Nothing, "Integer", "b", CallSiteKind.Value), "Integer") + } + + Dim updatedCode = + ''' + ''' + ''' + Sub M(a As Integer, b As Integer) + End Sub +End Class]]>.NormalizedValue() + + Await TestChangeSignatureViaCommandAsync(LanguageNames.VisualBasic, markup, updatedSignature:=permutation, expectedUpdatedInvocationDocumentCode:=updatedCode) + End Function End Class End Namespace diff --git a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs index 5ebd9cb87ab0b..e350faa680817 100644 --- a/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs +++ b/src/Features/CSharp/Portable/ChangeSignature/CSharpChangeSignatureService.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,7 +43,9 @@ internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureServ SyntaxKind.DelegateDeclaration, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, - SyntaxKind.LocalFunctionStatement); + SyntaxKind.LocalFunctionStatement, + SyntaxKind.RecordStructDeclaration, + SyntaxKind.RecordDeclaration); private static readonly ImmutableArray _declarationAndInvocableKinds = _declarationKinds.Concat(ImmutableArray.Create( @@ -85,7 +88,9 @@ internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureServ SyntaxKind.NameMemberCref, SyntaxKind.AnonymousMethodExpression, SyntaxKind.ParenthesizedLambdaExpression, - SyntaxKind.SimpleLambdaExpression); + SyntaxKind.SimpleLambdaExpression, + SyntaxKind.RecordStructDeclaration, + SyntaxKind.RecordDeclaration); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -270,7 +275,9 @@ public override async Task ChangeSignatureAsync( if (updatedNode.IsKind(SyntaxKind.MethodDeclaration) || updatedNode.IsKind(SyntaxKind.ConstructorDeclaration) || updatedNode.IsKind(SyntaxKind.IndexerDeclaration) || - updatedNode.IsKind(SyntaxKind.DelegateDeclaration)) + updatedNode.IsKind(SyntaxKind.DelegateDeclaration) || + updatedNode.IsKind(SyntaxKind.RecordStructDeclaration) || + updatedNode.IsKind(SyntaxKind.RecordDeclaration)) { var updatedLeadingTrivia = UpdateParamTagsInLeadingTrivia(document, updatedNode, declarationSymbol, signaturePermutation); if (updatedLeadingTrivia != default && !updatedLeadingTrivia.IsEmpty) @@ -286,6 +293,12 @@ public override async Task ChangeSignatureAsync( return method.WithParameterList(method.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation)); } + if (updatedNode is RecordDeclarationSyntax { ParameterList: not null } record) + { + var updatedParameters = UpdateDeclaration(record.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax); + return record.WithParameterList(record.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation)); + } + if (updatedNode.IsKind(SyntaxKind.LocalFunctionStatement, out LocalFunctionStatementSyntax? localFunction)) { var updatedParameters = UpdateDeclaration(localFunction.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax); @@ -745,13 +758,14 @@ private ImmutableArray UpdateParamTagsInLeadingTrivia(Document doc return GetPermutedDocCommentTrivia(document, node, permutedParamNodes); } - private static ImmutableArray VerifyAndPermuteParamNodes(IEnumerable paramNodes, ISymbol declarationSymbol, SignatureChange updatedSignature) + private ImmutableArray VerifyAndPermuteParamNodes(IEnumerable paramNodes, ISymbol declarationSymbol, SignatureChange updatedSignature) { // Only reorder if count and order match originally. var originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters(); var reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters(); - var declaredParameters = declarationSymbol.GetParameters(); + var declaredParameters = GetParameters(declarationSymbol); + if (paramNodes.Count() != declaredParameters.Length) { return ImmutableArray.Empty; @@ -875,5 +889,20 @@ protected override bool SupportsOptionalAndParamsArrayParametersSimultaneously() protected override SyntaxToken CommaTokenWithElasticSpace() => Token(SyntaxKind.CommaToken).WithTrailingTrivia(ElasticSpace); + + protected override bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) + => typeSymbol.TryGetRecordPrimaryConstructor(out primaryConstructor); + + protected override ImmutableArray GetParameters(ISymbol declarationSymbol) + { + var declaredParameters = declarationSymbol.GetParameters(); + if (declarationSymbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.TryGetRecordPrimaryConstructor(out var primaryConstructor)) + { + declaredParameters = primaryConstructor.Parameters; + } + + return declaredParameters; + } } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs index 57a8d38828ad3..f096d239f8fff 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -365,6 +366,18 @@ private string GetAttributeValue(XmlAttributeSyntax attribute) } } + protected override ImmutableArray GetParameters(ISymbol declarationSymbol) + { + var declaredParameters = declarationSymbol.GetParameters(); + if (declarationSymbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.TryGetRecordPrimaryConstructor(out var primaryConstructor)) + { + declaredParameters = primaryConstructor.Parameters; + } + + return declaredParameters; + } + private static readonly CompletionItemRules s_defaultRules = CompletionItemRules.Create( filterCharacterRules: FilterRules, diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 80ddbceb3bc97..dabab4bd296c8 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -74,6 +75,13 @@ public abstract Task ChangeSignatureAsync( /// protected abstract bool SupportsOptionalAndParamsArrayParametersSimultaneously(); + protected abstract bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor); + + /// + /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed. + /// + protected abstract ImmutableArray GetParameters(ISymbol declarationSymbol); + protected abstract SyntaxGenerator Generator { get; } protected abstract ISyntaxFacts SyntaxFacts { get; } @@ -124,6 +132,10 @@ internal async Task GetChangeSignatureContextAsy { symbol = typeSymbol.DelegateInvokeMethod; } + else if (TryGetRecordPrimaryConstructor(typeSymbol, out var primaryConstructor)) + { + symbol = primaryConstructor; + } } if (!symbol.MatchesKind(SymbolKind.Method, SymbolKind.Property)) @@ -164,7 +176,7 @@ internal async Task GetChangeSignatureContextAsy } var parameterConfiguration = ParameterConfiguration.Create( - symbol.GetParameters().Select(p => new ExistingParameter(p)).ToImmutableArray(), + GetParameters(symbol).Select(p => new ExistingParameter(p)).ToImmutableArray(), symbol.IsExtensionMethod(), selectedIndex); return new ChangeSignatureAnalysisSucceededContext( @@ -245,7 +257,7 @@ private static async Task> FindChangeSignatureR var symbols = await FindChangeSignatureReferencesAsync( declaredSymbol, context.Solution, cancellationToken).ConfigureAwait(false); - var declaredSymbolParametersCount = declaredSymbol.GetParameters().Length; + var declaredSymbolParametersCount = GetParameters(declaredSymbol).Length; var telemetryNumberOfDeclarationsToUpdate = 0; var telemetryNumberOfReferencesToUpdate = 0; @@ -441,14 +453,14 @@ private static bool TryGetNodeWithEditableSignatureOrAttributes(Location locatio return nodeToUpdate != null; } - protected static ImmutableArray PermuteArguments( + protected ImmutableArray PermuteArguments( ISymbol declarationSymbol, ImmutableArray arguments, SignatureChange updatedSignature, bool isReducedExtensionMethod = false) { // 1. Determine which parameters are permutable - var declarationParameters = declarationSymbol.GetParameters(); + var declarationParameters = GetParameters(declarationSymbol); var declarationParametersToPermute = GetParametersToPermute(arguments, declarationParameters, isReducedExtensionMethod); var argumentsToPermute = arguments.Take(declarationParametersToPermute.Length).ToList(); @@ -552,14 +564,14 @@ protected static ImmutableArray PermuteArguments( /// delegate Invoke methods (m) and delegate BeginInvoke methods (n = m + 2). This method adds on those extra parameters /// to the base . /// - private static SignatureChange UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(ISymbol declarationSymbol, SignatureChange updatedSignature) + private SignatureChange UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(ISymbol declarationSymbol, SignatureChange updatedSignature) { - if (declarationSymbol.GetParameters().Length > updatedSignature.OriginalConfiguration.ToListOfParameters().Length) + var realParameters = GetParameters(declarationSymbol); + if (realParameters.Length > updatedSignature.OriginalConfiguration.ToListOfParameters().Length) { var originalConfigurationParameters = updatedSignature.OriginalConfiguration.ToListOfParameters(); var updatedConfigurationParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters(); - var realParameters = declarationSymbol.GetParameters(); var bonusParameters = realParameters.Skip(originalConfigurationParameters.Length); var originalConfigurationParametersWithExtraParameters = originalConfigurationParameters.AddRange(bonusParameters.Select(p => new ExistingParameter(p))); @@ -759,13 +771,14 @@ protected virtual async Task> AddNewArgumentsToL if (updatedParameters[i] != signaturePermutation.UpdatedConfiguration.ThisParameter || !isReducedExtensionMethod) { + var parameters = GetParameters(declarationSymbol); if (updatedParameters[i] is AddedParameter addedParameter) { // Omitting an argument only works in some languages, depending on whether // there is a params array. We sometimes need to reinterpret an requested // omitted parameter as one with a TODO requested. var forcedCallsiteErrorDueToParamsArray = addedParameter.CallSiteKind == CallSiteKind.Omitted && - declarationSymbol.GetParameters().LastOrDefault()?.IsParams == true && + parameters.LastOrDefault()?.IsParams == true && !SupportsOptionalAndParamsArrayParametersSimultaneously(); var isCallsiteActuallyOmitted = addedParameter.CallSiteKind == CallSiteKind.Omitted && !forcedCallsiteErrorDueToParamsArray; @@ -808,7 +821,6 @@ protected virtual async Task> AddNewArgumentsToL } else { - var parameters = declarationSymbol.GetParameters(); if (indexInListOfPreexistingArguments == parameters.Length - 1 && parameters[indexInListOfPreexistingArguments].IsParams) { @@ -1001,7 +1013,6 @@ protected ImmutableArray GetPermutedDocCommentTrivia(Document docu node.GetTrailingTrivia(), lastWhiteSpaceTrivia, document.Project.Solution.Options.GetOption(FormattingOptions.NewLine, document.Project.Language)); - var newTrivia = Generator.Trivia(extraDocComments); updatedLeadingTrivia.Add(newTrivia); diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs index 84866db580dd2..06471715b2b8b 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractDocCommentCompletionProvider.cs @@ -97,6 +97,13 @@ public override async Task ProvideCompletionsAsync(CompletionContext context) protected abstract IEnumerable GetExistingTopLevelAttributeValues(TSyntax syntax, string tagName, string attributeName); + protected abstract IEnumerable GetKeywordNames(); + + /// + /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed. + /// + protected abstract ImmutableArray GetParameters(ISymbol symbol); + private CompletionItem GetItem(string name) { if (s_tagMap.TryGetValue(name, out var values)) @@ -152,7 +159,7 @@ protected IEnumerable GetNestedItems(ISymbol symbol, bool includ private IEnumerable GetParamRefItems(ISymbol symbol) { - var names = symbol.GetParameters().Select(p => p.Name); + var names = GetParameters(symbol).Select(p => p.Name); return names.Select(p => CreateCompletionItem( displayText: FormatParameter(ParameterReferenceElementName, p), @@ -176,7 +183,7 @@ protected IEnumerable GetAttributeValueItems(ISymbol symbol, str { if (tagName is ParameterElementName or ParameterReferenceElementName) { - return symbol.GetParameters() + return GetParameters(symbol) .Select(parameter => CreateCompletionItem(parameter.Name)); } else if (tagName == TypeParameterElementName) @@ -202,8 +209,6 @@ protected IEnumerable GetAttributeValueItems(ISymbol symbol, str return SpecializedCollections.EmptyEnumerable(); } - protected abstract IEnumerable GetKeywordNames(); - protected ImmutableArray GetTopLevelItems(ISymbol symbol, TSyntax syntax) { using var _1 = ArrayBuilder.GetInstance(out var items); @@ -216,7 +221,7 @@ protected ImmutableArray GetTopLevelItems(ISymbol symbol, TSynta if (symbol != null) { - items.AddRange(GetParameterItems(symbol.GetParameters(), syntax, ParameterElementName)); + items.AddRange(GetParameterItems(GetParameters(symbol), syntax, ParameterElementName)); items.AddRange(GetParameterItems(symbol.GetTypeParameters(), syntax, TypeParameterElementName)); if (symbol is IPropertySymbol && !existingTopLevelTags.Contains(ValueElementName)) diff --git a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb index da9d187d8a6a3..ce65e2060dbc7 100644 --- a/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb +++ b/src/Features/VisualBasic/Portable/ChangeSignature/VisualBasicChangeSignatureService.vb @@ -604,13 +604,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Return GetPermutedDocCommentTrivia(document, node, permutedParamNodes) End Function - Private Shared Function VerifyAndPermuteParamNodes(paramNodes As ImmutableArray(Of XmlElementSyntax), declarationSymbol As ISymbol, updatedSignature As SignatureChange) As ImmutableArray(Of SyntaxNode) + Private Function VerifyAndPermuteParamNodes(paramNodes As ImmutableArray(Of XmlElementSyntax), declarationSymbol As ISymbol, updatedSignature As SignatureChange) As ImmutableArray(Of SyntaxNode) ' Only reorder if count and order match originally. Dim originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters() Dim reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters() - Dim declaredParameters = declarationSymbol.GetParameters() + Dim declaredParameters = GetParameters(declarationSymbol) If paramNodes.Length <> declaredParameters.Length Then Return ImmutableArray(Of SyntaxNode).Empty End If @@ -755,5 +755,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ChangeSignature Protected Overrides Function CommaTokenWithElasticSpace() As SyntaxToken Return Token(SyntaxKind.CommaToken).WithTrailingTrivia(ElasticSpace) End Function + + Protected Overrides Function TryGetRecordPrimaryConstructor(typeSymbol As INamedTypeSymbol, ByRef primaryConstructor As IMethodSymbol) As Boolean + Return False + End Function + + Protected Overrides Function GetParameters(declarationSymbol As ISymbol) As ImmutableArray(Of IParameterSymbol) + Return declarationSymbol.GetParameters() + End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb index 29c5befe0a147..192e23a5d194a 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/XmlDocCommentCompletionProvider.vb @@ -338,6 +338,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return nameSyntax?.LocalName.ValueText End Function + Protected Overrides Function GetParameters(symbol As ISymbol) As ImmutableArray(Of IParameterSymbol) + Return symbol.GetParameters() + End Function + Private Shared ReadOnly s_defaultRules As CompletionItemRules = CompletionItemRules.Create( filterCharacterRules:=FilterRules, diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs index 2a525df7db468..448311407c50b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphQueries/SearchGraphQuery.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.PooledObjects; @@ -38,8 +39,11 @@ public SearchGraphQuery( public Task GetGraphAsync(Solution solution, IGraphContext context, CancellationToken cancellationToken) { + var experimentationService = solution.Workspace.Services.GetService(); + var forceLegacySearch = experimentationService?.IsExperimentEnabled(WellKnownExperimentNames.ProgressionForceLegacySearch) == true; + var option = solution.Options.GetOption(ProgressionOptions.SearchUsingNavigateToEngine); - return option + return !forceLegacySearch && option ? SearchUsingNavigateToEngineAsync(solution, context, cancellationToken) : SearchUsingSymbolsAsync(solution, context, cancellationToken); } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptions.cs b/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptions.cs index 94a3c73b535cf..4e9eeb23cbae7 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptions.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/ProgressionOptions.cs @@ -13,7 +13,7 @@ internal static class ProgressionOptions private const string LocalRegistryPath = @"Roslyn\Internal\OnOff\Components\Progression\"; public static readonly Option2 SearchUsingNavigateToEngine = new( - nameof(ProgressionOptions), nameof(SearchUsingNavigateToEngine), defaultValue: false, + nameof(ProgressionOptions), nameof(SearchUsingNavigateToEngine), defaultValue: true, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "SearchUsingNavigateToEngine")); } } diff --git a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb index ff84d6ee40eb9..4641ed11ca716 100644 --- a/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb +++ b/src/VisualStudio/Core/Test/Progression/SearchGraphQueryTests.vb @@ -25,6 +25,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -59,6 +60,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="F", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -95,6 +97,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Progression ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="M", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -144,6 +147,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="C", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -194,6 +198,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="Goo", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -243,6 +248,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="Z", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -284,6 +290,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -320,6 +327,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="C.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -344,6 +352,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -380,6 +389,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) @@ -416,6 +426,7 @@ End Namespace ) + testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption(ProgressionOptions.SearchUsingNavigateToEngine, False)) Dim threadingContext = testState.Workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim outputContext = Await testState.GetGraphContextAfterQuery( New Graph(), New SearchGraphQuery(searchPattern:="A.D.B", threadingContext, AsynchronousOperationListenerProvider.NullListener), GraphContextDirection.Custom) diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index ceaff08c190dc..b0e04e3c7cfe1 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -87,10 +87,13 @@ internal override SyntaxNode DocumentationCommentTrivia(IEnumerable SyntaxFactory.List(nodes), SyntaxFactory.Token(SyntaxKind.EndOfDocumentationCommentToken)); - return docTrivia - .WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")) - .WithTrailingTrivia(trailingTrivia) - .WithTrailingTrivia( + docTrivia = docTrivia.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")) + .WithTrailingTrivia(trailingTrivia); + + if (lastWhitespaceTrivia == default) + return docTrivia.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString)); + + return docTrivia.WithTrailingTrivia( SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia); } diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index 95cd7afd749c7..e4845a33961f1 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -45,5 +45,6 @@ internal static class WellKnownExperimentNames public const string LspPullDiagnosticsFeatureFlag = "Lsp.PullDiagnostics"; public const string OOPCoreClr = "Roslyn.OOPCoreClr"; public const string DisableAsynchronousQuickActions = "Roslyn.DisableAsynchronousQuickActions"; + public const string ProgressionForceLegacySearch = "Roslyn.ProgressionForceLegacySearch"; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs index 9b0cadc0c9d4c..73c1a9ff9649d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeSymbolExtensions.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -177,5 +179,26 @@ public static bool IsIntrinsicType(this ITypeSymbol typeSymbol) return false; } } + + public static bool TryGetRecordPrimaryConstructor(this INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor) + { + if (typeSymbol.IsRecord) + { + Debug.Assert(typeSymbol.GetParameters().IsDefaultOrEmpty, "If GetParameters extension handles record, we can remove the handling here."); + + // A bit hacky to determine the parameters of primary constructor associated with a given record. + // Simplifying is tracked by: https://github.com/dotnet/roslyn/issues/53092. + // Note: When the issue is handled, we can remove the logic here and handle things in GetParameters extension. BUT + // if GetParameters extension method gets updated to handle records, we need to test EVERY usage + // of the extension method and make sure the change is applicable to all these usages. + + primaryConstructor = typeSymbol.InstanceConstructors.FirstOrDefault( + c => c.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is RecordDeclarationSyntax); + return primaryConstructor is not null; + } + + primaryConstructor = null; + return false; + } } } diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 9d1659d957012..4bf5bf06f207b 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -67,9 +67,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Friend Overrides Function DocumentationCommentTrivia(nodes As IEnumerable(Of SyntaxNode), trailingTrivia As SyntaxTriviaList, lastWhitespaceTrivia As SyntaxTrivia, endOfLineString As String) As SyntaxNode Dim node = SyntaxFactory.DocumentationCommentTrivia(SyntaxFactory.List(nodes)) - Return node.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExteriorTrivia("''' ")). - WithTrailingTrivia(node.GetTrailingTrivia()). - WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia) + node = node.WithLeadingTrivia(SyntaxFactory.DocumentationCommentExteriorTrivia("''' ")). + WithTrailingTrivia(node.GetTrailingTrivia()) + + If lastWhitespaceTrivia = Nothing Then + Return node.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString)) + End If + + Return node.WithTrailingTrivia(SyntaxFactory.EndOfLine(endOfLineString), lastWhitespaceTrivia) End Function Friend Overrides Function DocumentationCommentTriviaWithUpdatedContent(trivia As SyntaxTrivia, content As IEnumerable(Of SyntaxNode)) As SyntaxNode