From e09ecd082953e5a88462496235f614241483deda Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 29 Jul 2022 12:37:13 +0200 Subject: [PATCH 001/274] Move IteratorTests.vb to new testing library --- .../Tests/Iterator/IteratorTests.vb | 103 +++++++++++------- 1 file changed, 65 insertions(+), 38 deletions(-) diff --git a/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb b/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb index e45308c4a5def..e5aadafc91e8b 100644 --- a/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb +++ b/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb @@ -5,25 +5,41 @@ Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics +Imports Microsoft.CodeAnalysis.Testing Imports Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator +Imports VerifyConvertToIterator = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator.VisualBasicConvertToIteratorCodeFixProvider) +Imports VerifyConvertToYield = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator.VisualBasicChangeToYieldCodeFixProvider) + Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings.Iterator + Public Class ConvertToIteratorTests - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + Private Shared Async Function VerifyCodeFixAsync(code As String, fixedCode As String) As Task + Await New VerifyConvertToIterator.Test With + { + .TestCode = code, + .FixedCode = fixedCode, + .CodeActionValidationMode = CodeActionValidationMode.None + }.RunAsync() + End Function - Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (Nothing, New VisualBasicConvertToIteratorCodeFixProvider()) + Private Shared Async Function VerifyNoCodeFixAsync(code As String) As Task + Await New VerifyConvertToIterator.Test With + { + .TestCode = code, + .FixedCode = code + }.RunAsync() End Function Public Async Function TestConvertToIteratorFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Imports System Imports System.Collections.Generic Module Module1 Function M() As IEnumerable(Of Integer) - [|Yield|] 1 + {|BC30451:Yield|} {|BC30800:1 |} End Function End Module", "Imports System @@ -38,24 +54,24 @@ End Module") Public Async Function TestConvertToIteratorSub() As Task - Await TestMissingInRegularAndScriptAsync( + Await VerifyNoCodeFixAsync( "Module Module1 - Sub M() As - [|Yield|] 1 + Sub M() {|BC30205:As|} + {|BC30451:Yield|} {|BC30800:1 |} End Sub End Module") End Function Public Async Function TestConvertToIteratorFunctionLambda() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Imports System Imports System.Collections.Generic Module Module1 Sub M() Dim a As Func(Of IEnumerable(Of Integer)) = Function() - [|Yield|] 0 + {|BC30451:Yield|} {|BC30800:0 |} End Function End Sub End Module", @@ -73,14 +89,14 @@ End Module") Public Async Function TestConvertToIteratorSubLambda() As Task - Await TestMissingInRegularAndScriptAsync( + Await VerifyNoCodeFixAsync( "Imports System Imports System.Collections.Generic Module Module1 Sub M() Dim a As Func(Of IEnumerable(Of Integer)) = Sub() - [|Yield|] 0 + {|BC30451:Yield|} {|BC30800:1 |} End Sub End Sub End Module") @@ -88,48 +104,59 @@ End Module") Public Async Function TestConvertToIteratorSingleLineFunctionLambda() As Task - Await TestMissingInRegularAndScriptAsync( + Await VerifyNoCodeFixAsync( "Imports System Imports System.Collections.Generic Module Module1 Sub M() - Dim a As Func(Of IEnumerable(Of Integer)) = Function() [|Yield|] 0 + Dim a As Func(Of IEnumerable(Of Integer)) = Function() {|BC30451:Yield|} {|BC30205:1|} End Sub End Module") End Function Public Async Function TestConvertToIteratorSingleLineSubLambda() As Task - Await TestMissingInRegularAndScriptAsync( + Await VerifyNoCodeFixAsync( "Imports System Imports System.Collections.Generic Module Module1 Sub M() - Dim a As Func(Of IEnumerable(Of Integer)) = Sub() [|Yield|] 0 + Dim a As Func(Of IEnumerable(Of Integer)) = Sub() {|BC30451:Yield|} {|BC30800:1 |} End Sub End Module") End Function End Class Public Class ChangeToYieldTests - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + Private Shared Async Function VerifyCodeFixAsync(code As String, fixedCode As String) As Task + Await New VerifyConvertToYield.Test With + { + .TestCode = code, + .FixedCode = fixedCode + }.RunAsync() + End Function - Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (Nothing, New VisualBasicChangeToYieldCodeFixProvider()) + Private Shared Async Function VerifyNoCodeFixAsync(code As String) As Task + Await New VerifyConvertToYield.Test With + { + .TestCode = code, + .FixedCode = code + }.RunAsync() End Function + Public Async Function TestChangeToYieldCodeFixProviderFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Module Module1 - Iterator Function M() As IEnumerable(Of Integer) - [|Return|] 1 + Iterator Function M() As {|BC30002:IEnumerable(Of Integer)|} + {|BC36942:Return 1|} End Function End Module", "Module Module1 - Iterator Function M() As IEnumerable(Of Integer) + Iterator Function M() As {|BC30002:IEnumerable(Of Integer)|} Yield 1 End Function End Module") @@ -137,14 +164,14 @@ End Module") Public Async Function TestChangeToYieldCodeFixProviderSub() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Module Module1 - Iterator Sub M() - [|Return|] 1 + Iterator {|BC36938:Sub|} M() + {|BC36942:Return 1|} End Sub End Module", "Module Module1 - Iterator Sub M() + Iterator {|BC36938:Sub|} M() Yield 1 End Sub End Module") @@ -152,11 +179,11 @@ End Module") Public Async Function TestChangeToYieldCodeFixProviderFunctionLambda() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Module Module1 Sub M() Dim a = Iterator Function() - [|Return|] 0 + {|BC36942:Return 0|} End Function End Sub End Module", @@ -171,17 +198,17 @@ End Module") Public Async Function TestChangeToYieldCodeFixProviderSubLambda() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Module Module1 Sub M() - Dim a = Iterator Sub() - [|Return|] 0 + Dim a = Iterator {|BC36938:Sub|}() + {|BC36942:Return 0|} End Sub End Sub End Module", "Module Module1 Sub M() - Dim a = Iterator Sub() + Dim a = Iterator {|BC36938:Sub|}() Yield 0 End Sub End Sub @@ -190,24 +217,24 @@ End Module") Public Async Function TestChangeToYieldCodeFixProviderSingleLineFunctionLambda() As Task - Await TestMissingInRegularAndScriptAsync("Module Module1 + Await VerifyNoCodeFixAsync("Module Module1 Sub M() - Dim a = Iterator Function() [|Return|] 0 + Dim a = {|BC36947:Iterator Function() |}{|BC30201:|}Return 0 End Sub End Module") End Function Public Async Function TestChangeToYieldCodeFixProviderSingleLineSubLambda() As Task - Await TestInRegularAndScriptAsync( + Await VerifyCodeFixAsync( "Module Module1 Sub M() - Dim a = Iterator Sub() [|Return|] 0 + Dim a = Iterator {|BC36938:Sub|}() {|BC36942:Return 0|} End Sub End Module", "Module Module1 Sub M() - Dim a = Iterator Sub() Yield 0 + Dim a = Iterator {|BC36938:Sub|}() Yield 0 End Sub End Module") End Function From 72bd817491c92fd6726d55a435e6574f1adc6a23 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 29 Jul 2022 13:10:39 +0200 Subject: [PATCH 002/274] Move add required parentheses to new testing library --- .../AddRequiredParenthesesTests.vb | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/src/Analyzers/VisualBasic/Tests/AddRequiredParentheses/AddRequiredParenthesesTests.vb b/src/Analyzers/VisualBasic/Tests/AddRequiredParentheses/AddRequiredParenthesesTests.vb index a625944175bbe..9e229a98301df 100644 --- a/src/Analyzers/VisualBasic/Tests/AddRequiredParentheses/AddRequiredParenthesesTests.vb +++ b/src/Analyzers/VisualBasic/Tests/AddRequiredParentheses/AddRequiredParenthesesTests.vb @@ -8,162 +8,204 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.VisualBasic.AddRequiredParentheses +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of Microsoft.CodeAnalysis.VisualBasic.AddRequiredParentheses.VisualBasicAddRequiredParenthesesForBinaryLikeExpressionDiagnosticAnalyzer, Microsoft.CodeAnalysis.AddRequiredParentheses.AddRequiredParenthesesCodeFixProvider) + Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.AddRequiredParentheses Partial Public Class AddRequiredParenthesesTests - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + Private Const RequireAllParenthesesForClarity As String = "[*] +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +" + Private Const RequireOtherBinaryParenthesesForClarity As String = "[*] +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +" + Private Const RequireArithmeticBinaryParenthesesForClarity As String = "[*] +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +" + Private Shared Async Function VerifyCodeFixAsync(code As String, fixedCode As String, editorConfig As String) As Task + Await New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = fixedCode, + .EditorConfig = editorConfig + }.RunAsync() + End Function - Friend Overrides Function CreateDiagnosticProviderAndFixer(Workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (New VisualBasicAddRequiredParenthesesForBinaryLikeExpressionDiagnosticAnalyzer(), New AddRequiredParenthesesCodeFixProvider()) + Private Shared Async Function VerifyNoCodeFixAsync(code As String, editorConfig As String) As Task + Await New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = code, + .EditorConfig = editorConfig + }.RunAsync() End Function Public Async Function TestArithmeticPrecedence() As Task - Await TestInRegularAndScript1Async( + Await VerifyCodeFixAsync( "class C sub M() - dim x = 1 + 2 $$* 3 + dim x = 1 + 2 [|*|] 3 end sub end class", "class C sub M() dim x = 1 + (2 * 3) end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNoArithmeticOnLowerPrecedence() As Task - Await TestMissingAsync( + Await VerifyCodeFixAsync( "class C sub M() - dim x = 1 $$+ 2 * 3 + dim x = 1 + 2 [|*|] 3 end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", +"class C + sub M() + dim x = 1 + (2 * 3) + end sub +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNotIfArithmeticPrecedenceStaysTheSame() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = 1 + 2 $$+ 3 + dim x = 1 + 2 + 3 end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNotIfArithmeticPrecedenceIsNotEnforced1() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = 1 + 2 $$+ 3 + dim x = 1 + 2 + 3 end sub -end class", parameters:=New TestParameters(options:=RequireOtherBinaryParenthesesForClarity)) +end class", RequireOtherBinaryParenthesesForClarity) End Function Public Async Function TestNotIfArithmeticPrecedenceIsNotEnforced2() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = 1 + 2 $$* 3 + dim x = 1 + 2 * 3 end sub -end class", parameters:=New TestParameters(options:=RequireOtherBinaryParenthesesForClarity)) +end class", RequireOtherBinaryParenthesesForClarity) End Function Public Async Function TestLogicalPrecedence() As Task - Await TestInRegularAndScript1Async( + Await VerifyCodeFixAsync( "class C sub M() - dim x = a orelse b $$andalso c + dim x = {|BC30451:a|} orelse {|BC30451:b|} [|andalso|] {|BC30109:c|} end sub end class", "class C sub M() - dim x = a orelse (b andalso c) + dim x = {|BC30451:a|} orelse ({|BC30451:b|} andalso {|BC30109:c|}) end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNoLogicalOnLowerPrecedence() As Task - Await TestMissingAsync( + Await VerifyCodeFixAsync( +"class C + sub M() + dim x = {|BC30451:a|} orelse {|BC30451:b|} [|andalso|] {|BC30109:c|} + end sub +end class", "class C sub M() - dim x = a $$orelse b andalso c + dim x = {|BC30451:a|} orelse ({|BC30451:b|} andalso {|BC30109:c|}) end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNotIfLogicalPrecedenceStaysTheSame() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - int x = a orelse b $$orelse c + {|BC30451:int|} {|BC30800:{|BC30451:x|} = {|BC30451:a|} orelse {|BC30451:b|} orelse {|BC30109:c|}|} end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestNotIfLogicalPrecedenceIsNotEnforced() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = a orelse b $$orelse c + dim x = {|BC30451:a|} orelse {|BC30451:b|} orelse {|BC30109:c|} end sub -end class", parameters:=New TestParameters(options:=RequireArithmeticBinaryParenthesesForClarity)) +end class", RequireArithmeticBinaryParenthesesForClarity) End Function Public Async Function TestMixedArithmeticAndLogical() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = a = b $$andalso c = d + dim x = {|BC30451:a|} = {|BC30451:b|} andalso {|BC30109:c|} = {|BC30451:d|} end sub -end class", New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestShiftPrecedence1() As Task - Await TestInRegularAndScript1Async( + Await VerifyCodeFixAsync( "class C sub M() - dim x = 1 $$+ 2 << 3 + dim x = 1 [|+|] 2 << 3 end sub end class", "class C sub M() dim x = (1 + 2) << 3 end sub -end class", parameters:=New TestParameters(options:=RequireAllParenthesesForClarity)) +end class", RequireAllParenthesesForClarity) End Function Public Async Function TestShiftPrecedence2() As Task - Await TestInRegularAndScript1Async( + Await VerifyCodeFixAsync( "class C sub M() - dim x = 1 $$+ 2 << 3 + dim x = 1 [|+|] 2 << 3 end sub end class", "class C sub M() dim x = (1 + 2) << 3 end sub -end class", parameters:=New TestParameters(options:=RequireArithmeticBinaryParenthesesForClarity)) +end class", RequireArithmeticBinaryParenthesesForClarity) End Function Public Async Function TestShiftPrecedence3() As Task - Await TestMissingAsync( + Await VerifyNoCodeFixAsync( "class C sub M() - dim x = 1 $$<< 2 << 3 + dim x = 1 << 2 << 3 end sub -end class", parameters:=New TestParameters(options:=RequireArithmeticBinaryParenthesesForClarity)) +end class", RequireArithmeticBinaryParenthesesForClarity) End Function End Class End Namespace From 473a8fc8c8edc72d4c0254b174a85b15894c6a35 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 29 Jul 2022 14:31:39 +0200 Subject: [PATCH 003/274] Fix IDE2000: Avoid multiple blank lines --- src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb b/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb index e5aadafc91e8b..b133c267816b8 100644 --- a/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb +++ b/src/Analyzers/VisualBasic/Tests/Iterator/IteratorTests.vb @@ -146,7 +146,6 @@ End Module") }.RunAsync() End Function - Public Async Function TestChangeToYieldCodeFixProviderFunction() As Task Await VerifyCodeFixAsync( From b680581bd9ebe537bede423e668c4bae010bd833 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 29 Jul 2022 14:59:13 +0200 Subject: [PATCH 004/274] Move AddYieldTests too --- .../CSharp/Tests/Iterator/AddYieldTests.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/Iterator/AddYieldTests.cs b/src/Analyzers/CSharp/Tests/Iterator/AddYieldTests.cs index e80fcd40a0337..1dc63cc50b979 100644 --- a/src/Analyzers/CSharp/Tests/Iterator/AddYieldTests.cs +++ b/src/Analyzers/CSharp/Tests/Iterator/AddYieldTests.cs @@ -2,27 +2,28 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.CodeFixes.Iterator; -using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; using Xunit; -using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.Iterator { - public class AddYieldTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + using VerifyCS = CSharpCodeFixVerifier; + + public class AddYieldTests { - public AddYieldTests(ITestOutputHelper logger) - : base(logger) + private static async Task TestMissingInRegularAndScriptAsync(string code) { + await VerifyCS.VerifyCodeFixAsync(code, code); } - internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (null, new CSharpAddYieldCodeFixProvider()); + private static async Task TestInRegularAndScriptAsync(string code, string fixedCode) + { + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsChangeToYield)] public async Task TestAddYieldIEnumerableReturnNull() @@ -35,7 +36,7 @@ class Program { static IEnumerable M() { - [|return null|]; + return null; } }"; await TestMissingInRegularAndScriptAsync(initial); @@ -52,7 +53,7 @@ class Program { static IEnumerable M() { - [|return new object()|]; + return {|CS0266:new object()|}; } }"; var expected = @@ -80,7 +81,7 @@ class Program { static IEnumerator M() { - [|return new object()|]; + return {|CS0266:new object()|}; } }"; var expected = @@ -109,7 +110,7 @@ class Program { static IEnumerator M() { - [|return new List()|]; + return {|CS0266:new List()|}; } }"; var expected = @@ -139,7 +140,7 @@ class Program { static IEnumerator M() { - [|return new object()|]; + return {|CS0266:new object()|}; } }"; var expected = @@ -169,7 +170,7 @@ class Program { static IEnumerable M() { - [|return new object()|]; + return {|CS0266:new object()|}; } }"; var expected = @@ -199,7 +200,7 @@ class Program { static IEnumerable M() { - [|return new List()|]; + return new List(); } }"; await TestMissingInRegularAndScriptAsync(initial); @@ -217,7 +218,7 @@ class Program { static IEnumerator M() { - [|return default(T)|]; + return {|CS0266:default(T)|}; } }"; var expected = @@ -247,7 +248,7 @@ class Program { static IEnumerable M() { - [|return 0|]; + return {|CS0029:0|}; } }"; var expected = @@ -277,7 +278,7 @@ class Program { static IEnumerator M() { - [|return 0|]; + return {|CS0029:0|}; } }"; var expected = @@ -307,7 +308,7 @@ class Program { static IEnumerator> M() { - [|return new List()|]; + return {|CS0266:new List()|}; } }"; await TestMissingInRegularAndScriptAsync(initial); @@ -325,7 +326,7 @@ class Program { static IEnumerator> M() { - [|return new List()|]; + return {|CS0266:new List()|}; } }"; var expected = @@ -373,13 +374,13 @@ static void Main(string[] args) public class B : A> where Y : new() { - public override A.B P1 { get; set; } - public virtual Y P2 { get { [|return new Z()|]; } } + public override A.B P1 { get; {|CS0546:set|}; } + public virtual Y P2 { get { return {|CS0029:new Z()|}; } } public class C : B> where X : new() { public override A.B>.B.B.C> P1 { get; set; } - public override A.B.C P2 { get; set; } + public override A.B.C P2 { get; {|CS0546:set|}; } public virtual X P3 { get; set; } public class D : C> where W : new() From 97f0057439af198f2a95bfce1731cd7e86f58a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Schwartz?= <40674593+3schwartz@users.noreply.github.com> Date: Fri, 19 Aug 2022 09:58:09 +0200 Subject: [PATCH 005/274] Change wrong variable name --- docs/features/incremental-generators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/incremental-generators.md b/docs/features/incremental-generators.md index 2e0a3004567a8..d4106e8b56a26 100644 --- a/docs/features/incremental-generators.md +++ b/docs/features/incremental-generators.md @@ -36,7 +36,7 @@ public class Generator : IIncrementalGenerator IncrementalValuesProvider<(string name, string content)> namesAndContents = textFiles.Select((text, cancellationToken) => (name: Path.GetFileNameWithoutExtension(text.Path), content: text.GetText(cancellationToken)!.ToString())); // generate a class that contains their values as const strings - context.RegisterSourceOutput(namesAndContents, (spc, nameAndContent) => + initContext.RegisterSourceOutput(namesAndContents, (spc, nameAndContent) => { spc.AddSource($"ConstStrings.{nameAndContent.name}", $@" public static partial class ConstStrings From 348549b27aded2e7be709ccdaddd4ffc3687ee46 Mon Sep 17 00:00:00 2001 From: Soren Schwartz Date: Sat, 20 Aug 2022 20:58:40 +0200 Subject: [PATCH 006/274] Remove RegisterExecutionPipeline --- docs/features/incremental-generators.md | 135 ++++++++++-------------- 1 file changed, 54 insertions(+), 81 deletions(-) diff --git a/docs/features/incremental-generators.md b/docs/features/incremental-generators.md index d4106e8b56a26..383f91e86c2c0 100644 --- a/docs/features/incremental-generators.md +++ b/docs/features/incremental-generators.md @@ -206,18 +206,14 @@ transformations together: Consider the following simple example: ```csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; - - // apply a 1-to-1 transform on each text, which represents extracting the path - IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // transform each extracted path into something else - IncrementalValuesProvider prefixTransform = transformed.Select(static (path, _) => "prefix_" + path); +// apply a 1-to-1 transform on each text, which represents extracting the path +IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); -}); +// transform each extracted path into something else +IncrementalValuesProvider prefixTransform = transformed.Select(static (path, _) => "prefix_" + path); ``` Note how `transformed` and `prefixTransform` are themselves an @@ -369,17 +365,14 @@ distinct item for generation, effectively splitting a single additional file into multiple sub-items. ``` csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // extract each element from each additional file - IncrementalValuesProvider elements = additionalTexts.SelectMany(static (text, _) => /*transform text into an array of MyElementType*/); +// extract each element from each additional file +IncrementalValuesProvider elements = additionalTexts.SelectMany(static (text, _) => /*transform text into an array of MyElementType*/); - // now the generator can consider the union of elements in all additional texts, without needing to consider multiple files - IncrementalValuesProvider transformed = elements.Select(static (element, _) => /*transform the individual element*/); -} +// now the generator can consider the union of elements in all additional texts, without needing to consider multiple files +IncrementalValuesProvider transformed = elements.Select(static (element, _) => /*transform the individual element*/); ``` ### Where @@ -422,14 +415,11 @@ interested in. For example, the generator will likely want to filter additional texts on file extensions: ```csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // filter additional texts by extension - IncrementalValuesProvider xmlFiles = additionalTexts.Where(static (text, _) => text.Path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)); -} +// filter additional texts by extension +IncrementalValuesProvider xmlFiles = additionalTexts.Where(static (text, _) => text.Path.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)); ``` ### Collect @@ -464,18 +454,14 @@ IncrementalValuesProvider IncrementalValueProvider -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // collect the additional texts into a single item - IncrementalValueProvider collected = context.AdditionalTexts.Collect(); - - // perform a transformation where you can access all texts at once - var transform = collected.Select(static (texts, _) => /* ... */); -} +// collect the additional texts into a single item +IncrementalValueProvider collected = additionalTexts.Collect(); +// perform a transformation where you can access all texts at once +var transform = collected.Select(static (texts, _) => /* ... */); ``` ### Multi-path pipelines @@ -520,18 +506,15 @@ Those transforms can then be used as the inputs to new single path transforms, i For example: ```csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; +// get the additional text provider +IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; - // apply a 1-to-1 transform on each text, extracting the path - IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); +// apply a 1-to-1 transform on each text, extracting the path +IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); - // split the processing into two paths of derived data - IncrementalValuesProvider nameTransform = transformed.Select(static (path, _) => "prefix_" + path); - IncrementalValuesProvider extensionTransform = transformed.Select(static (path, _) => Path.ChangeExtension(path, ".new")); -} +// split the processing into two paths of derived data +IncrementalValuesProvider nameTransform = transformed.Select(static (path, _) => "prefix_" + path); +IncrementalValuesProvider extensionTransform = transformed.Select(static (path, _) => Path.ChangeExtension(path, ".new")); ``` `nameTransform` and `extensionTransform` produce different values for the same @@ -671,23 +654,19 @@ With the above transformations the generator author can now take one or more inputs and combine them into a single source of data. For example: ```csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; - - // combine each additional text with the parse options - IncrementalValuesProvider<(AdditionalText, ParseOptions)> combined = context.AdditionalTextsProvider.Combine(context.ParseOptionsProvider); +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // perform a transform on each text, with access to the options - var transformed = combined.Select(static (pair, _) => - { - AdditionalText text = pair.Left; - ParseOptions parseOptions = pair.Right; +// combine each additional text with the parse options +IncrementalValuesProvider<(AdditionalText, ParseOptions)> combined = initContext.AdditionalTextsProvider.Combine(initContext.ParseOptionsProvider); - // do the actual transform ... - }); -} +// perform a transform on each text, with access to the options +var transformed = combined.Select(static (pair, _) => +{ + AdditionalText text = pair.Left; + ParseOptions parseOptions = pair.Right; + // do the actual transform ... +}); ``` If either of the inputs to a combine change, then subsequent transformation will @@ -751,12 +730,9 @@ public class Class3 {} As an author I can make an input node that extracts the return type information ```csharp -initContext.RegisterExecutionPipeline(static context => -{ - // create a syntax provider that extracts the return type kind of method symbols - var returnKinds = context.SyntaxProvider.CreateSyntaxProvider(static (n, _) => n is MethodDeclarationSyntax, +// create a syntax provider that extracts the return type kind of method symbols + var returnKinds = initContext.SyntaxProvider.CreateSyntaxProvider(static (n, _) => n is MethodDeclarationSyntax, static (n, _) => ((IMethodSymbol)n.SemanticModel.GetDeclaredSymbol(n.Node)).ReturnType.Kind); -} ``` Initially the `predicate` will run for all syntax nodes, and select the two @@ -856,21 +832,19 @@ For example, a generator can extract out the set of paths for the additional files and create a method that prints them out: ``` csharp -initContext.RegisterExecutionPipeline(static context => -{ - // get the additional text provider - IncrementalValuesProvider additionalTexts = context.AdditionalTextsProvider; +// get the additional text provider +IncrementalValuesProvider additionalTexts = initContext.AdditionalTextsProvider; - // apply a 1-to-1 transform on each text, extracting the path - IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); +// apply a 1-to-1 transform on each text, extracting the path +IncrementalValuesProvider transformed = additionalTexts.Select(static (text, _) => text.Path); - // collect the paths into a batch - IncrementalValueProvider> collected = transformed.Collect(); - - // take the file paths from the above batch and make some user visible syntax - initContext.RegisterSourceOutput(collected, static (sourceProductionContext, filePaths) => - { - sourceProductionContext.AddSource("additionalFiles.cs", @" +// collect the paths into a batch +IncrementalValueProvider> collected = transformed.Collect(); + +// take the file paths from the above batch and make some user visible syntax +initContext.RegisterSourceOutput(collected, static (sourceProductionContext, filePaths) => +{ + sourceProductionContext.AddSource("additionalFiles.cs", @" namespace Generated { public class AdditionalTextList @@ -881,8 +855,7 @@ namespace Generated } } }"); - }); -} +}); ``` **RegisterImplementationSourceOutput**: From 416e16bcd0c5a2bd19eb33cacef4f7f52f0f863c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 15 Sep 2022 12:36:11 +1000 Subject: [PATCH 007/274] PR Feedback --- .../EditAndContinue/DeltaMetadataWriter.cs | 36 +++++----- .../Emit/EditAndContinue/SymbolChanges.cs | 70 ++++++------------- 2 files changed, 43 insertions(+), 63 deletions(-) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index abbc532e5ecbb..dec06cead2c8d 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -541,6 +541,8 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) // First we find the deleted methods, and add them to our dictionary. This is used later when // processing events, properties, methods, and references. + ImmutableDictionary? deletedMethodDefinitions = null; + var deletedMethods = _changes.GetDeletedMethods(typeDef); if (deletedMethods.Length > 0) { @@ -551,7 +553,8 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) deletedTypeMembers.Add(oldMethodDef, new DeletedMethodDefinition(oldMethodDef, typeDef, _typesUsedByDeletedMembers)); } - _deletedTypeMembers.Add(typeDef, deletedTypeMembers.ToImmutableDictionary()); + deletedMethodDefinitions = deletedTypeMembers.ToImmutableDictionary(); + _deletedTypeMembers.Add(typeDef, deletedMethodDefinitions); } foreach (var eventDef in typeDef.GetEvents(this.Context)) @@ -565,17 +568,18 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) this.AddDefIfNecessary(_eventDefs, eventDef, eventChange); } - var deletedEvents = _changes.GetDeletedEvents(typeDef); - foreach (var eventDel in deletedEvents) + + foreach (var eventDel in _changes.GetDeletedEvents(typeDef)) { + RoslynDebug.AssertNotNull(deletedMethodDefinitions); + var oldEventDef = (IEventDefinition)eventDel.GetCciAdapter(); - // Because deleted event information comes from the associated symbol of the deleted accessors, its safe + // Because deleted property information comes from the associated symbol of the deleted accessors, its safe // to assume that everything will be in the dictionary. We wouldn't be here it if wasn't. - var deletedMembers = _deletedTypeMembers[typeDef]; - var adder = deletedMembers[(IMethodDefinition)oldEventDef.Adder]; - var remover = deletedMembers[(IMethodDefinition)oldEventDef.Remover]; - var caller = oldEventDef.Caller is null ? null : deletedMembers[(IMethodDefinition)oldEventDef.Caller]; + var adder = deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Adder]; + var remover = deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Remover]; + var caller = oldEventDef.Caller is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Caller]; var newEventDef = new DeletedEventDefinition(oldEventDef, adder, remover, caller, typeDef, _typesUsedByDeletedMembers); _eventDefs.AddUpdated(newEventDef); } @@ -595,9 +599,9 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) // Because we already processed the deleted methods above, this is a bit easier than // properties and events, and we just need to make sure we add the right indices - if (_deletedTypeMembers.ContainsKey(typeDef)) + if (deletedMethodDefinitions is not null) { - foreach (var (_, newMethodDef) in _deletedTypeMembers[typeDef]) + foreach (var (_, newMethodDef) in deletedMethodDefinitions) { _methodDefs.AddUpdated(newMethodDef); CreateIndicesForMethod(newMethodDef, SymbolChange.Updated); @@ -615,16 +619,16 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) this.AddDefIfNecessary(_propertyDefs, propertyDef, propertyChange); } - var deletedProperties = _changes.GetDeletedProperties(typeDef); - foreach (var propertyDef in deletedProperties) + foreach (var propertyDef in _changes.GetDeletedProperties(typeDef)) { + RoslynDebug.AssertNotNull(deletedMethodDefinitions); + var oldPropertyDef = (IPropertyDefinition)propertyDef.GetCciAdapter(); // Because deleted property information comes from the associated symbol of the deleted accessors, its safe // to assume that everything will be in the dictionary. We wouldn't be here it if wasn't. - var deletedMembers = _deletedTypeMembers[typeDef]; - var getter = oldPropertyDef.Getter is null ? null : deletedMembers[(IMethodDefinition)oldPropertyDef.Getter]; - var setter = oldPropertyDef.Setter is null ? null : deletedMembers[(IMethodDefinition)oldPropertyDef.Setter]; + var getter = oldPropertyDef.Getter is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldPropertyDef.Getter]; + var setter = oldPropertyDef.Setter is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldPropertyDef.Setter]; var newPropertyDef = new DeletedPropertyDefinition(oldPropertyDef, getter, setter, typeDef, _typesUsedByDeletedMembers); _propertyDefs.AddUpdated(newPropertyDef); } @@ -669,7 +673,7 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) implementingMethods.Free(); } - private bool DefinitionExistsInAnyPreviousGeneration(IDefinition item) => item switch + private bool DefinitionExistsInAnyPreviousGeneration(ITypeDefinitionMember item) => item switch { IMethodDefinition methodDef => TryGetExistingMethodDefIndex(methodDef, out _), IPropertyDefinition propertyDef => TryGetExistingPropertyDefIndex(propertyDef, out _), diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs index 026107b41ebb6..4cc52fc6d73df 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs @@ -70,6 +70,22 @@ public ImmutableDictionary> Get return builder.ToImmutable(); } + private ImmutableArray GetDeletedMemberInternalSymbols(IDefinition containingType, bool includeMethods, bool includeProperties, bool includeEvents) + { + var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); + if (containingSymbol is null) + { + return ImmutableArray.Empty; + } + + if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted)) + { + return ImmutableArray.Empty; + } + + return GetDeletedMemberInternalSymbols(deleted, includeMethods, includeProperties, includeEvents); + } + private ImmutableArray GetDeletedMemberInternalSymbols(ISet deletedMembers, bool includeMethods, bool includeProperties, bool includeEvents) { var internalSymbols = ArrayBuilder.GetInstance(); @@ -112,52 +128,13 @@ private ImmutableArray GetDeletedMemberInternalSymbols(ISet GetDeletedMethods(IDefinition containingType) - { - var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); - if (containingSymbol is null) - { - return ImmutableArray.Empty; - } - - if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted)) - { - return ImmutableArray.Empty; - } - - return GetDeletedMemberInternalSymbols(deleted, includeMethods: true, includeProperties: false, includeEvents: false); - } + => GetDeletedMemberInternalSymbols(containingType, includeMethods: true, includeProperties: false, includeEvents: false); public ImmutableArray GetDeletedProperties(IDefinition containingType) - { - var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); - if (containingSymbol is null) - { - return ImmutableArray.Empty; - } - - if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted)) - { - return ImmutableArray.Empty; - } - - return GetDeletedMemberInternalSymbols(deleted, includeMethods: false, includeProperties: true, includeEvents: false); - } + => GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: true, includeEvents: false); public ImmutableArray GetDeletedEvents(IDefinition containingType) - { - var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); - if (containingSymbol is null) - { - return ImmutableArray.Empty; - } - - if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted)) - { - return ImmutableArray.Empty; - } - - return GetDeletedMemberInternalSymbols(deleted, includeMethods: false, includeProperties: false, includeEvents: true); - } + => GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: false, includeEvents: true); public bool IsReplaced(IDefinition definition, bool checkEnclosingTypes = false) => definition.GetInternalSymbol() is { } internalSymbol && IsReplaced(internalSymbol.GetISymbol(), checkEnclosingTypes); @@ -384,13 +361,13 @@ private SymbolChange GetChange(ISymbol symbol) } } - public SymbolChange GetChangeForPossibleReAddedMember(IDefinition item, Func definitionExistsInAnyPreviousGeneration) + public SymbolChange GetChangeForPossibleReAddedMember(ITypeDefinitionMember item, Func definitionExistsInAnyPreviousGeneration) { var change = GetChange(item); return fixChangeIfMemberIsReAdded(item, change, definitionExistsInAnyPreviousGeneration); - SymbolChange fixChangeIfMemberIsReAdded(IDefinition item, SymbolChange change, Func definitionExistsInAnyPreviousGeneration) + SymbolChange fixChangeIfMemberIsReAdded(ITypeDefinitionMember item, SymbolChange change, Func definitionExistsInAnyPreviousGeneration) { // If this is a field that is being added, but it's part of a property or event that has been deleted // and is now being re-added, we don't want to add the field twice, so we ignore the change. @@ -399,7 +376,7 @@ SymbolChange fixChangeIfMemberIsReAdded(IDefinition item, SymbolChange change, F // This also makes sure to check that the field itself is being re-added, because it could be // a property that is being re-added as an auto-prop, when it wasn't one before, for example. if (item is IFieldDefinition fieldDefinition && - GetContainingDefinitionForBackingField(fieldDefinition) is IDefinition containingDef && + GetContainingDefinitionForBackingField(fieldDefinition) is ITypeDefinitionMember containingDef && GetChange(containingDef) == SymbolChange.Added && definitionExistsInAnyPreviousGeneration(item) && fixChangeIfMemberIsReAdded(containingDef, SymbolChange.Added, definitionExistsInAnyPreviousGeneration) == SymbolChange.Updated) @@ -412,8 +389,7 @@ SymbolChange fixChangeIfMemberIsReAdded(IDefinition item, SymbolChange change, F // deleted in a generation, and then "added" in a subsequent one, but that is an update // even if the previous generation doesn't know about it. if (change == SymbolChange.Added && - item is ITypeDefinitionMember member && - !IsReplaced(member.ContainingTypeDefinition, checkEnclosingTypes: true) && + !IsReplaced(item.ContainingTypeDefinition, checkEnclosingTypes: true) && definitionExistsInAnyPreviousGeneration(item)) { return SymbolChange.Updated; From 40d45fa1ea1436969a1fe0e422fa2bcb77068e0c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 1 Dec 2022 18:08:47 -0800 Subject: [PATCH 008/274] Simplify WeaklyCachedRecoverableValueSource --- .../Solution/RecoverableTextAndVersion.cs | 6 +- .../WeaklyCachedRecoverableValueSource.cs | 123 ++++++++---------- 2 files changed, 58 insertions(+), 71 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index 6549d09c1d7bb..ee5645a5bb545 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -93,7 +93,7 @@ public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancel { if (_initialSourceOrRecoverableText is ITextAndVersionSource source) { - // replace initial source with recovarable text if it hasn't been replaced already: + // replace initial source with recoverable text if it hasn't been replaced already: Interlocked.CompareExchange( ref _initialSourceOrRecoverableText, value: new RecoverableText(source, source.GetValue(options, cancellationToken), options, _services), @@ -118,7 +118,7 @@ public async Task GetValueAsync(LoadTextOptions options, Cancell { if (_initialSourceOrRecoverableText is ITextAndVersionSource source) { - // replace initial source with recovarable text if it hasn't been replaced already: + // replace initial source with recoverable text if it hasn't been replaced already: Interlocked.CompareExchange( ref _initialSourceOrRecoverableText, value: new RecoverableText(source, await source.GetValueAsync(options, cancellationToken).ConfigureAwait(false), options, _services), @@ -150,7 +150,7 @@ private sealed class RecoverableText : WeaklyCachedRecoverableValueSource(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 9076b37a371ed..5dd179689d517 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host @@ -19,13 +20,18 @@ namespace Microsoft.CodeAnalysis.Host /// internal abstract class WeaklyCachedRecoverableValueSource : ValueSource where T : class { + // enforce saving in a queue so save's don't overload the thread pool. + private static Task s_latestTask = Task.CompletedTask; + private static readonly NonReentrantLock s_taskGuard = new(); + private SemaphoreSlim? _lazyGate; // Lazily created. Access via the Gate property private bool _saved; + private WeakReference? _weakReference; - private ValueSource _recoverySource; + private T? _initialValue; - public WeaklyCachedRecoverableValueSource(ValueSource initialValue) - => _recoverySource = initialValue; + public WeaklyCachedRecoverableValueSource(T initialValue) + => _initialValue = initialValue; /// /// Override this to save the state of the instance so it can be recovered. @@ -45,45 +51,35 @@ public WeaklyCachedRecoverableValueSource(ValueSource initialValue) /// protected abstract T Recover(CancellationToken cancellationToken); - // enforce saving in a queue so save's don't overload the thread pool. - private static Task s_latestTask = Task.CompletedTask; - private static readonly NonReentrantLock s_taskGuard = new(); - private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance); -#pragma warning disable CS8610 // Nullability of reference types in type of parameter doesn't match overridden member. (The compiler incorrectly identifies this as a change.) public override bool TryGetValue([NotNullWhen(true)] out T? value) -#pragma warning restore CS8610 // Nullability of reference types in type of parameter doesn't match overridden member. { - // It has 2 fields that can hold onto the value. if we only check weakInstance, we will - // return false for the initial case where weakInstance is set to s_noReference even if - // value can be retrieved from _recoverySource. so we check both here. + // See if we still have the constant value stored. If so, we can trivially return that. + value = _initialValue; + if (_initialValue != null) + return true; + + // If not, see if it's something someone else is holding into, and is available through the weak-ref. + value = null; var weakReference = _weakReference; - return weakReference != null && weakReference.TryGetTarget(out value) || - _recoverySource.TryGetValue(out value); + return weakReference != null && weakReference.TryGetTarget(out value) && value != null; } public override T GetValue(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var weakReference = _weakReference; - if (weakReference == null || !weakReference.TryGetTarget(out var instance)) + if (!TryGetValue(out var instance)) { - Task? saveTask = null; using (Gate.DisposableWait(cancellationToken)) { - if (_weakReference == null || !_weakReference.TryGetTarget(out instance)) + if (!TryGetValue(out instance)) { - instance = _recoverySource.GetValue(cancellationToken); - saveTask = EnsureInstanceIsSavedAsync(instance); + instance = Recover(cancellationToken); + EnqueueSaveTask_NoLock(instance); } } - - if (saveTask != null) - { - ResetRecoverySource(saveTask, instance); - } } return instance; @@ -91,72 +87,63 @@ public override T GetValue(CancellationToken cancellationToken) public override async Task GetValueAsync(CancellationToken cancellationToken) { - var weakReference = _weakReference; - if (weakReference == null || !weakReference.TryGetTarget(out var instance)) + if (!TryGetValue(out var instance)) { - Task? saveTask = null; using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (_weakReference == null || !_weakReference.TryGetTarget(out instance)) + if (!TryGetValue(out instance)) { - instance = await _recoverySource.GetValueAsync(cancellationToken).ConfigureAwait(false); - saveTask = EnsureInstanceIsSavedAsync(instance); + instance = await RecoverAsync(cancellationToken).ConfigureAwait(false); + EnqueueSaveTask_NoLock(instance); } } - - if (saveTask != null) - { - ResetRecoverySource(saveTask, instance); - } } return instance; } - private void ResetRecoverySource(Task saveTask, T instance) + /// + /// Kicks off the work to save this instance to secondary storage at some point in the future. Once that save + /// occurs successfully, we will drop our cached data and return values from that storage instead. + /// + private void EnqueueSaveTask_NoLock(T instance) { - saveTask.SafeContinueWith(t => - { - using (Gate.DisposableWait(CancellationToken.None)) - { - // Only assume the instance is saved if the saveTask completed successfully. If the save did not - // complete, we can still rely on a constant value source to provide the instance. - _recoverySource = saveTask.Status == TaskStatus.RanToCompletion - ? new AsyncLazy(RecoverAsync, Recover, cacheResult: false) - : new AsyncLazy(instance); - - // Need to keep instance alive until recovery source is updated. - GC.KeepAlive(instance); - } - }, TaskScheduler.Default); - } + Contract.ThrowIfTrue(Gate.CurrentCount != 0); - private Task? EnsureInstanceIsSavedAsync(T instance) - { - if (_weakReference == null) - { - _weakReference = new WeakReference(instance); - } - else - { - _weakReference.SetTarget(instance); - } + _weakReference ??= new WeakReference(instance); + _weakReference.SetTarget(instance); + // Ensure we only save once. if (!_saved) { _saved = true; using (s_taskGuard.DisposableWait()) { // force all save tasks to be in sequence so we don't hog all the threads - s_latestTask = s_latestTask.SafeContinueWithFromAsync(t => - SaveAsync(instance, CancellationToken.None), CancellationToken.None, TaskScheduler.Default); - return s_latestTask; + s_latestTask = SaveAndResetInitialValue(s_latestTask); } } -#pragma warning disable VSTHRD114 // Avoid returning a null Task (False positive: https://github.com/microsoft/vs-threading/issues/637) - return null; -#pragma warning restore VSTHRD114 // Avoid returning a null Task + return; + + async Task SaveAndResetInitialValue(Task previousTask) + { + // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They + // do not affect if we run or not. + await previousTask.NoThrowAwaitableInternal(captureContext: false); + + // Now defer to our subclass to actually save the instance to secondary storage. + await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); + + // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, + // we want to keep it around to service future requests. Once we do clear out this value, then all + // future request will either retrieve the value from the weak reference (if anyone else is holding onto + // it), or will recover from the + using (Gate.DisposableWait(CancellationToken.None)) + { + _initialValue = null; + } + } } } } From bb7ebd9b8ad818d1b87ced9750da2612a05bbb31 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 1 Dec 2022 18:46:59 -0800 Subject: [PATCH 009/274] Fix --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 5dd179689d517..41380a6227613 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -57,11 +57,10 @@ public override bool TryGetValue([NotNullWhen(true)] out T? value) { // See if we still have the constant value stored. If so, we can trivially return that. value = _initialValue; - if (_initialValue != null) + if (value != null) return true; // If not, see if it's something someone else is holding into, and is available through the weak-ref. - value = null; var weakReference = _weakReference; return weakReference != null && weakReference.TryGetTarget(out value) && value != null; } From f9888f2a1b7380ed969ead5ca72645af53fa9a32 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 1 Dec 2022 18:48:04 -0800 Subject: [PATCH 010/274] Move code down --- .../Shared/TestHooks/TaskExtensions.cs | 120 +--------------- .../Core/CompilerExtensions.projitems | 1 + .../Compiler/Core/TestHooks/TaskExtensions.cs | 133 ++++++++++++++++++ 3 files changed, 135 insertions(+), 119 deletions(-) create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs index ac2fb3cb78fd3..b58488f812ff3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/TaskExtensions.cs @@ -4,14 +4,13 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Shared.TestHooks { - internal static class TaskExtensions + internal static partial class TaskExtensions { [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "This is a Task wrapper, not an asynchronous method.")] public static Task CompletesAsyncOperation(this Task task, IAsyncToken asyncToken) @@ -46,122 +45,5 @@ static Task CompletesTrackingOperationSlow(Task task, IDisposable token) TaskScheduler.Default); } } - - // Following code is copied from Microsoft.VisualStudio.Threading.TplExtensions (renamed to avoid ambiguity) - // https://github.com/microsoft/vs-threading/blob/main/src/Microsoft.VisualStudio.Threading/TplExtensions.cs - - /// - /// Returns an awaitable for the specified task that will never throw, even if the source task - /// faults or is canceled. - /// - /// The task whose completion should signal the completion of the returned awaitable. - /// if set to true the continuation will be scheduled on the caller's context; false to always execute the continuation on the threadpool. - /// An awaitable. - public static NoThrowTaskAwaitable NoThrowAwaitableInternal(this Task task, bool captureContext = true) - { - return new NoThrowTaskAwaitable(task, captureContext); - } - - /// - /// An awaitable that wraps a task and never throws an exception when waited on. - /// - public readonly struct NoThrowTaskAwaitable - { - /// - /// The task. - /// - private readonly Task _task; - - /// - /// A value indicating whether the continuation should be scheduled on the current sync context. - /// - private readonly bool _captureContext; - - /// - /// Initializes a new instance of the struct. - /// - /// The task. - /// Whether the continuation should be scheduled on the current sync context. - public NoThrowTaskAwaitable(Task task, bool captureContext) - { - Contract.ThrowIfNull(task, nameof(task)); - _task = task; - _captureContext = captureContext; - } - - /// - /// Gets the awaiter. - /// - /// The awaiter. - public NoThrowTaskAwaiter GetAwaiter() - { - return new NoThrowTaskAwaiter(_task, _captureContext); - } - } - - /// - /// An awaiter that wraps a task and never throws an exception when waited on. - /// - public readonly struct NoThrowTaskAwaiter : ICriticalNotifyCompletion - { - /// - /// The task. - /// - private readonly Task _task; - - /// - /// A value indicating whether the continuation should be scheduled on the current sync context. - /// - private readonly bool _captureContext; - - /// - /// Initializes a new instance of the struct. - /// - /// The task. - /// if set to true [capture context]. - public NoThrowTaskAwaiter(Task task, bool captureContext) - { - Contract.ThrowIfNull(task, nameof(task)); - _task = task; - _captureContext = captureContext; - } - - /// - /// Gets a value indicating whether the task has completed. - /// - public bool IsCompleted - { - get { return _task.IsCompleted; } - } - - /// - /// Schedules a delegate for execution at the conclusion of a task's execution. - /// - /// The action. - public void OnCompleted(Action continuation) - { - _task.ConfigureAwait(_captureContext).GetAwaiter().OnCompleted(continuation); - } - - /// - /// Schedules a delegate for execution at the conclusion of a task's execution - /// without capturing the ExecutionContext. - /// - /// The action. - public void UnsafeOnCompleted(Action continuation) - { - _task.ConfigureAwait(_captureContext).GetAwaiter().UnsafeOnCompleted(continuation); - } - - /// - /// Does nothing. - /// -#pragma warning disable CA1822 // Mark members as static - public void GetResult() -#pragma warning restore CA1822 // Mark members as static - { - // Never throw here. - } - } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 471d82cb737d5..91ce2c593e0ea 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -464,6 +464,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs new file mode 100644 index 0000000000000..275a60df30a34 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/TestHooks/TaskExtensions.cs @@ -0,0 +1,133 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Shared.TestHooks +{ + internal static partial class TaskExtensions + { + // Following code is copied from Microsoft.VisualStudio.Threading.TplExtensions (renamed to avoid ambiguity) + // https://github.com/microsoft/vs-threading/blob/main/src/Microsoft.VisualStudio.Threading/TplExtensions.cs + + /// + /// Returns an awaitable for the specified task that will never throw, even if the source task + /// faults or is canceled. + /// + /// The task whose completion should signal the completion of the returned awaitable. + /// if set to true the continuation will be scheduled on the caller's context; false to always execute the continuation on the threadpool. + /// An awaitable. + public static NoThrowTaskAwaitable NoThrowAwaitableInternal(this Task task, bool captureContext = true) + { + return new NoThrowTaskAwaitable(task, captureContext); + } + + /// + /// An awaitable that wraps a task and never throws an exception when waited on. + /// + public readonly struct NoThrowTaskAwaitable + { + /// + /// The task. + /// + private readonly Task _task; + + /// + /// A value indicating whether the continuation should be scheduled on the current sync context. + /// + private readonly bool _captureContext; + + /// + /// Initializes a new instance of the struct. + /// + /// The task. + /// Whether the continuation should be scheduled on the current sync context. + public NoThrowTaskAwaitable(Task task, bool captureContext) + { + Contract.ThrowIfNull(task, nameof(task)); + _task = task; + _captureContext = captureContext; + } + + /// + /// Gets the awaiter. + /// + /// The awaiter. + public NoThrowTaskAwaiter GetAwaiter() + { + return new NoThrowTaskAwaiter(_task, _captureContext); + } + } + + /// + /// An awaiter that wraps a task and never throws an exception when waited on. + /// + public readonly struct NoThrowTaskAwaiter : ICriticalNotifyCompletion + { + /// + /// The task. + /// + private readonly Task _task; + + /// + /// A value indicating whether the continuation should be scheduled on the current sync context. + /// + private readonly bool _captureContext; + + /// + /// Initializes a new instance of the struct. + /// + /// The task. + /// if set to true [capture context]. + public NoThrowTaskAwaiter(Task task, bool captureContext) + { + Contract.ThrowIfNull(task, nameof(task)); + _task = task; + _captureContext = captureContext; + } + + /// + /// Gets a value indicating whether the task has completed. + /// + public bool IsCompleted + { + get { return _task.IsCompleted; } + } + + /// + /// Schedules a delegate for execution at the conclusion of a task's execution. + /// + /// The action. + public void OnCompleted(Action continuation) + { + _task.ConfigureAwait(_captureContext).GetAwaiter().OnCompleted(continuation); + } + + /// + /// Schedules a delegate for execution at the conclusion of a task's execution + /// without capturing the ExecutionContext. + /// + /// The action. + public void UnsafeOnCompleted(Action continuation) + { + _task.ConfigureAwait(_captureContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + + /// + /// Does nothing. + /// +#pragma warning disable CA1822 // Mark members as static + public void GetResult() +#pragma warning restore CA1822 // Mark members as static + { + // Never throw here. + } + } + } +} From c1776ea123d3470edafd1c9561f9ad6f3f2d3937 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:18:04 -0800 Subject: [PATCH 011/274] Fix --- .../WeaklyCachedRecoverableValueSource.cs | 132 +++++++++++------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 41380a6227613..23e96a47c08f5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -12,11 +12,10 @@ namespace Microsoft.CodeAnalysis.Host { /// - /// This class is a that holds onto a value weakly, - /// but can save its value and recover it on demand if needed. - /// - /// The initial value comes from the specified in the constructor. - /// Derived types implement SaveAsync and RecoverAsync. + /// This class is a that holds onto a value weakly, but can save its value and recover + /// it on demand if needed. The value is initially strongly held, until the first time that + /// or is called. At that point, it will be dumped to secondary storage, and retrieved + /// and weakly held from that point on in the future. /// internal abstract class WeaklyCachedRecoverableValueSource : ValueSource where T : class { @@ -53,7 +52,18 @@ public WeaklyCachedRecoverableValueSource(T initialValue) private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance); - public override bool TryGetValue([NotNullWhen(true)] out T? value) + /// + /// Attempts to get the value, but only through the weak reference. This will only succeed *after* the value + /// has been retrieved at least once, and has thus then been save to secondary storage. + /// + private bool TryGetWeakValue([NotNullWhen(true)] out T? value) + { + value = null; + var weakReference = _weakReference; + return weakReference != null && weakReference.TryGetTarget(out value) && value != null; + } + + private bool TryGetWeakOrStrongValue([NotNullWhen(true)] out T? value) { // See if we still have the constant value stored. If so, we can trivially return that. value = _initialValue; @@ -61,51 +71,58 @@ public override bool TryGetValue([NotNullWhen(true)] out T? value) return true; // If not, see if it's something someone else is holding into, and is available through the weak-ref. - var weakReference = _weakReference; - return weakReference != null && weakReference.TryGetTarget(out value) && value != null; + return TryGetWeakValue(out value); } + public override bool TryGetValue([MaybeNullWhen(false)] out T value) + => TryGetWeakOrStrongValue(out value); + public override T GetValue(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (!TryGetValue(out var instance)) + // if the value is currently being held weakly, then we can return that immediately as we know we will have + // kicked off the work to save the value to secondary storage. + if (TryGetWeakValue(out var instance)) + return instance; + + // Otherwise, we're either holding the value strongly, or we need to recovery it from secondary storage. + using (Gate.DisposableWait(cancellationToken)) { - using (Gate.DisposableWait(cancellationToken)) - { - if (!TryGetValue(out instance)) - { - instance = Recover(cancellationToken); - EnqueueSaveTask_NoLock(instance); - } - } - } + if (!TryGetWeakOrStrongValue(out instance)) + instance = Recover(cancellationToken); - return instance; + // If the value was strongly held, kick off the work to write it to secondary storage and release the + // strong reference to it. + UpdateWeakReferenceAndEnqueueSaveTask_NoLock(instance); + return instance; + } } public override async Task GetValueAsync(CancellationToken cancellationToken) { - if (!TryGetValue(out var instance)) + // if the value is currently being held weakly, then we can return that immediately as we know we will have + // kicked off the work to save the value to secondary storage. + if (TryGetWeakValue(out var instance)) + return instance; + + using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - if (!TryGetValue(out instance)) - { - instance = await RecoverAsync(cancellationToken).ConfigureAwait(false); - EnqueueSaveTask_NoLock(instance); - } - } - } + if (!TryGetWeakOrStrongValue(out instance)) + instance = await RecoverAsync(cancellationToken).ConfigureAwait(false); - return instance; + // If the value was strongly held, kick off the work to write it to secondary storage and release the + // strong reference to it. + UpdateWeakReferenceAndEnqueueSaveTask_NoLock(instance); + return instance; + } } /// /// Kicks off the work to save this instance to secondary storage at some point in the future. Once that save /// occurs successfully, we will drop our cached data and return values from that storage instead. /// - private void EnqueueSaveTask_NoLock(T instance) + private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) { Contract.ThrowIfTrue(Gate.CurrentCount != 0); @@ -119,30 +136,45 @@ private void EnqueueSaveTask_NoLock(T instance) using (s_taskGuard.DisposableWait()) { // force all save tasks to be in sequence so we don't hog all the threads - s_latestTask = SaveAndResetInitialValue(s_latestTask); + s_latestTask = s_latestTask + .SafeContinueWithFromAsync(async task => + { + // Ignore all errors from prior tasks. They do not affect if we save or not. + await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); + + // Now that we've saved the instance, explicitly 'null out' 'instance' here so that it's not + // held alive in this capture. + instance = null!; + }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default) + .SafeContinueWith(task => + { + if (task.Status != TaskStatus.RanToCompletion) + return; + + // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, + // we want to keep it around to service future requests. Once we do clear out this value, then all + // future request will either retrieve the value from the weak reference (if anyone else is holding onto + // it), or will recover from the + using (Gate.DisposableWait(CancellationToken.None)) + { + _initialValue = null; + } + }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); } } return; - async Task SaveAndResetInitialValue(Task previousTask) - { - // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They - // do not affect if we run or not. - await previousTask.NoThrowAwaitableInternal(captureContext: false); - - // Now defer to our subclass to actually save the instance to secondary storage. - await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); - - // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, - // we want to keep it around to service future requests. Once we do clear out this value, then all - // future request will either retrieve the value from the weak reference (if anyone else is holding onto - // it), or will recover from the - using (Gate.DisposableWait(CancellationToken.None)) - { - _initialValue = null; - } - } + //async Task SaveAndResetInitialValue(Task previousTask) + //{ + // // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They + // // do not affect if we run or not. + // await previousTask.NoThrowAwaitableInternal(captureContext: false); + + // // Now defer to our subclass to actually save the instance to secondary storage. + // await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); + + //} } } } From 3194313796d8983e4699db3a5c20c5c9e703383a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:20:00 -0800 Subject: [PATCH 012/274] Simplify --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 23e96a47c08f5..a61f83624957a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -135,7 +135,9 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) _saved = true; using (s_taskGuard.DisposableWait()) { - // force all save tasks to be in sequence so we don't hog all the threads + // force all save tasks to be in sequence so we don't hog all the threads. Use + // RunContinuationsAsynchronously so that the continuation work doesn't run in the thread of the + // caller (which is holding 'Gate'). s_latestTask = s_latestTask .SafeContinueWithFromAsync(async task => { @@ -145,11 +147,6 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) // Now that we've saved the instance, explicitly 'null out' 'instance' here so that it's not // held alive in this capture. instance = null!; - }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default) - .SafeContinueWith(task => - { - if (task.Status != TaskStatus.RanToCompletion) - return; // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, // we want to keep it around to service future requests. Once we do clear out this value, then all From 3a7f2fd82ff5e24bca67c4565bda655d89efbec2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:20:29 -0800 Subject: [PATCH 013/274] Don't need gate --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index a61f83624957a..aa3df096da64c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -152,10 +152,7 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) // we want to keep it around to service future requests. Once we do clear out this value, then all // future request will either retrieve the value from the weak reference (if anyone else is holding onto // it), or will recover from the - using (Gate.DisposableWait(CancellationToken.None)) - { _initialValue = null; - } }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); } } From 11faf2f4ba74bfb8c0faf822e818acaa49f4126e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:23:19 -0800 Subject: [PATCH 014/274] Simplify --- .../WeaklyCachedRecoverableValueSource.cs | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index aa3df096da64c..afad87c5638d9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -135,40 +135,28 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) _saved = true; using (s_taskGuard.DisposableWait()) { - // force all save tasks to be in sequence so we don't hog all the threads. Use - // RunContinuationsAsynchronously so that the continuation work doesn't run in the thread of the - // caller (which is holding 'Gate'). - s_latestTask = s_latestTask - .SafeContinueWithFromAsync(async task => - { - // Ignore all errors from prior tasks. They do not affect if we save or not. - await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); - - // Now that we've saved the instance, explicitly 'null out' 'instance' here so that it's not - // held alive in this capture. - instance = null!; - - // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, - // we want to keep it around to service future requests. Once we do clear out this value, then all - // future request will either retrieve the value from the weak reference (if anyone else is holding onto - // it), or will recover from the - _initialValue = null; - }, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); + // force all save tasks to be in sequence so we don't hog all the threads. + s_latestTask = SaveAndResetInitialValue(s_latestTask); } } return; - //async Task SaveAndResetInitialValue(Task previousTask) - //{ - // // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They - // // do not affect if we run or not. - // await previousTask.NoThrowAwaitableInternal(captureContext: false); - - // // Now defer to our subclass to actually save the instance to secondary storage. - // await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); - - //} + async Task SaveAndResetInitialValue(Task previousTask) + { + // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They + // do not affect if we run or not. + await previousTask.NoThrowAwaitableInternal(captureContext: false); + + // Now defer to our subclass to actually save the instance to secondary storage. + await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); + + // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, + // we want to keep it around to service future requests. Once we do clear out this value, then all + // future request will either retrieve the value from the weak reference (if anyone else is holding onto + // it), or will recover from underlying storage. + _initialValue = null; + } } } } From 831b4ae2134e5cf654f4b799f76e953053bf4f6c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:23:58 -0800 Subject: [PATCH 015/274] Simplify --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index afad87c5638d9..033b0ddbb0375 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -63,7 +63,7 @@ private bool TryGetWeakValue([NotNullWhen(true)] out T? value) return weakReference != null && weakReference.TryGetTarget(out value) && value != null; } - private bool TryGetWeakOrStrongValue([NotNullWhen(true)] out T? value) + private bool TryGetStrongOrWeakValue([NotNullWhen(true)] out T? value) { // See if we still have the constant value stored. If so, we can trivially return that. value = _initialValue; @@ -75,7 +75,7 @@ private bool TryGetWeakOrStrongValue([NotNullWhen(true)] out T? value) } public override bool TryGetValue([MaybeNullWhen(false)] out T value) - => TryGetWeakOrStrongValue(out value); + => TryGetStrongOrWeakValue(out value); public override T GetValue(CancellationToken cancellationToken) { @@ -89,7 +89,7 @@ public override T GetValue(CancellationToken cancellationToken) // Otherwise, we're either holding the value strongly, or we need to recovery it from secondary storage. using (Gate.DisposableWait(cancellationToken)) { - if (!TryGetWeakOrStrongValue(out instance)) + if (!TryGetStrongOrWeakValue(out instance)) instance = Recover(cancellationToken); // If the value was strongly held, kick off the work to write it to secondary storage and release the @@ -108,7 +108,7 @@ public override async Task GetValueAsync(CancellationToken cancellationToken) using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (!TryGetWeakOrStrongValue(out instance)) + if (!TryGetStrongOrWeakValue(out instance)) instance = await RecoverAsync(cancellationToken).ConfigureAwait(false); // If the value was strongly held, kick off the work to write it to secondary storage and release the From b04b9e5a00c80bb085efdc7e9d3af6880da06b7b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:24:51 -0800 Subject: [PATCH 016/274] Simplify --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 033b0ddbb0375..a5209f76529d8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -101,6 +101,8 @@ public override T GetValue(CancellationToken cancellationToken) public override async Task GetValueAsync(CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + // if the value is currently being held weakly, then we can return that immediately as we know we will have // kicked off the work to save the value to secondary storage. if (TryGetWeakValue(out var instance)) From 62226ec852b6deab1c60657ea53716eccd943a26 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 09:28:56 -0800 Subject: [PATCH 017/274] Docs --- .../WeaklyCachedRecoverableValueSource.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index a5209f76529d8..70ee9124fc900 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -23,12 +23,28 @@ internal abstract class WeaklyCachedRecoverableValueSource : ValueSource w private static Task s_latestTask = Task.CompletedTask; private static readonly NonReentrantLock s_taskGuard = new(); - private SemaphoreSlim? _lazyGate; // Lazily created. Access via the Gate property + /// + /// Lazily created. Access via the property. + /// + private SemaphoreSlim? _lazyGate; + + /// + /// Whether or not we've saved our value to secondary storage. Used so we only do that once. + /// private bool _saved; - private WeakReference? _weakReference; + /// + /// Initial strong value that this value source is initialized with. Will be used to respond to the first + /// request to get the value, at which point it will be dumped into secondary storage. + /// private T? _initialValue; + /// + /// Weak reference to the value last returned from this value source. Will thus return the same value as long + /// as something external is holding onto it. + /// + private WeakReference? _weakReference; + public WeaklyCachedRecoverableValueSource(T initialValue) => _initialValue = initialValue; @@ -63,6 +79,9 @@ private bool TryGetWeakValue([NotNullWhen(true)] out T? value) return weakReference != null && weakReference.TryGetTarget(out value) && value != null; } + /// + /// Attempts to get the value, either through our strong or weak reference. + /// private bool TryGetStrongOrWeakValue([NotNullWhen(true)] out T? value) { // See if we still have the constant value stored. If so, we can trivially return that. From a614fc7296e63237dace327ac806216f1b7720c2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 2 Dec 2022 10:56:30 -0800 Subject: [PATCH 018/274] Fix formatting --- .../ValuesSources/WeaklyCachedRecoverableValueSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs index 70ee9124fc900..fe2cc137a61da 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs @@ -26,7 +26,7 @@ internal abstract class WeaklyCachedRecoverableValueSource : ValueSource w /// /// Lazily created. Access via the property. /// - private SemaphoreSlim? _lazyGate; + private SemaphoreSlim? _lazyGate; /// /// Whether or not we've saved our value to secondary storage. Used so we only do that once. From 7982d7d62780850b1082a9990f2ef0a68ec980ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 5 Dec 2022 15:30:01 -0800 Subject: [PATCH 019/274] Doc --- .../Portable/Syntax/CSharpSyntaxNode.cs | 7 +- .../Core/Portable/Syntax/SyntaxList.cs | 9 +-- .../Core/Portable/Syntax/SyntaxNode.cs | 77 +++++++++++++++++-- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs index f1719695e36d0..e8c4290d3f88f 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs @@ -154,10 +154,9 @@ public SyntaxKind Kind() /// /// The language name that this node is syntax of. /// - public override string Language - { - get { return LanguageNames.CSharp; } - } + public override string Language => LanguageNames.CSharp; + + protected override int ConditionalDirectiveKind => (int)SyntaxKind.IfDirectiveTrivia; /// /// The list of trivia that appears before this node in the source code. diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxList.cs b/src/Compilers/Core/Portable/Syntax/SyntaxList.cs index 2073ef9448ed8..3bceb17f6aba0 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxList.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxList.cs @@ -15,13 +15,8 @@ internal SyntaxList(InternalSyntax.SyntaxList green, SyntaxNode? parent, int pos { } - public override string Language - { - get - { - throw ExceptionUtilities.Unreachable(); - } - } + public override string Language => throw ExceptionUtilities.Unreachable(); + protected override int ConditionalDirectiveKind => throw ExceptionUtilities.Unreachable(); // https://github.com/dotnet/roslyn/issues/40733 protected override SyntaxTree SyntaxTreeCore => this.Parent!.SyntaxTree; diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 5d8c3d0c01498..91ea24e5e7b48 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -64,6 +64,12 @@ private string GetDebuggerDisplay() /// public abstract string Language { get; } + /// + /// The raw SyntaxKind for this language corresponding to a conditional directive (#if in C# and + /// #If in Visual Basic). + /// + protected abstract int ConditionalDirectiveKind { get; } + internal GreenNode Green { get; } internal int Position { get; } @@ -426,24 +432,81 @@ public bool ContainsSkippedText } /// - /// Determines whether this node has any descendant preprocessor directives. + /// Determines whether this node or any of its descendant nodes, tokens or trivia have any diagnostics on them. /// - public bool ContainsDirectives + public bool ContainsDiagnostics { get { - return this.Green.ContainsDirectives; + return this.Green.ContainsDiagnostics; } } /// - /// Determines whether this node or any of its descendant nodes, tokens or trivia have any diagnostics on them. + /// Determines whether this node has any descendant preprocessor directives. /// - public bool ContainsDiagnostics + public bool ContainsDirectives => this.Green.ContainsDirectives; + + /// + /// Returns true if this node contains any conditional directives (#if in C# and #If in Visual + /// Basic) within it. This is similar to except that where that returns true + /// if there are any preprocessor directives whatsoever, this is only true for for if directives. + /// + public bool ContainsConditionalDirectives() { - get + var conditionalDirectiveKind = this.ConditionalDirectiveKind; + + var stack = PooledObjects.ArrayBuilder.GetInstance(); + stack.Push(this.Green); + + try { - return this.Green.ContainsDiagnostics; + while (stack.Count > 0) + { + var current = stack.Pop(); + if (current is null) + continue; + + // Don't bother looking further down this portion of the tree if it clearly doesn't contain directives. + if (!current.ContainsDirectives) + continue; + + if (current.IsToken) + { + // no need to look within if this token doesn't even have leading trivia. + if (current.HasLeadingTrivia) + { + var leadingTriviaNode = current.GetLeadingTriviaCore(); + Debug.Assert(leadingTriviaNode != null); + + // Will either have one or many trivia nodes. + if (leadingTriviaNode.IsList) + { + for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) + { + if (leadingTriviaNode.GetSlot(i)?.RawKind == conditionalDirectiveKind) + return true; + } + } + else if (leadingTriviaNode.RawKind == conditionalDirectiveKind) + { + return true; + } + } + } + else + { + // nodes and lists. Push children backwards so we walk the tree in lexical order. + for (int i = current.SlotCount - 1; i >= 0; i--) + stack.Push(current.GetSlot(i)); + } + } + + return false; + } + finally + { + stack.Free(); } } From 2ed8fe130f73e9142be43c1909eca5a5c6c27602 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 5 Dec 2022 15:30:28 -0800 Subject: [PATCH 020/274] Update --- src/Analyzers/Core/Analyzers/Analyzers.projitems | 2 +- .../VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems index 4eeaf0e479b85..8e2fb4f89b1ae 100644 --- a/src/Analyzers/Core/Analyzers/Analyzers.projitems +++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems @@ -106,4 +106,4 @@ - + \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb index cddea06481803..01aa337a763d9 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb @@ -110,6 +110,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + Protected Overrides ReadOnly Property ConditionalDirectiveKind As Integer + Get + Return SyntaxKind.IfDirectiveTrivia + End Get + End Property + ''' ''' The parent of this node. ''' From 3699b299affe5d378a51035ec5ef9b208a22497b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Dec 2022 17:05:56 -0800 Subject: [PATCH 021/274] Move value source back into workspace --- .../Solution}/WeaklyCachedRecoverableValueSource.cs | 0 .../Core/AddImport/AddImportPlacementOptions.cs | 5 ++++- .../Compiler/Core/CodeCleanup/CodeCleanupOptions.cs | 2 +- .../Core/CodeGeneration/CleanCodeGenerationOptions.cs | 3 +++ .../Core/CodeGeneration/CodeGenerationOptions.cs | 10 ++++------ .../Compiler/Core/CodeStyle/IdeCodeStyleOptions.cs | 5 ++++- .../Compiler/Core/CompilerExtensions.projitems | 1 - .../Compiler/Core/Diagnostics/IdeAnalyzerOptions.cs | 3 +++ .../Core/Formatting/SyntaxFormattingOptions.cs | 5 ++++- .../Compiler/Core/Indentation/IndentationOptions.cs | 3 +++ .../Compiler/Core/NamingStyles/NamingStyleOptions.cs | 5 ++++- .../Indentation/VisualBasicSmartTokenFormatter.vb | 2 -- 12 files changed, 30 insertions(+), 14 deletions(-) rename src/Workspaces/{SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources => Core/Portable/Workspace/Solution}/WeaklyCachedRecoverableValueSource.cs (100%) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs similarity index 100% rename from src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ValuesSources/WeaklyCachedRecoverableValueSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/AddImport/AddImportPlacementOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/AddImport/AddImportPlacementOptions.cs index 67a42f9889b5a..6c8b6a3748454 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/AddImport/AddImportPlacementOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/AddImport/AddImportPlacementOptions.cs @@ -7,9 +7,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; +#if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; +#endif + namespace Microsoft.CodeAnalysis.AddImport; [DataContract] diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeCleanup/CodeCleanupOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeCleanup/CodeCleanupOptions.cs index 09e5d2d6ff056..921b16de570c0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeCleanup/CodeCleanupOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeCleanup/CodeCleanupOptions.cs @@ -8,12 +8,12 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.AddImport; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.CodeActions; using Roslyn.Utilities; using Microsoft.CodeAnalysis.Diagnostics; #if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.OrganizeImports; #endif diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CleanCodeGenerationOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CleanCodeGenerationOptions.cs index 9cc7fb1756363..e0cfee0b46d40 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CleanCodeGenerationOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CleanCodeGenerationOptions.cs @@ -7,7 +7,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; + +#if !CODE_STYLE using Microsoft.CodeAnalysis.Host; +#endif namespace Microsoft.CodeAnalysis.CodeGeneration; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CodeGenerationOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CodeGenerationOptions.cs index fad46022618e4..e925ba6188351 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CodeGenerationOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeGeneration/CodeGenerationOptions.cs @@ -2,21 +2,19 @@ // 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.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.AddImport; using Roslyn.Utilities; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; +#if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; +#endif + namespace Microsoft.CodeAnalysis.CodeGeneration; /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/IdeCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/IdeCodeStyleOptions.cs index 6ec140dba0c28..3f9ccfc70e5e9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/IdeCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/IdeCodeStyleOptions.cs @@ -5,9 +5,12 @@ using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +#if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; +#endif + namespace Microsoft.CodeAnalysis.CodeStyle; internal abstract class IdeCodeStyleOptions diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 91ce2c593e0ea..fc31b13ac5803 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -543,7 +543,6 @@ - diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/IdeAnalyzerOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/IdeAnalyzerOptions.cs index 3f13eb7deb333..4b3dfa529a388 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/IdeAnalyzerOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/IdeAnalyzerOptions.cs @@ -6,7 +6,10 @@ using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeStyle; + +#if !CODE_STYLE using Microsoft.CodeAnalysis.Host; +#endif namespace Microsoft.CodeAnalysis.Diagnostics; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/SyntaxFormattingOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/SyntaxFormattingOptions.cs index afe452f08bbee..6d3ef1c8a1893 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/SyntaxFormattingOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/SyntaxFormattingOptions.cs @@ -8,9 +8,12 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +#if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; +#endif + namespace Microsoft.CodeAnalysis.Formatting; internal abstract class SyntaxFormattingOptions diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/IndentationOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/IndentationOptions.cs index 2262cf292ca6e..7c32cbd3ac91a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/IndentationOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/IndentationOptions.cs @@ -4,7 +4,10 @@ using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Formatting; + +#if !CODE_STYLE using Microsoft.CodeAnalysis.Host; +#endif namespace Microsoft.CodeAnalysis.Indentation { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs index fb0ba6a9e0f2d..ccf86a68c9978 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs @@ -5,9 +5,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +#if !CODE_STYLE +using Microsoft.CodeAnalysis.Host; +#endif + namespace Microsoft.CodeAnalysis.CodeStyle { internal static class NamingStyleOptions diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb index 07d4675771c7f..bb1204a4fb833 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb @@ -6,8 +6,6 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.Indentation -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Formatting From 7dced21b0fb63d561a12956c156dd929bfecc041 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Dec 2022 17:08:09 -0800 Subject: [PATCH 022/274] Grammar --- .../Workspace/Solution/WeaklyCachedRecoverableValueSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs index fe2cc137a61da..c9dfe9b39d7ca 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs @@ -105,7 +105,7 @@ public override T GetValue(CancellationToken cancellationToken) if (TryGetWeakValue(out var instance)) return instance; - // Otherwise, we're either holding the value strongly, or we need to recovery it from secondary storage. + // Otherwise, we're either holding the value strongly, or we need to recover it from secondary storage. using (Gate.DisposableWait(cancellationToken)) { if (!TryGetStrongOrWeakValue(out instance)) From 1b3fdbe1af7742c6a180e41ae3fe846e41011d90 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Dec 2022 17:10:31 -0800 Subject: [PATCH 023/274] Explicitly yield --- .../Workspace/Solution/WeaklyCachedRecoverableValueSource.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs index c9dfe9b39d7ca..4bab136674f0d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs @@ -165,6 +165,10 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) async Task SaveAndResetInitialValue(Task previousTask) { + // Explicitly yield our work so that the outer caller can immediately get our task, assign to + // s_latestTask and let go of s_taskGuard. + await Task.Yield(); + // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They // do not affect if we run or not. await previousTask.NoThrowAwaitableInternal(captureContext: false); From d04ba5fceadb56d0f73f48947c7fee7a13e05dee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Dec 2022 17:32:08 -0800 Subject: [PATCH 024/274] Switch to explicit task chaining --- .../WeaklyCachedRecoverableValueSource.cs | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs index 4bab136674f0d..3b1e6a543ba56 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/WeaklyCachedRecoverableValueSource.cs @@ -157,31 +157,24 @@ private void UpdateWeakReferenceAndEnqueueSaveTask_NoLock(T instance) using (s_taskGuard.DisposableWait()) { // force all save tasks to be in sequence so we don't hog all the threads. - s_latestTask = SaveAndResetInitialValue(s_latestTask); + s_latestTask = s_latestTask.SafeContinueWithFromAsync(async _ => + { + // Now defer to our subclass to actually save the instance to secondary storage. + await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); + + // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, + // we want to keep it around to service future requests. Once we do clear out this value, then all + // future request will either retrieve the value from the weak reference (if anyone else is holding onto + // it), or will recover from underlying storage. + _initialValue = null; + }, + CancellationToken.None, + // Ensure we run continuations asynchronously so that we don't start running the continuation while + // holding s_taskGuard. + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default); } } - - return; - - async Task SaveAndResetInitialValue(Task previousTask) - { - // Explicitly yield our work so that the outer caller can immediately get our task, assign to - // s_latestTask and let go of s_taskGuard. - await Task.Yield(); - - // First wait for the prior task in the chain to be done. Ignore all errors from prior tasks. They - // do not affect if we run or not. - await previousTask.NoThrowAwaitableInternal(captureContext: false); - - // Now defer to our subclass to actually save the instance to secondary storage. - await SaveAsync(instance, CancellationToken.None).ConfigureAwait(false); - - // Only set _initialValue to null if the saveTask completed successfully. If the save did not complete, - // we want to keep it around to service future requests. Once we do clear out this value, then all - // future request will either retrieve the value from the weak reference (if anyone else is holding onto - // it), or will recover from underlying storage. - _initialValue = null; - } } } } From f725c81348bf20a4fff43f2039960c50f8f3960c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Dec 2022 11:43:09 -0800 Subject: [PATCH 025/274] Remove code for cloning compilations for skeleton references --- .../Options/WorkspaceConfigurationOptionsStorage.cs | 7 +------ .../Portable/Workspace/IWorkspaceConfigurationService.cs | 6 ++---- .../Solution/SolutionState.SkeletonReferenceCache.cs | 6 +----- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs index fc2a7b87d3191..b9b07fa548335 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs @@ -13,8 +13,7 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi => new( CacheStorage: globalOptions.GetOption(CloudCacheFeatureFlag) ? StorageDatabase.CloudCache : globalOptions.GetOption(Database), EnableOpeningSourceGeneratedFiles: globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspace) ?? - globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag), - DisableCloneWhenProducingSkeletonReferences: globalOptions.GetOption(DisableCloneWhenProducingSkeletonReferences)); + globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag)); public static readonly Option2 Database = new( "FeatureManager/Storage", nameof(Database), WorkspaceConfigurationOptions.Default.CacheStorage, @@ -24,10 +23,6 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi "FeatureManager/Storage", "CloudCacheFeatureFlag", WorkspaceConfigurationOptions.Default.CacheStorage == StorageDatabase.CloudCache, new FeatureFlagStorageLocation("Roslyn.CloudCache3")); - public static readonly Option2 DisableCloneWhenProducingSkeletonReferences = new( - "WorkspaceConfigurationOptions", "DisableCloneWhenProducingSkeletonReferences", WorkspaceConfigurationOptions.Default.DisableCloneWhenProducingSkeletonReferences, - new FeatureFlagStorageLocation("Roslyn.DisableCloneWhenProducingSkeletonReferences")); - public static readonly Option2 DisableReferenceManagerRecoverableMetadata = new( "WorkspaceConfigurationOptions", "DisableReferenceManagerRecoverableMetadata", WorkspaceConfigurationOptions.Default.DisableReferenceManagerRecoverableMetadata, new FeatureFlagStorageLocation("Roslyn.DisableReferenceManagerRecoverableMetadata")); diff --git a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs index 532d6e4451476..cc38b1bbb9e87 100644 --- a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs +++ b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs @@ -25,9 +25,8 @@ internal interface IWorkspaceConfigurationService : IWorkspaceService internal readonly record struct WorkspaceConfigurationOptions( [property: DataMember(Order = 0)] StorageDatabase CacheStorage = StorageDatabase.SQLite, [property: DataMember(Order = 1)] bool EnableOpeningSourceGeneratedFiles = false, - [property: DataMember(Order = 2)] bool DisableCloneWhenProducingSkeletonReferences = false, - [property: DataMember(Order = 3)] bool DisableReferenceManagerRecoverableMetadata = false, - [property: DataMember(Order = 4)] bool DisableBackgroundCompilation = false) + [property: DataMember(Order = 2)] bool DisableReferenceManagerRecoverableMetadata = false, + [property: DataMember(Order = 3)] bool DisableBackgroundCompilation = false) { public WorkspaceConfigurationOptions() : this(CacheStorage: StorageDatabase.SQLite) @@ -43,7 +42,6 @@ public WorkspaceConfigurationOptions() public static readonly WorkspaceConfigurationOptions RemoteDefault = new( CacheStorage: StorageDatabase.None, EnableOpeningSourceGeneratedFiles: false, - DisableCloneWhenProducingSkeletonReferences: false, DisableReferenceManagerRecoverableMetadata: false, DisableBackgroundCompilation: false); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs index 0b1bff510257e..b3bb20238bf8f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs @@ -241,11 +241,7 @@ private bool TryReadSkeletonReferenceSetAtThisVersion(VersionStamp version, out { using var stream = SerializableBytes.CreateWritableStream(); - var optionsService = workspace.Services.GetService(); - var doNotClone = optionsService != null && optionsService.Options.DisableCloneWhenProducingSkeletonReferences; - - var compilationToEmit = doNotClone ? compilation : compilation.Clone(); - var emitResult = compilationToEmit.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); if (emitResult.Success) { From 44c7164a80a89e9ea086e1b422be60412c5ec391 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 09:53:42 -0800 Subject: [PATCH 026/274] Update helper --- .../Core/Portable/Workspace/Workspace.cs | 123 +++++++++--------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index b196d4433f2bf..5de2a872dfbc7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -780,49 +780,6 @@ protected virtual void CheckDocumentCanBeRemoved(DocumentId documentId) { } - /// - /// Call this method when the text of a document is changed on disk. - /// - protected internal void OnDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) - { - SetCurrentSolution( - oldSolution => - { - CheckDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.DocumentChanged, documentId: documentId, - onAfterUpdate: (_, newSolution) => this.OnDocumentTextChanged(newSolution.GetRequiredDocument(documentId))); - } - - /// - /// Call this method when the text of a additional document is changed on disk. - /// - protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) - { - SetCurrentSolution( - oldSolution => - { - CheckAdditionalDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithAdditionalDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.AdditionalDocumentChanged, documentId: documentId); - } - - /// - /// Call this method when the text of a analyzer config document is changed on disk. - /// - protected internal void OnAnalyzerConfigDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) - { - SetCurrentSolution( - oldSolution => - { - CheckAnalyzerConfigDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.AnalyzerConfigDocumentChanged, documentId: documentId); - } - /// /// Call this method when the document info changes, such as the name, folders or file path. /// @@ -871,11 +828,10 @@ protected internal void OnDocumentTextChanged(DocumentId documentId, SourceText { OnAnyDocumentTextChanged( documentId, - newText, - mode, + (newText, mode), CheckDocumentIsInSolution, (solution, docId) => solution.GetRelatedDocumentIds(docId), - (solution, docId, text, preservationMode) => solution.WithDocumentText(docId, text, preservationMode), + (solution, docId, newTextAndMode) => solution.WithDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.DocumentChanged, isCodeDocument: true); } @@ -887,11 +843,10 @@ protected internal void OnAdditionalDocumentTextChanged(DocumentId documentId, S { OnAnyDocumentTextChanged( documentId, - newText, - mode, + (newText, mode), CheckAdditionalDocumentIsInSolution, (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents - (solution, docId, text, preservationMode) => solution.WithAdditionalDocumentText(docId, text, preservationMode), + (solution, docId, newTextAndMode) => solution.WithAdditionalDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.AdditionalDocumentChanged, isCodeDocument: false); } @@ -903,28 +858,80 @@ protected internal void OnAnalyzerConfigDocumentTextChanged(DocumentId documentI { OnAnyDocumentTextChanged( documentId, - newText, - mode, + (newText, mode), CheckAnalyzerConfigDocumentIsInSolution, (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents - (solution, docId, text, preservationMode) => solution.WithAnalyzerConfigDocumentText(docId, text, preservationMode), + (solution, docId, newTextAndMode) => solution.WithAnalyzerConfigDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.AnalyzerConfigDocumentChanged, isCodeDocument: false); } + /// + /// Call this method when the text of a document is changed on disk. + /// + protected internal void OnDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) + { + //OnAnyDocumentTextChanged( + // documentId, + // newText, + // mode, + // CheckDocumentIsInSolution, + // (solution, docId) => solution.GetRelatedDocumentIds(docId), + // (solution, docId, text, preservationMode) => solution.WithDocumentText(docId, text, preservationMode), + // WorkspaceChangeKind.DocumentChanged, + // isCodeDocument: true); + + + SetCurrentSolution( + oldSolution => + { + CheckDocumentIsInSolution(oldSolution, documentId); + return oldSolution.WithDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); + }, + WorkspaceChangeKind.DocumentChanged, documentId: documentId, + onAfterUpdate: (_, newSolution) => this.OnDocumentTextChanged(newSolution.GetRequiredDocument(documentId))); + } + + /// + /// Call this method when the text of a additional document is changed on disk. + /// + protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) + { + SetCurrentSolution( + oldSolution => + { + CheckAdditionalDocumentIsInSolution(oldSolution, documentId); + return oldSolution.WithAdditionalDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); + }, + WorkspaceChangeKind.AdditionalDocumentChanged, documentId: documentId); + } + + /// + /// Call this method when the text of a analyzer config document is changed on disk. + /// + protected internal void OnAnalyzerConfigDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) + { + SetCurrentSolution( + oldSolution => + { + CheckAnalyzerConfigDocumentIsInSolution(oldSolution, documentId); + return oldSolution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); + }, + WorkspaceChangeKind.AnalyzerConfigDocumentChanged, documentId: documentId); + } + /// /// When a s text is changed, we need to make sure all of the linked /// files also have their content updated in the new solution before applying it to the /// workspace to avoid the workspace having solutions with linked files where the contents /// do not match. /// - private void OnAnyDocumentTextChanged( + private void OnAnyDocumentTextChanged( DocumentId documentId, - SourceText newText, - PreservationMode mode, + TArg arg, Action checkIsInSolution, Func> getRelatedDocuments, - Func updateSolutionWithText, + Func updateSolutionWithText, WorkspaceChangeKind changeKind, bool isCodeDocument) { @@ -946,14 +953,14 @@ private void OnAnyDocumentTextChanged( foreach (var linkedDocument in linkedDocuments) { var previousSolution = newSolution; - newSolution = data.updateSolutionWithText(newSolution, linkedDocument, data.newText, data.mode); + newSolution = data.updateSolutionWithText(newSolution, linkedDocument, data.arg); if (previousSolution != newSolution) updatedDocumentIds.Add(linkedDocument); } return newSolution; }, - data: (@this: this, documentId, newText, mode, checkIsInSolution, getRelatedDocuments, updateSolutionWithText, changeKind, isCodeDocument, updatedDocumentIds), + data: (@this: this, documentId, arg, checkIsInSolution, getRelatedDocuments, updateSolutionWithText, changeKind, isCodeDocument, updatedDocumentIds), onAfterUpdate: static (oldSolution, newSolution, data) => { if (data.isCodeDocument) From 92aad537094076a046ce86f4e0eeefc2e8789878 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 10:03:45 -0800 Subject: [PATCH 027/274] Update all linked docs when a textloader changes for one of htem --- .../Core/Portable/Workspace/Workspace.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 5de2a872dfbc7..cd967dc31103b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -871,25 +871,14 @@ protected internal void OnAnalyzerConfigDocumentTextChanged(DocumentId documentI /// protected internal void OnDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) { - //OnAnyDocumentTextChanged( - // documentId, - // newText, - // mode, - // CheckDocumentIsInSolution, - // (solution, docId) => solution.GetRelatedDocumentIds(docId), - // (solution, docId, text, preservationMode) => solution.WithDocumentText(docId, text, preservationMode), - // WorkspaceChangeKind.DocumentChanged, - // isCodeDocument: true); - - - SetCurrentSolution( - oldSolution => - { - CheckDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.DocumentChanged, documentId: documentId, - onAfterUpdate: (_, newSolution) => this.OnDocumentTextChanged(newSolution.GetRequiredDocument(documentId))); + OnAnyDocumentTextChanged( + documentId, + loader, + CheckDocumentIsInSolution, + (solution, docId) => solution.GetRelatedDocumentIds(docId), + (solution, docId, loader) => solution.WithDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), + WorkspaceChangeKind.DocumentChanged, + isCodeDocument: true); } /// @@ -897,13 +886,14 @@ protected internal void OnDocumentTextLoaderChanged(DocumentId documentId, TextL /// protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) { - SetCurrentSolution( - oldSolution => - { - CheckAdditionalDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithAdditionalDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.AdditionalDocumentChanged, documentId: documentId); + OnAnyDocumentTextChanged( + documentId, + loader, + CheckAdditionalDocumentIsInSolution, + (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents + (solution, docId, loader) => solution.WithAdditionalDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), + WorkspaceChangeKind.AdditionalDocumentChanged, + isCodeDocument: false); } /// @@ -911,13 +901,14 @@ protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documen /// protected internal void OnAnalyzerConfigDocumentTextLoaderChanged(DocumentId documentId, TextLoader loader) { - SetCurrentSolution( - oldSolution => - { - CheckAnalyzerConfigDocumentIsInSolution(oldSolution, documentId); - return oldSolution.WithAnalyzerConfigDocumentTextLoader(documentId, loader, PreservationMode.PreserveValue); - }, - WorkspaceChangeKind.AnalyzerConfigDocumentChanged, documentId: documentId); + OnAnyDocumentTextChanged( + documentId, + loader, + CheckAnalyzerConfigDocumentIsInSolution, + (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents + (solution, docId, loader) => solution.WithAnalyzerConfigDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), + WorkspaceChangeKind.AnalyzerConfigDocumentChanged, + isCodeDocument: false); } /// From ff7d6259ea5990a134821f7174ab0f5de01b2ca7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 12:25:56 -0800 Subject: [PATCH 028/274] rename file --- .../Completion/CompletionServiceTests.cs | 2 +- ...emoveUnnecessaryPragmaSuppressionsTests.cs | 2 +- .../Core/Interactive/InteractiveSession.cs | 2 +- ...ractiveWorkspace.SolutionAnalyzerSetter.cs | 2 +- .../Core/Interactive/InteractiveWorkspace.cs | 2 +- .../CompileTimeSolutionProviderTests.cs | 4 +- .../EditAndContinueWorkspaceServiceTests.cs | 2 +- .../Ordering/RequestOrderingTests.cs | 2 +- ...spaceImpl.SolutionAnalyzerSetterService.cs | 2 +- .../VisualStudioWorkspaceImpl.cs | 6 +- .../Services/SolutionAssetCacheTests.cs | 2 +- .../Services/SolutionServiceTests.cs | 2 +- .../Core/Portable/Workspace/Workspace.cs | 70 +++++++++---------- .../SolutionWithSourceGeneratorTests.cs | 12 ++-- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs index 29432880c9997..29b75ed20ad31 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs @@ -171,7 +171,7 @@ public class C1 .AddAnalyzerReference(analyzerReference) .AddDocument("Document1.cs", sourceMarkup, filePath: "Document1.cs").Project; - Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); var completionService = document.GetLanguageService(); diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs index 227ac0e5036f3..0cfa1fe3660a6 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs @@ -1064,7 +1064,7 @@ void M() var compilationOptions = TestOptions.DebugDll.WithSpecificDiagnosticOptions( ImmutableDictionary.Empty .Add(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, ReportDiagnostic.Suppress)); - workspace.SetCurrentSolution(s => s.WithProjectCompilationOptions(projectId, compilationOptions), WorkspaceChangeKind.ProjectChanged, projectId); + workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithProjectCompilationOptions(projectId, compilationOptions), WorkspaceChangeKind.ProjectChanged, projectId); var (actions, _) = await GetCodeActionsAsync(workspace, parameters); Assert.True(actions.Length == 0, "An action was offered when none was expected"); diff --git a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs index f13654f4f4e5f..0c272da207d4c 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs @@ -224,7 +224,7 @@ private void AddSubmissionProjectNoLock(ITextBuffer submissionBuffer, string lan textDocument.Rename(newSubmissionFilePath); // Chain projects to the the last submission that successfully executed. - _workspace.SetCurrentSolution(solution => + _workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => { if (initializationScriptProjectId != null) { diff --git a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs index 24eab269c2826..801a1035e03db 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs @@ -34,7 +34,7 @@ public SolutionAnalyzerSetter(InteractiveWorkspace workspace) => _workspace = workspace; public void SetAnalyzerReferences(ImmutableArray references) - => _workspace.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged); + => _workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged); } } } diff --git a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs index 2348187d602f1..26d736c2ee9b7 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs @@ -81,7 +81,7 @@ public void ResetSolution() ClearOpenDocuments(); var emptySolution = CreateSolution(SolutionId.CreateNewId("InteractiveSolution")); - SetCurrentSolution(solution => emptySolution.WithAnalyzerReferences(solution.AnalyzerReferences), WorkspaceChangeKind.SolutionCleared); + SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => emptySolution.WithAnalyzerReferences(solution.AnalyzerReferences), WorkspaceChangeKind.SolutionCleared); } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs b/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs index f5cace812b627..3f2cb1814daac 100644 --- a/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs @@ -116,7 +116,7 @@ public async Task GeneratorOutputCachedBetweenAcrossCompileTimeSolutions() var analyzerConfigText = "is_global = true\r\nbuild_property.SuppressRazorSourceGenerator = true"; - workspace.SetCurrentSolution(s => s. + workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s. AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj", "proj", LanguageNames.CSharp)). AddAnalyzerReference(projectId, new TestGeneratorReference(generator)). AddAdditionalDocument(additionalDocumentId, "additional", SourceText.From(""), filePath: "additional.razor"). @@ -137,7 +137,7 @@ public async Task GeneratorOutputCachedBetweenAcrossCompileTimeSolutions() // Now do something that shouldn't force the generator to rerun; we must change this through the workspace since the // service itself uses versions that won't change otherwise var documentId = DocumentId.CreateNewId(projectId); - workspace.SetCurrentSolution( + workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents( s => s.AddDocument(documentId, "Test.cs", "// source file"), WorkspaceChangeKind.DocumentAdded, projectId, diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 3106342dbe8e8..d7e73457d34ec 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -4699,7 +4699,7 @@ public async Task DefaultPdbMatchingSourceTextProvider() loader: new WorkspaceFileTextLoader(workspace.Services.SolutionServices, sourceFile.Path, Encoding.UTF8), filePath: sourceFile.Path)); - Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionAdded)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => solution, WorkspaceChangeKind.SolutionAdded)); solution = workspace.CurrentSolution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs index 4db0bb46b2269..65bd974682834 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs @@ -209,7 +209,7 @@ public async Task NonMutatingRequestsOperateOnTheSameSolutionAfterMutation() Assert.Equal(expectedSolution, solution); // Apply some random change to the workspace that the LSP server doesn't "see" - testLspServer.TestWorkspace.SetCurrentSolution(s => s.WithProjectName(s.Projects.First().Id, "NewName"), WorkspaceChangeKind.ProjectChanged); + testLspServer.TestWorkspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithProjectName(s.Projects.First().Id, "NewName"), WorkspaceChangeKind.ProjectChanged); expectedSolution = testLspServer.GetCurrentSolution(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs index 51945225467d8..3ec6ae73c8b60 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs @@ -35,7 +35,7 @@ public SolutionAnalyzerSetter(VisualStudioWorkspaceImpl workspace) => _workspace = workspace; public void SetAnalyzerReferences(ImmutableArray references) - => _workspace.ApplyChangeToWorkspace(w => w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); + => _workspace.ApplyChangeToWorkspace(w => w.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); } } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index eeb532930a7db..2cba00032fb40 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1594,7 +1594,7 @@ public void ApplyChangeToWorkspace(ProjectId projectId, Func solutionChanges.Solution, solutionChanges.WorkspaceChangeKind, solutionChanges.WorkspaceChangeProjectId, @@ -1730,7 +1730,7 @@ internal void RemoveSolution_NoLock() // to look up by that location; we also keep the existing analyzer references around since those are host-level analyzers that were loaded asynchronously. ClearOpenDocuments(); - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( solution => CreateSolution( SolutionInfo.Create( SolutionId.CreateNewId(), diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs index 06ae77da3dfeb..9b1b7a0128188 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs @@ -100,7 +100,7 @@ public async Task TestSolutionKeepsAssetPinned() Assert.False(gotChecksum2); // Now, add a project. At this point, the original pinned object should go away. - workspace.SetCurrentSolution(solution => solution.AddProject("Project", "Assembly", LanguageNames.CSharp).Solution, WorkspaceChangeKind.ProjectAdded); + workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => solution.AddProject("Project", "Assembly", LanguageNames.CSharp).Solution, WorkspaceChangeKind.ProjectAdded); for (var i = 0; i < 10; i++) { diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index c26a06654968a..77e998cc6c07e 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -192,7 +192,7 @@ static void ValidateProperties(Solution solution, int version) Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } - Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); await VerifySolutionUpdate(workspace, newSolutionGetter: s => SetProjectProperties(s, version: 1), diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index cd967dc31103b..082dd2a2978a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -194,7 +194,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio /// The id of the project updated by to be passed to the workspace change event. /// The id of the document updated by to be passed to the workspace change event. /// True if was set to the transformed solution, false if the transformation did not change the solution. - internal bool SetCurrentSolution( + internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( Func transformation, WorkspaceChangeKind kind, ProjectId? projectId = null, @@ -312,7 +312,7 @@ public OptionSet Options internal void UpdateCurrentSolutionOnOptionsChanged() { - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => oldSolution.WithOptions(new SolutionOptionSet(_legacyOptions)), WorkspaceChangeKind.SolutionChanged); } @@ -450,7 +450,7 @@ private static Solution CheckAndAddProject(Solution newSolution, ProjectInfo pro /// protected internal void OnSolutionAdded(SolutionInfo solutionInfo) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckSolutionIsEmpty(oldSolution); @@ -469,7 +469,7 @@ protected internal void OnSolutionAdded(SolutionInfo solutionInfo) /// protected internal void OnSolutionReloaded(SolutionInfo reloadedSolutionInfo) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { var newSolution = this.CreateSolution(reloadedSolutionInfo); @@ -490,7 +490,7 @@ protected internal void OnSolutionReloaded(SolutionInfo reloadedSolutionInfo) /// protected internal void OnSolutionRemoved() { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( _ => this.CreateSolution(SolutionId.CreateNewId()), WorkspaceChangeKind.SolutionRemoved, onBeforeUpdate: (_, _) => this.ClearSolutionData()); @@ -501,7 +501,7 @@ protected internal void OnSolutionRemoved() /// protected internal void OnProjectAdded(ProjectInfo projectInfo) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => CheckAndAddProject(oldSolution, projectInfo), WorkspaceChangeKind.ProjectAdded, projectId: projectInfo.Id); } @@ -512,7 +512,7 @@ protected internal void OnProjectAdded(ProjectInfo projectInfo) protected internal virtual void OnProjectReloaded(ProjectInfo reloadedProjectInfo) { var projectId = reloadedProjectInfo.Id; - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckProjectIsInSolution(oldSolution, projectId); @@ -528,7 +528,7 @@ protected internal virtual void OnProjectReloaded(ProjectInfo reloadedProjectInf /// protected internal virtual void OnProjectRemoved(ProjectId projectId) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckProjectIsInSolution(oldSolution, projectId); @@ -557,19 +557,19 @@ protected virtual void CheckProjectCanBeRemoved(ProjectId projectId) /// Call this method when a project's assembly name is changed in the host environment. /// protected internal void OnAssemblyNameChanged(ProjectId projectId, string assemblyName) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectAssemblyName(projectId, assemblyName), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectAssemblyName(projectId, assemblyName), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's output file path is changed in the host environment. /// protected internal void OnOutputFilePathChanged(ProjectId projectId, string? outputFilePath) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectOutputFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectOutputFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's output ref file path is changed in the host environment. /// protected internal void OnOutputRefFilePathChanged(ProjectId projectId, string? outputFilePath) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectOutputRefFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectOutputRefFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's name is changed in the host environment. @@ -579,32 +579,32 @@ protected internal void OnOutputRefFilePathChanged(ProjectId projectId, string? // I'm leaving this marked as "non-null" so as not to say we actually support that behavior. The underlying // requirement is ProjectInfo.ProjectAttributes holds a non-null name, so you can't get a null into this even if you tried. protected internal void OnProjectNameChanged(ProjectId projectId, string name, string? filePath) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectName(projectId, name).WithProjectFilePath(projectId, filePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectName(projectId, name).WithProjectFilePath(projectId, filePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's default namespace is changed in the host environment. /// internal void OnDefaultNamespaceChanged(ProjectId projectId, string? defaultNamespace) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectDefaultNamespace(projectId, defaultNamespace), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectDefaultNamespace(projectId, defaultNamespace), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's compilation options are changed in the host environment. /// protected internal void OnCompilationOptionsChanged(ProjectId projectId, CompilationOptions options) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectCompilationOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectCompilationOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's parse options are changed in the host environment. /// protected internal void OnParseOptionsChanged(ProjectId projectId, ParseOptions options) - => SetCurrentSolution(oldSolution => oldSolution.WithProjectParseOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectParseOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project reference is added to a project in the host environment. /// protected internal void OnProjectReferenceAdded(ProjectId projectId, ProjectReference projectReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectIsInCurrentSolution(projectReference.ProjectId); CheckProjectDoesNotHaveProjectReference(projectId, projectReference); @@ -621,7 +621,7 @@ protected internal void OnProjectReferenceAdded(ProjectId projectId, ProjectRefe /// protected internal void OnProjectReferenceRemoved(ProjectId projectId, ProjectReference projectReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectIsInCurrentSolution(projectReference.ProjectId); CheckProjectHasProjectReference(projectId, projectReference); @@ -635,7 +635,7 @@ protected internal void OnProjectReferenceRemoved(ProjectId projectId, ProjectRe /// protected internal void OnMetadataReferenceAdded(ProjectId projectId, MetadataReference metadataReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectDoesNotHaveMetadataReference(projectId, metadataReference); return oldSolution.AddMetadataReference(projectId, metadataReference); @@ -647,7 +647,7 @@ protected internal void OnMetadataReferenceAdded(ProjectId projectId, MetadataRe /// protected internal void OnMetadataReferenceRemoved(ProjectId projectId, MetadataReference metadataReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectHasMetadataReference(projectId, metadataReference); return oldSolution.RemoveMetadataReference(projectId, metadataReference); @@ -659,7 +659,7 @@ protected internal void OnMetadataReferenceRemoved(ProjectId projectId, Metadata /// protected internal void OnAnalyzerReferenceAdded(ProjectId projectId, AnalyzerReference analyzerReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectDoesNotHaveAnalyzerReference(projectId, analyzerReference); return oldSolution.AddAnalyzerReference(projectId, analyzerReference); @@ -671,7 +671,7 @@ protected internal void OnAnalyzerReferenceAdded(ProjectId projectId, AnalyzerRe /// protected internal void OnAnalyzerReferenceRemoved(ProjectId projectId, AnalyzerReference analyzerReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckProjectHasAnalyzerReference(projectId, analyzerReference); return oldSolution.RemoveAnalyzerReference(projectId, analyzerReference); @@ -683,7 +683,7 @@ protected internal void OnAnalyzerReferenceRemoved(ProjectId projectId, Analyzer /// internal void OnSolutionAnalyzerReferenceAdded(AnalyzerReference analyzerReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckSolutionDoesNotHaveAnalyzerReference(oldSolution, analyzerReference); return oldSolution.AddAnalyzerReference(analyzerReference); @@ -695,7 +695,7 @@ internal void OnSolutionAnalyzerReferenceAdded(AnalyzerReference analyzerReferen /// internal void OnSolutionAnalyzerReferenceRemoved(AnalyzerReference analyzerReference) { - SetCurrentSolution(oldSolution => + SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => { CheckSolutionHasAnalyzerReference(oldSolution, analyzerReference); return oldSolution.RemoveAnalyzerReference(analyzerReference); @@ -708,20 +708,20 @@ internal void OnSolutionAnalyzerReferenceRemoved(AnalyzerReference analyzerRefer /// // TODO: make it public internal void OnHasAllInformationChanged(ProjectId projectId, bool hasAllInformation) - => SetCurrentSolution(oldSolution => oldSolution.WithHasAllInformation(projectId, hasAllInformation), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithHasAllInformation(projectId, hasAllInformation), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's RunAnalyzers property is changed in the host environment. /// internal void OnRunAnalyzersChanged(ProjectId projectId, bool runAnalyzers) - => SetCurrentSolution(oldSolution => oldSolution.WithRunAnalyzers(projectId, runAnalyzers), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithRunAnalyzers(projectId, runAnalyzers), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a document is added to a project in the host environment. /// protected internal void OnDocumentAdded(DocumentInfo documentInfo) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => oldSolution.AddDocument(documentInfo), WorkspaceChangeKind.DocumentAdded, documentId: documentInfo.Id); } @@ -749,7 +749,7 @@ protected internal void OnDocumentsAdded(ImmutableArray documentIn protected internal void OnDocumentReloaded(DocumentInfo newDocumentInfo) { var documentId = newDocumentInfo.Id; - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => oldSolution.RemoveDocument(documentId).AddDocument(newDocumentInfo), WorkspaceChangeKind.DocumentReloaded, documentId: documentId); } @@ -759,7 +759,7 @@ protected internal void OnDocumentReloaded(DocumentInfo newDocumentInfo) /// protected internal void OnDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -785,7 +785,7 @@ protected virtual void CheckDocumentCanBeRemoved(DocumentId documentId) /// protected internal void OnDocumentInfoChanged(DocumentId documentId, DocumentInfo newInfo) { - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -980,7 +980,7 @@ private void OnAnyDocumentTextChanged( /// protected internal void OnDocumentSourceCodeKindChanged(DocumentId documentId, SourceCodeKind sourceCodeKind) { - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -996,7 +996,7 @@ protected internal void OnDocumentSourceCodeKindChanged(DocumentId documentId, S protected internal void OnAdditionalDocumentAdded(DocumentInfo documentInfo) { var documentId = documentInfo.Id; - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckProjectIsInSolution(oldSolution, documentId.ProjectId); @@ -1011,7 +1011,7 @@ protected internal void OnAdditionalDocumentAdded(DocumentInfo documentInfo) /// protected internal void OnAdditionalDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckAdditionalDocumentIsInSolution(oldSolution, documentId); @@ -1034,7 +1034,7 @@ protected internal void OnAdditionalDocumentRemoved(DocumentId documentId) protected internal void OnAnalyzerConfigDocumentAdded(DocumentInfo documentInfo) { var documentId = documentInfo.Id; - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckProjectIsInSolution(oldSolution, documentId.ProjectId); @@ -1050,7 +1050,7 @@ protected internal void OnAnalyzerConfigDocumentAdded(DocumentInfo documentInfo) /// protected internal void OnAnalyzerConfigDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolution( + this.SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => { CheckAnalyzerConfigDocumentIsInSolution(oldSolution, documentId); @@ -1071,7 +1071,7 @@ protected internal void OnAnalyzerConfigDocumentRemoved(DocumentId documentId) /// protected void UpdateReferencesAfterAdd() { - SetCurrentSolution( + SetCurrentSolutionAndUnifyLinkedDocumentContents( oldSolution => UpdateReferencesAfterAdd(oldSolution), WorkspaceChangeKind.SolutionChanged); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index cceca9efe2ed7..0f74f8f93a1bc 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -572,7 +572,7 @@ public async Task OpenSourceGeneratedUpdatedToBufferContentsWhenCallingGetOpenDo var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -596,7 +596,7 @@ public async Task OpenSourceGeneratedFileDoesNotCreateNewSnapshotIfContentsKnown var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// StaticContent", Encoding.UTF8).Container; @@ -616,7 +616,7 @@ public async Task OpenSourceGeneratedFileMatchesBufferContentsEvenIfGeneratedFil .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", SourceText.From("")); - Assert.True(workspace.SetCurrentSolution(_ => originalAdditionalFile.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => originalAdditionalFile.Project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await originalAdditionalFile.Project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -647,7 +647,7 @@ public async Task OpenSourceGeneratedDocumentUpdatedAndVisibleInProjectReference solution = AddEmptyProject(solution).AddProjectReference( new ProjectReference(projectIdWithGenerator)).Solution; - Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await workspace.CurrentSolution.GetRequiredProject(projectIdWithGenerator).GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -674,7 +674,7 @@ public async Task OpenSourceGeneratedDocumentsUpdateIsDocumentOpenAndCloseWorks( var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -703,7 +703,7 @@ public async Task FreezingSolutionEnsuresGeneratorsDoNotRun(bool forkBeforeFreez .AddAnalyzerReference(analyzerReference) .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project; - Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var documentToFreeze = workspace.CurrentSolution.Projects.Single().Documents.Single(); From f6e0598f7fab10a643c140db52f0ddd34de2f14c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 13:03:15 -0800 Subject: [PATCH 029/274] UPdate document contents when changing the workspace --- .../Workspace/Solution/DocumentState.cs | 20 ++++++ .../Portable/Workspace/Solution/Solution.cs | 6 ++ .../Workspace/Solution/SolutionState.cs | 14 ++++ .../Core/Portable/Workspace/Workspace.cs | 68 ++++++++++++++++++- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 3e1c7e5b0c159..6858dd9cd4f47 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -81,6 +81,8 @@ public DocumentState( } } + public ValueSource? TreeSource => _treeSource; + [MemberNotNullWhen(true, nameof(_treeSource))] [MemberNotNullWhen(true, nameof(_options))] internal bool SupportsSyntaxTree @@ -490,6 +492,19 @@ protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSou treeSource: newTreeSource); } + internal DocumentState UpdateTextAndTreeSource(ITextAndVersionSource textSource, ValueSource? treeSource) + { + return new DocumentState( + LanguageServices, + solutionServices, + Services, + Attributes, + _options, + textSource, + LoadTextOptions, + treeSource); + } + internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) { if (!SupportsSyntaxTree) @@ -686,6 +701,11 @@ private static void BindSyntaxTreeToId(SyntaxTree tree, DocumentId id) return id; } + public DocumentState WithTextAndTree(ITextAndVersionSource textSource, ValueSource? treeSource) + { + return new DocumentState(this.LanguageServices, this.solutionServices, this.Services, this.Attributes, this._options, textSource, this.LoadTextOptions, treeSource); + } + private static void CheckTree( SyntaxTree newTree, SourceText newText, diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index ecfea4241e501..6ec45d4dfbfbc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1550,6 +1550,12 @@ public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, P return new Solution(newState); } + internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) + { + var newState = _state.WithDocumentContentsFrom(documentId, documentState); + return newState == _state ? this : new Solution(newState); + } + /// /// Creates a new solution instance with the document specified updated to have the source /// code kind specified. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index d891fd758a071..3b66b8bc337bd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1370,6 +1370,20 @@ public SolutionState WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode ro return UpdateDocumentState(oldDocument.UpdateTree(root, mode), contentChanged: true); } + public SolutionState WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) + { + var oldDocument = GetRequiredDocumentState(documentId); + if (oldDocument.TextAndVersionSource == documentState.TextAndVersionSource && + oldDocument.TreeSource == documentState.TreeSource) + { + return this; + } + + return UpdateDocumentState( + oldDocument.UpdateTextAndTreeSource(documentState.TextAndVersionSource, documentState.TreeSource), + contentChanged: true); + } + private static async Task UpdateDocumentInCompilationAsync( Compilation compilation, DocumentState oldDocument, diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 082dd2a2978a6..7bfa09d7785ce 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -203,7 +204,11 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( Action? onAfterUpdate = null) { var (oldSolution, newSolution) = SetCurrentSolution( - transformation: static (oldSolution, data) => data.transformation(oldSolution), + transformation: static (oldSolution, data) => + { + var newSolution = data.transformation(oldSolution); + return UnifyLinkedDocumentContents(oldSolution, newSolution); + }, data: (@this: this, transformation, onBeforeUpdate, onAfterUpdate, kind, projectId, documentId), onBeforeUpdate: static (oldSolution, newSolution, data) => { @@ -220,6 +225,67 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( }); return oldSolution != newSolution; + + static Solution UnifyLinkedDocumentContents(Solution oldSolution, Solution newSolution) + { + // note: if it turns out this is too expensive, we could consider using the passed in projectId/document + // to limit the set of changes we look at. However, GetChanges *should* be fairly fast as it does + // workspace-green-node identity checks to quickly narrow down what changed. + + var changes = newSolution.GetChanges(oldSolution); + + // For all added documents, see if they link to an existing document. If so, use that existing documents text/tree. + foreach (var addedProject in changes.GetAddedProjects()) + { + foreach (var addedDocument in addedProject.Documents) + newSolution = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument.Id); + } + + using var _ = PooledHashSet.GetInstance(out var seenChangedDocuments); + + foreach (var projectChanges in changes.GetProjectChanges()) + { + // Now do the same for all added documents in a project. + foreach (var addedDocument in projectChanges.GetAddedDocuments()) + newSolution = UpdateAddedDocumentToExistingContentsInSolution(newSolution, addedDocument); + + // now, for any changed document, ensure we go and make all links to it have the same text/tree. + foreach (var changedDocumentId in projectChanges.GetChangedDocuments()) + newSolution = UpdateExistingDocumentsToChangedDocumentContents(newSolution, changedDocumentId, seenChangedDocuments); + } + + return newSolution; + } + + static Solution UpdateAddedDocumentToExistingContentsInSolution(Solution solution, DocumentId addedDocumentId) + { + var relatedDocumentIds = solution.GetRelatedDocumentIds(addedDocumentId); + foreach (var relatedDocumentId in relatedDocumentIds) + { + var relatedDocument = solution.GetRequiredDocument(relatedDocumentId); + return solution.WithDocumentContentsFrom(addedDocumentId, relatedDocument.DocumentState); + } + + return solution; + } + + static Solution UpdateExistingDocumentsToChangedDocumentContents(Solution solution, DocumentId changedDocumentId, HashSet processedDocuments) + { + // Changing a document in a linked-doc-chain will end up producing N changed documents. We only want to + // process that chain once. + if (processedDocuments.Add(changedDocumentId)) + { + var changedDocument = solution.GetRequiredDocument(changedDocumentId); + var relatedDocumentIds = solution.GetRelatedDocumentIds(changedDocumentId); + foreach (var relatedDocumentId in relatedDocumentIds) + { + if (processedDocuments.Add(relatedDocumentId)) + solution = solution.WithDocumentContentsFrom(relatedDocumentId, changedDocument.DocumentState); + } + } + + return solution; + } } /// From 474102a63ace67ae400cf0322f76ba476e0449d4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 13:08:05 -0800 Subject: [PATCH 030/274] REmove method --- .../Core/Portable/Workspace/Solution/DocumentState.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 6858dd9cd4f47..da364cd701001 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -701,11 +701,6 @@ private static void BindSyntaxTreeToId(SyntaxTree tree, DocumentId id) return id; } - public DocumentState WithTextAndTree(ITextAndVersionSource textSource, ValueSource? treeSource) - { - return new DocumentState(this.LanguageServices, this.solutionServices, this.Services, this.Attributes, this._options, textSource, this.LoadTextOptions, treeSource); - } - private static void CheckTree( SyntaxTree newTree, SourceText newText, From 8f2267ed4b817ceb8ce12cbfccd534050b14deb6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 13:44:49 -0800 Subject: [PATCH 031/274] Sharing --- .../Workspace/Solution/SolutionState.cs | 3 ++ .../Core/Portable/Workspace/Workspace.cs | 36 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 3b66b8bc337bd..8870efb45bb35 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1373,6 +1373,9 @@ public SolutionState WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode ro public SolutionState WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) { var oldDocument = GetRequiredDocumentState(documentId); + if (oldDocument == documentState) + return this; + if (oldDocument.TextAndVersionSource == documentState.TextAndVersionSource && oldDocument.TreeSource == documentState.TreeSource) { diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 7bfa09d7785ce..8e0e18533f3b9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -896,7 +896,6 @@ protected internal void OnDocumentTextChanged(DocumentId documentId, SourceText documentId, (newText, mode), CheckDocumentIsInSolution, - (solution, docId) => solution.GetRelatedDocumentIds(docId), (solution, docId, newTextAndMode) => solution.WithDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.DocumentChanged, isCodeDocument: true); @@ -911,7 +910,6 @@ protected internal void OnAdditionalDocumentTextChanged(DocumentId documentId, S documentId, (newText, mode), CheckAdditionalDocumentIsInSolution, - (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents (solution, docId, newTextAndMode) => solution.WithAdditionalDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.AdditionalDocumentChanged, isCodeDocument: false); @@ -926,7 +924,6 @@ protected internal void OnAnalyzerConfigDocumentTextChanged(DocumentId documentI documentId, (newText, mode), CheckAnalyzerConfigDocumentIsInSolution, - (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents (solution, docId, newTextAndMode) => solution.WithAnalyzerConfigDocumentText(docId, newTextAndMode.newText, newTextAndMode.mode), WorkspaceChangeKind.AnalyzerConfigDocumentChanged, isCodeDocument: false); @@ -941,7 +938,6 @@ protected internal void OnDocumentTextLoaderChanged(DocumentId documentId, TextL documentId, loader, CheckDocumentIsInSolution, - (solution, docId) => solution.GetRelatedDocumentIds(docId), (solution, docId, loader) => solution.WithDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), WorkspaceChangeKind.DocumentChanged, isCodeDocument: true); @@ -956,7 +952,6 @@ protected internal void OnAdditionalDocumentTextLoaderChanged(DocumentId documen documentId, loader, CheckAdditionalDocumentIsInSolution, - (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents (solution, docId, loader) => solution.WithAdditionalDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), WorkspaceChangeKind.AdditionalDocumentChanged, isCodeDocument: false); @@ -971,7 +966,6 @@ protected internal void OnAnalyzerConfigDocumentTextLoaderChanged(DocumentId doc documentId, loader, CheckAnalyzerConfigDocumentIsInSolution, - (solution, docId) => ImmutableArray.Create(docId), // We do not support the concept of linked additional documents (solution, docId, loader) => solution.WithAnalyzerConfigDocumentTextLoader(docId, loader, PreservationMode.PreserveValue), WorkspaceChangeKind.AnalyzerConfigDocumentChanged, isCodeDocument: false); @@ -987,7 +981,6 @@ private void OnAnyDocumentTextChanged( DocumentId documentId, TArg arg, Action checkIsInSolution, - Func> getRelatedDocuments, Func updateSolutionWithText, WorkspaceChangeKind changeKind, bool isCodeDocument) @@ -1004,20 +997,33 @@ private void OnAnyDocumentTextChanged( data.checkIsInSolution(oldSolution, data.documentId); + // First, just update the text for the document passed in. var newSolution = oldSolution; - - var linkedDocuments = data.getRelatedDocuments(oldSolution, data.documentId); - foreach (var linkedDocument in linkedDocuments) + var previousSolution = newSolution; + newSolution = data.updateSolutionWithText(newSolution, data.documentId, data.arg); + if (previousSolution != newSolution) + updatedDocumentIds.Add(data.documentId); + + // Now, see if that document is linked to anything else. If so, update their document-state to point + // at the exact text/tree-source as the doc we just made. This way we can share text/trees and not + // allocate for them unnecessarily. This is only for regular documents, not additional-docs or + // analyzer config, as those don't support links). If so + var linkedDocumentIds = oldSolution.GetRelatedDocumentIds(data.documentId); + if (linkedDocumentIds.Length > 0) { - var previousSolution = newSolution; - newSolution = data.updateSolutionWithText(newSolution, linkedDocument, data.arg); - if (previousSolution != newSolution) - updatedDocumentIds.Add(linkedDocument); + var newDocument = newSolution.GetRequiredDocument(data.documentId); + foreach (var linkedDocumentId in linkedDocumentIds) + { + previousSolution = newSolution; + newSolution = newSolution.WithDocumentContentsFrom(linkedDocumentId, newDocument.DocumentState); + if (previousSolution != newSolution) + updatedDocumentIds.Add(linkedDocumentId); + } } return newSolution; }, - data: (@this: this, documentId, arg, checkIsInSolution, getRelatedDocuments, updateSolutionWithText, changeKind, isCodeDocument, updatedDocumentIds), + data: (@this: this, documentId, arg, checkIsInSolution, updateSolutionWithText, changeKind, isCodeDocument, updatedDocumentIds), onAfterUpdate: static (oldSolution, newSolution, data) => { if (data.isCodeDocument) From dca5834682fa4f04cf6659a6d4ee66253cad8e0b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 10:57:57 -0800 Subject: [PATCH 032/274] Add tests --- .../Portable/Workspace/Solution/TextLoader.cs | 4 +- .../CoreTest/SolutionTests/SolutionTests.cs | 186 ++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index 08a07c0e6b202..9ede218d5c571 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -172,6 +172,8 @@ private TextAndVersion CreateFailedText(string message) Diagnostic.Create(WorkspaceDiagnosticDescriptors.ErrorReadingFileContent, location, new[] { display, message })); } + private static readonly ConditionalWeakTable s_textToLoader = new(); + /// /// Creates a new from an already existing source text and version. /// @@ -182,7 +184,7 @@ public static TextLoader From(TextAndVersion textAndVersion) throw new ArgumentNullException(nameof(textAndVersion)); } - return new TextDocumentLoader(textAndVersion); + return s_textToLoader.GetValue(textAndVersion.Text, _ => new TextDocumentLoader(textAndVersion)); } /// diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index fa0a5cbac8c15..524b921c54f2f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -60,6 +60,32 @@ private static Workspace CreateWorkspaceWithProjectAndDocuments() return workspace; } + private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments() + { + var projectId1 = ProjectId.CreateNewId(); + var projectId2 = ProjectId.CreateNewId(); + + var workspace = CreateWorkspace(); + + // note: despite teh additional-doc and analyzer-config doc being at the same path in multiple projects, + // they will still be treated as unique as the workspace only has the concept of linked docs for normal + // docs. + var docSourceText = SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default); + var additionalDocSourceText = SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default); + var editorConfigSourceText = SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default); + Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution + .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp) + .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", docSourceText, filePath: "goo.cs") + .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", additionalDocSourceText, filePath: "add.txt") + .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", editorConfigSourceText, filePath: "/a/b") + .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp) + .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", docSourceText, filePath: "goo.cs") + .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", additionalDocSourceText, filePath: "add.txt") + .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", editorConfigSourceText, filePath: "/a/b"))); + + return workspace; + } + private static IEnumerable EmptyEnumerable() { yield break; @@ -363,6 +389,166 @@ public void WithDocumentText_MultipleDocuments() Assert.Throws(() => solution.WithDocumentText(new[] { documentId }, text, (PreservationMode)(-1))); } + [Fact] + public async Task WithDocumentText_SourceText_LinkedFiles() + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + + solution = solution.WithDocumentText(documentId1, text, PreservationMode.PreserveIdentity); + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + } + + [Fact] + public async Task WithDocumentText_TextAndVersion_LinkedFiles() + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + + solution = solution.WithDocumentText(documentId1, textAndVersion, PreservationMode.PreserveIdentity); + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + } + + [Fact] + public async Task WithDocumentTextLoader_LinkedFiles() + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + var text = TextLoader.From(TextAndVersion.Create(SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1), VersionStamp.Create())); + + solution = solution.WithDocumentTextLoader(documentId1, text, PreservationMode.PreserveIdentity); + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + } + [Fact] public void WithAdditionalDocumentText_SourceText() { From 03731913d817e5e05dd67e07b1e648bfeaa5ad84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 12:01:16 -0800 Subject: [PATCH 033/274] lint --- .../Core/Portable/Workspace/Solution/DocumentState.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index da364cd701001..9bf5c8bb41ef1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -23,8 +23,7 @@ internal partial class DocumentState : TextDocumentState { private static readonly Func s_fullParseLog = (path, mode) => $"{path} : {mode}"; - private static readonly ConditionalWeakTable s_syntaxTreeToIdMap = - new(); + private static readonly ConditionalWeakTable s_syntaxTreeToIdMap = new(); // properties inherited from the containing project: private readonly HostLanguageServices _languageServices; From 5eb86ca53400226731a5c219bfe93877d9a2dad7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 14:02:57 -0800 Subject: [PATCH 034/274] Remove --- src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index 9ede218d5c571..08a07c0e6b202 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -172,8 +172,6 @@ private TextAndVersion CreateFailedText(string message) Diagnostic.Create(WorkspaceDiagnosticDescriptors.ErrorReadingFileContent, location, new[] { display, message })); } - private static readonly ConditionalWeakTable s_textToLoader = new(); - /// /// Creates a new from an already existing source text and version. /// @@ -184,7 +182,7 @@ public static TextLoader From(TextAndVersion textAndVersion) throw new ArgumentNullException(nameof(textAndVersion)); } - return s_textToLoader.GetValue(textAndVersion.Text, _ => new TextDocumentLoader(textAndVersion)); + return new TextDocumentLoader(textAndVersion); } /// From 52e8523bcd5dac2306526c5b519668e4de02b18d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 14:07:43 -0800 Subject: [PATCH 035/274] Simplify tests --- .../CoreTest/SolutionTests/SolutionTests.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 524b921c54f2f..c0a836d000ca5 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -70,18 +70,15 @@ private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments() // note: despite teh additional-doc and analyzer-config doc being at the same path in multiple projects, // they will still be treated as unique as the workspace only has the concept of linked docs for normal // docs. - var docSourceText = SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default); - var additionalDocSourceText = SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default); - var editorConfigSourceText = SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default); Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp) - .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", docSourceText, filePath: "goo.cs") - .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", additionalDocSourceText, filePath: "add.txt") + .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") + .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", editorConfigSourceText, filePath: "/a/b") .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp) - .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", docSourceText, filePath: "goo.cs") - .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", additionalDocSourceText, filePath: "add.txt") - .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", editorConfigSourceText, filePath: "/a/b"))); + .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") + .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") + .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b"))); return workspace; } From 847b3639a71479abe57b80f210e9248c731ae0b8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 14:12:22 -0800 Subject: [PATCH 036/274] Add root tests --- .../CoreTest/SolutionTests/SolutionTests.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index c0a836d000ca5..7b435cab0bc1d 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -74,7 +74,7 @@ private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments() .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp) .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") - .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", editorConfigSourceText, filePath: "/a/b") + .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b") .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp) .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") @@ -402,10 +402,16 @@ public async Task WithDocumentText_SourceText_LinkedFiles() var text2 = await document2.GetTextAsync(); var version1 = await document1.GetTextVersionAsync(); var version2 = await document2.GetTextVersionAsync(); + var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); Assert.Equal(text1.ToString(), text2.ToString()); Assert.Equal(version1, version2); + // We get different red nodes, but they should be backed by the same green nodes. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); solution = solution.WithDocumentText(documentId1, text, PreservationMode.PreserveIdentity); @@ -419,10 +425,16 @@ public async Task WithDocumentText_SourceText_LinkedFiles() text2 = await document2.GetTextAsync(); version1 = await document1.GetTextVersionAsync(); version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); Assert.NotEqual(text1.ToString(), text2.ToString()); Assert.NotEqual(version1, version2); + // We get different red and green nodes. + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. workspace.TryApplyChanges(solution); solution = workspace.CurrentSolution; @@ -434,9 +446,15 @@ public async Task WithDocumentText_SourceText_LinkedFiles() text2 = await document2.GetTextAsync(); version1 = await document1.GetTextVersionAsync(); version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); Assert.Equal(text1.ToString(), text2.ToString()); Assert.Equal(version1, version2); + + // We get different red nodes, but they should be backed by the same green nodes. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); } [Fact] From cf863217f86fb3fb0dd3fdfecfe48f43d73ccd83 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 14:25:58 -0800 Subject: [PATCH 037/274] clone the tree --- .../Workspace/Solution/DocumentState.cs | 32 +++++++++++++++++-- .../Workspace/Solution/SolutionState.cs | 2 +- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9bf5c8bb41ef1..ef0df6aebc717 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -491,8 +491,34 @@ protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSou treeSource: newTreeSource); } - internal DocumentState UpdateTextAndTreeSource(ITextAndVersionSource textSource, ValueSource? treeSource) - { + internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource textSource, ValueSource? treeSource) + { + // if a tree source is provided, then we'll want to use the tree it creates, to share as much memory as + // possible with linked files. However, we can't point at that source directly. If we did, we'd produce + // the *exact* same tree-reference as another file. That would be bad as it would break the invariant that + // each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers to the + // provided source, gets the tree from it, and then wraps its root in a new tree for us. + var newTreeSource = treeSource == null + ? null + : AsyncLazy.Create(async cancellationToken => + { + var originalTreeAndVersion = await treeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); + var originalTree = originalTreeAndVersion.Tree; + + var root = await originalTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var treeFactory = this.LanguageServices.GetRequiredService(); + + Contract.ThrowIfNull(_options); + var newTree = treeFactory.CreateSyntaxTree( + this.Attributes.SyntaxTreeFilePath, + _options, + originalTree.Encoding, + LoadTextOptions.ChecksumAlgorithm, + root); + + return new TreeAndVersion(newTree, originalTreeAndVersion.Version); + }, cacheResult: true); + return new DocumentState( LanguageServices, solutionServices, @@ -501,7 +527,7 @@ internal DocumentState UpdateTextAndTreeSource(ITextAndVersionSource textSource, _options, textSource, LoadTextOptions, - treeSource); + newTreeSource); } internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 8870efb45bb35..2fe88b0428291 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1383,7 +1383,7 @@ public SolutionState WithDocumentContentsFrom(DocumentId documentId, DocumentSta } return UpdateDocumentState( - oldDocument.UpdateTextAndTreeSource(documentState.TextAndVersionSource, documentState.TreeSource), + oldDocument.UpdateTextAndTreeContents(documentState.TextAndVersionSource, documentState.TreeSource), contentChanged: true); } From 8d9b5323ad137e02bfd8bd9273549dd9f1b2c280 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 14:53:57 -0800 Subject: [PATCH 038/274] Only reuse tree when legal --- .../Workspace/Solution/DocumentState.cs | 273 ++++++++++++++++-- 1 file changed, 251 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index ef0df6aebc717..f6526211aabf0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -83,7 +83,9 @@ public DocumentState( public ValueSource? TreeSource => _treeSource; [MemberNotNullWhen(true, nameof(_treeSource))] + [MemberNotNullWhen(true, nameof(TreeSource))] [MemberNotNullWhen(true, nameof(_options))] + [MemberNotNullWhen(true, nameof(ParseOptions))] internal bool SupportsSyntaxTree => _treeSource != null; @@ -491,33 +493,30 @@ protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSou treeSource: newTreeSource); } - internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource textSource, ValueSource? treeSource) + internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTextSource, ValueSource? siblingTreeSource) { + if (!SupportsSyntaxTree) + { + return new DocumentState( + LanguageServices, + solutionServices, + Services, + Attributes, + _options, + siblingTextSource, + LoadTextOptions, + treeSource: null); + } + + Contract.ThrowIfNull(siblingTreeSource); + // if a tree source is provided, then we'll want to use the tree it creates, to share as much memory as // possible with linked files. However, we can't point at that source directly. If we did, we'd produce // the *exact* same tree-reference as another file. That would be bad as it would break the invariant that // each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers to the // provided source, gets the tree from it, and then wraps its root in a new tree for us. - var newTreeSource = treeSource == null - ? null - : AsyncLazy.Create(async cancellationToken => - { - var originalTreeAndVersion = await treeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); - var originalTree = originalTreeAndVersion.Tree; - - var root = await originalTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var treeFactory = this.LanguageServices.GetRequiredService(); - - Contract.ThrowIfNull(_options); - var newTree = treeFactory.CreateSyntaxTree( - this.Attributes.SyntaxTreeFilePath, - _options, - originalTree.Encoding, - LoadTextOptions.ChecksumAlgorithm, - root); - return new TreeAndVersion(newTree, originalTreeAndVersion.Version); - }, cacheResult: true); + var newTreeSource = GetReuseTreeSource(this, siblingTextSource, siblingTreeSource); return new DocumentState( LanguageServices, @@ -525,10 +524,240 @@ internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource textSourc Services, Attributes, _options, - textSource, + siblingTextSource, LoadTextOptions, - newTreeSource); + treeSource: newTreeSource); + } + + // Static so we don't accidentally capture "this" green documentstate node in the async lazy. + + private static AsyncLazy GetReuseTreeSource( + DocumentState documentState, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource) + { + // copy data from this entity, so we don't keep this green node alive. + Contract.ThrowIfFalse(documentState.SupportsSyntaxTree); + + var filePath = documentState.Attributes.SyntaxTreeFilePath; + var languageServices = documentState.LanguageServices; + var loadTextOptions = documentState.LoadTextOptions; + var parseOptions = documentState.ParseOptions; + var textAndVersionSource = documentState.TextAndVersionSource; + var treeSource = documentState.TreeSource; + + return new AsyncLazy( + cancellationToken => TryReuseSiblingTreeAsync(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cacheResult: true); + } + + private static async Task TryReuseSiblingTreeAsync( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions options, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) + { + var siblingTreeAndVersion = await siblingTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); + var siblingTree = siblingTreeAndVersion.Tree; + + var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + + if (CanReuseSiblingRoot(parseOptions, siblingTree.Options, siblingRoot)) + { + var treeFactory = languageServices.GetRequiredService(); + + var newTree = treeFactory.CreateSyntaxTree( + filePath, + parseOptions, + siblingTree.Encoding, + options.ChecksumAlgorithm, + siblingRoot); + + return new TreeAndVersion(newTree, siblingTreeAndVersion.Version); + } + else + { + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, options, cancellationToken).ConfigureAwait(false); + } + } + + private static TreeAndVersion TryReuseSiblingTree( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions options, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) + { + var siblingTreeAndVersion = siblingTreeSource.GetValue(cancellationToken); + var siblingTree = siblingTreeAndVersion.Tree; + + var siblingRoot = siblingTree.GetRoot(cancellationToken); + + if (CanReuseSiblingRoot(parseOptions, siblingTree.Options, siblingRoot)) + { + var treeFactory = languageServices.GetRequiredService(); + + var newTree = treeFactory.CreateSyntaxTree( + filePath, + parseOptions, + siblingTree.Encoding, + options.ChecksumAlgorithm, + siblingRoot); + + return new TreeAndVersion(newTree, siblingTreeAndVersion.Version); + } + else + { + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return IncrementallyParseTree(treeSource, siblingTextSource, options, cancellationToken); + } + } + + private static bool CanReuseSiblingRoot( + ParseOptions parseOptions, + ParseOptions siblingParseOptions, + SyntaxNode siblingRoot) + { + var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; + var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; + + // If both documents have the same preprocessor directives defined, then they'll always produce the + // same trees. So we can trivially reuse the tree from one for the other. + if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) + return true; + + // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. + if (!siblingRoot.ContainsDirectives) + return true; + +#if false + // It's ok to contain directives like #nullable, or #region. They don't affect parsing. + if (!siblingRoot.ContainsConditionalDirectives()) + return true; +#endif + + // If the tree contains a #if directive, and the pp-symbol-names are different, then the files + // absolutely may be parsed differently, and so they should not be shared. + return false; + } + +#if false + private static void TryInitializeTreeSourceFromRelatedDocument( + SolutionState solution, DocumentState document, ValueSource treeSource) + { + s_tryShareSyntaxTreeCount++; + if (document.FilePath == null) + return; + + var relatedDocumentIds = solution.GetDocumentIdsWithFilePath(document.FilePath); + foreach (var docId in relatedDocumentIds) + { + // ignore this document when looking at siblings. We can't initialize ourself with ourself. + if (docId == document.Id) + continue; + + var otherProject = solution.GetProjectState(docId.ProjectId); + if (otherProject == null) + continue; + + var otherDocument = otherProject.DocumentStates.GetState(docId); + if (otherDocument == null) + continue; + + // Now, see if the linked doc actually has its tree readily available. + if (otherDocument._treeSource == null || !otherDocument._treeSource.TryGetValue(out var otherTreeAndVersion)) + continue; + + // And see if its root is there as well. Note: we only need the root to determine if the tree contains + // pp directives. If this could be stored on the tree itself, that would remove the need for having to have + // the actual root available. + + var otherTree = otherTreeAndVersion.Tree; + if (!otherTree.TryGetRoot(out var otherRoot)) + continue; + + // If the processor directives are not compatible between the other document and this one, we definitely + // can't reuse the tree. + if (!HasCompatiblePreprocessorDirectives(document, otherDocument, otherRoot)) + continue; + + // Note: even if the pp directives are compatible, it may *technically* not be safe to reuse the tree. + // For example, C# parses some things differently across language version. like `record Goo() { }` is a + // method prior to 9.0, and a record from 9.0 onwards. *However*, code that actually contains + // constructs that would be parsed differently is considered pathological by us. e.g. we do not believe + // it is a realistic scenario that users would genuinely write such a construct and need it to have + // different syntactic meaning like this across versions. So we allow for this reuse even though the + // above it a possibility, since we do not consider it important or relevant to support. + +#if false + + // Want to make sure that these two docs are pointing at the same text. Technically it's possible + // (though unpleasant) to have linked docs pointing to different text. This is because our in-memory + // model doesn't enforce any invariants here. So it's trivially possible to take two linked documents + // and do things like `doc1.WithSomeText(text1)` and `doc2.WithSomeText(text2)` and now have them be + // inconsistent in that regard. They will eventually become consistent, but there can be periods when + // they are not. In this case, we don't want a forked doc to grab a tree from another doc that may be + // looking at some different text. So we conservatively only allow for the case where we are certain + // things are ok. + // + // https://github.com/dotnet/roslyn/issues/65797 tracks a cleaner model where the workspace would + // enforce that all linked docs would share the same source and we would not need this conservative + // check here. + var textsAreEquivalent = (document.TextAndVersionSource, otherDocument.TextAndVersionSource) switch + { + // For constant sources (like what we have that wraps open documents, or explicitly forked docs) we + // can reuse if the SourceTexts are clearly identical. + (ConstantTextAndVersionSource constant1, ConstantTextAndVersionSource constant2) => constant1.Value.Text == constant2.Value.Text, + // For loadable sources (like what we have for docs loaded from disk) we know they should have the + // same text since they correspond to the same final physical entity on the machine. Note: this is + // not strictly true as technically it's possible to race here with event notifications where a file + // changes, one doc sees it and updates its text loader, and the other linked doc hasn't done this + // yet. However, this race has always existed and we accept that it could cause inconsistencies + // anyways. + (LoadableTextAndVersionSource loadable1, LoadableTextAndVersionSource loadable2) => loadable1.Loader.FilePath == loadable2.Loader.FilePath, + + // Anything else, and we presume we can't share this root. + _ => false, + }; + + if (!textsAreEquivalent) + { + Console.WriteLine($"Texts are not equivalent: {document.TextAndVersionSource.GetType().Name}-{otherDocument.TextAndVersionSource.GetType().Name}"); + continue; + } + + // Console.WriteLine("Texts are equivalent"); + +#endif + + var factory = document.LanguageServices.GetRequiredService(); + var newTree = factory.CreateSyntaxTree( + document.FilePath, otherTree.Options, otherTree.Encoding, document.LoadTextOptions.ChecksumAlgorithm, otherRoot); + + // Ok, now try to set out value-source to this newly created tree. This may fail if some other thread + // beat us here. That's ok, our caller (GetSyntaxTreeAsync) will read the source itself. So we'll only + // ever have one source of truth here. + treeSource.TrySetValue(new TreeAndVersion(newTree, otherTreeAndVersion.Version)); + s_successfullySharedSyntaxTreeCount++; + return; + } + + return; + + static bool HasCompatiblePreprocessorDirectives(DocumentState document1, DocumentState document2, SyntaxNode root) + { + } } +#endif internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) { From 6e33cfb3f384f92a262fcf5f8c06efb87022fe4e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:00:50 -0800 Subject: [PATCH 039/274] Move to theory --- .../CoreTest/SolutionTests/SolutionTests.cs | 130 +++--------------- 1 file changed, 19 insertions(+), 111 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 7b435cab0bc1d..185755dd5e7de 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -386,8 +386,17 @@ public void WithDocumentText_MultipleDocuments() Assert.Throws(() => solution.WithDocumentText(new[] { documentId }, text, (PreservationMode)(-1))); } - [Fact] - public async Task WithDocumentText_SourceText_LinkedFiles() + public enum TextUpdateType + { + SourceText, + TextLoader, + TextAndVersion, + } + + [Theory, CombinatorialData] + public async Task WithDocumentText_SourceText_LinkedFiles( + PreservationMode mode, + TextUpdateType updateType) { using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); var solution = workspace.CurrentSolution; @@ -413,8 +422,14 @@ public async Task WithDocumentText_SourceText_LinkedFiles() Assert.True(root1.IsIncrementallyIdenticalTo(root2)); var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); - - solution = solution.WithDocumentText(documentId1, text, PreservationMode.PreserveIdentity); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; // because we only forked one doc, the text/versions should be different in this interim solution. @@ -457,113 +472,6 @@ public async Task WithDocumentText_SourceText_LinkedFiles() Assert.True(root1.IsIncrementallyIdenticalTo(root2)); } - [Fact] - public async Task WithDocumentText_TextAndVersion_LinkedFiles() - { - using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); - var solution = workspace.CurrentSolution; - - var documentId1 = solution.Projects.First().DocumentIds.Single(); - var documentId2 = solution.Projects.Last().DocumentIds.Single(); - - var document1 = solution.GetRequiredDocument(documentId1); - var document2 = solution.GetRequiredDocument(documentId2); - - var text1 = await document1.GetTextAsync(); - var text2 = await document2.GetTextAsync(); - var version1 = await document1.GetTextVersionAsync(); - var version2 = await document2.GetTextVersionAsync(); - - Assert.Equal(text1.ToString(), text2.ToString()); - Assert.Equal(version1, version2); - - var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); - var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - - solution = solution.WithDocumentText(documentId1, textAndVersion, PreservationMode.PreserveIdentity); - - // because we only forked one doc, the text/versions should be different in this interim solution. - - document1 = solution.GetRequiredDocument(documentId1); - document2 = solution.GetRequiredDocument(documentId2); - - text1 = await document1.GetTextAsync(); - text2 = await document2.GetTextAsync(); - version1 = await document1.GetTextVersionAsync(); - version2 = await document2.GetTextVersionAsync(); - - Assert.NotEqual(text1.ToString(), text2.ToString()); - Assert.NotEqual(version1, version2); - - // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. - workspace.TryApplyChanges(solution); - solution = workspace.CurrentSolution; - - document1 = solution.GetRequiredDocument(documentId1); - document2 = solution.GetRequiredDocument(documentId2); - - text1 = await document1.GetTextAsync(); - text2 = await document2.GetTextAsync(); - version1 = await document1.GetTextVersionAsync(); - version2 = await document2.GetTextVersionAsync(); - - Assert.Equal(text1.ToString(), text2.ToString()); - Assert.Equal(version1, version2); - } - - [Fact] - public async Task WithDocumentTextLoader_LinkedFiles() - { - using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); - var solution = workspace.CurrentSolution; - - var documentId1 = solution.Projects.First().DocumentIds.Single(); - var documentId2 = solution.Projects.Last().DocumentIds.Single(); - - var document1 = solution.GetRequiredDocument(documentId1); - var document2 = solution.GetRequiredDocument(documentId2); - - var text1 = await document1.GetTextAsync(); - var text2 = await document2.GetTextAsync(); - var version1 = await document1.GetTextVersionAsync(); - var version2 = await document2.GetTextVersionAsync(); - - Assert.Equal(text1.ToString(), text2.ToString()); - Assert.Equal(version1, version2); - - var text = TextLoader.From(TextAndVersion.Create(SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1), VersionStamp.Create())); - - solution = solution.WithDocumentTextLoader(documentId1, text, PreservationMode.PreserveIdentity); - - // because we only forked one doc, the text/versions should be different in this interim solution. - - document1 = solution.GetRequiredDocument(documentId1); - document2 = solution.GetRequiredDocument(documentId2); - - text1 = await document1.GetTextAsync(); - text2 = await document2.GetTextAsync(); - version1 = await document1.GetTextVersionAsync(); - version2 = await document2.GetTextVersionAsync(); - - Assert.NotEqual(text1.ToString(), text2.ToString()); - Assert.NotEqual(version1, version2); - - // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. - workspace.TryApplyChanges(solution); - solution = workspace.CurrentSolution; - - document1 = solution.GetRequiredDocument(documentId1); - document2 = solution.GetRequiredDocument(documentId2); - - text1 = await document1.GetTextAsync(); - text2 = await document2.GetTextAsync(); - version1 = await document1.GetTextVersionAsync(); - version2 = await document2.GetTextVersionAsync(); - - Assert.Equal(text1.ToString(), text2.ToString()); - Assert.Equal(version1, version2); - } - [Fact] public void WithAdditionalDocumentText_SourceText() { From 590b89e0471d206e35e430e75d219bb100bce76a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:06:16 -0800 Subject: [PATCH 040/274] Add tests --- .../CoreTest/SolutionTests/SolutionTests.cs | 174 +++++++++++++++++- 1 file changed, 169 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 185755dd5e7de..df08774cae909 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -60,7 +60,7 @@ private static Workspace CreateWorkspaceWithProjectAndDocuments() return workspace; } - private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments() + private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments(string docContents) { var projectId1 = ProjectId.CreateNewId(); var projectId2 = ProjectId.CreateNewId(); @@ -72,11 +72,11 @@ private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments() // docs. Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp) - .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") + .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b") .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp) - .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") + .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b"))); @@ -394,11 +394,175 @@ public enum TextUpdateType } [Theory, CombinatorialData] - public async Task WithDocumentText_SourceText_LinkedFiles( + public async Task WithDocumentText_LinkedFiles( PreservationMode mode, TextUpdateType updateType) { - using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(); + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments("public class Goo { }"); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We get different red nodes, but they should be backed by the same green nodes. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // We get different red and green nodes. + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We get different red nodes, but they should be backed by the same green nodes. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + } + + [Theory, CombinatorialData] + public async Task WithDocumentText_LinkedFiles_PPConditionalDirective( + PreservationMode mode, + TextUpdateType updateType) + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(""" + #if NETSTANDARD + public class Goo { } + """); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We can never reuse trees with conditional directives. + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // We get different red and green nodes entirely + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We can never reuse trees with conditional directives. + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + } + + [Theory, CombinatorialData] + public async Task WithDocumentText_LinkedFiles_NonConditionalDirective( + PreservationMode mode, + TextUpdateType updateType) + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(""" + #nullable enable // should not impact being able to reuse. + public class Goo { } + """); var solution = workspace.CurrentSolution; var documentId1 = solution.Projects.First().DocumentIds.Single(); From df2b02110fef6cbde63efc7ac9d30c4486e6e9ee Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:15:17 -0800 Subject: [PATCH 041/274] Review feedback --- src/Compilers/CSharp/Portable/CSharpExtensions.cs | 3 +++ .../CSharp/Portable/PublicAPI.Unshipped.txt | 1 + src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 1 + src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 13 +++++-------- .../VisualBasic/Portable/PublicAPI.Unshipped.txt | 1 + .../VisualBasic/Portable/VisualBasicExtensions.vb | 5 +++++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpExtensions.cs b/src/Compilers/CSharp/Portable/CSharpExtensions.cs index d308a54dee1fd..c4ba7c3bc22a2 100644 --- a/src/Compilers/CSharp/Portable/CSharpExtensions.cs +++ b/src/Compilers/CSharp/Portable/CSharpExtensions.cs @@ -60,6 +60,9 @@ public static bool IsKind(this SyntaxNodeOrToken nodeOrToken, SyntaxKind kind) return nodeOrToken.RawKind == (int)kind; } + public static bool ContainsDirective(this SyntaxNode node, SyntaxKind kind) + => node.ContainsDirective((int)kind); + internal static SyntaxKind ContextualKind(this SyntaxToken token) { return (object)token.Language == (object)LanguageNames.CSharp ? (SyntaxKind)token.RawContextualKind : SyntaxKind.None; diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 72a5aa6c898a5..96ed633ceeb31 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -17,5 +17,6 @@ override Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax.Accept(Microsoft. override Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> TResult? static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ScopedType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ScopedType(Microsoft.CodeAnalysis.SyntaxToken scopedKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax! +static Microsoft.CodeAnalysis.CSharpExtensions.ContainsDirective(this Microsoft.CodeAnalysis.SyntaxNode! node, Microsoft.CodeAnalysis.CSharp.SyntaxKind kind) -> bool virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitScopedType(Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax! node) -> void virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitScopedType(Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax! node) -> TResult? diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index c9d3372ccfdbf..7be29a3050bed 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -23,6 +23,7 @@ Microsoft.CodeAnalysis.ScopedKind.ScopedRef = 1 -> Microsoft.CodeAnalysis.Scoped Microsoft.CodeAnalysis.ScopedKind.ScopedValue = 2 -> Microsoft.CodeAnalysis.ScopedKind Microsoft.CodeAnalysis.SymbolDisplayLocalOptions.IncludeModifiers = 4 -> Microsoft.CodeAnalysis.SymbolDisplayLocalOptions Microsoft.CodeAnalysis.SymbolDisplayParameterOptions.IncludeModifiers = 2 -> Microsoft.CodeAnalysis.SymbolDisplayParameterOptions +Microsoft.CodeAnalysis.SyntaxNode.ContainsDirective(int rawKind) -> bool override sealed Microsoft.CodeAnalysis.CompilationOptions.GetHashCode() -> int static Microsoft.CodeAnalysis.Location.Create(string! filePath, Microsoft.CodeAnalysis.Text.TextSpan textSpan, Microsoft.CodeAnalysis.Text.LinePositionSpan lineSpan, string! mappedFilePath, Microsoft.CodeAnalysis.Text.LinePositionSpan mappedLineSpan) -> Microsoft.CodeAnalysis.Location! static Microsoft.CodeAnalysis.ModuleMetadata.CreateFromMetadata(System.IntPtr metadata, int size, System.Action! onDispose) -> Microsoft.CodeAnalysis.ModuleMetadata! diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 91ea24e5e7b48..9ecbd1b582d05 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -448,14 +448,11 @@ public bool ContainsDiagnostics public bool ContainsDirectives => this.Green.ContainsDirectives; /// - /// Returns true if this node contains any conditional directives (#if in C# and #If in Visual - /// Basic) within it. This is similar to except that where that returns true - /// if there are any preprocessor directives whatsoever, this is only true for for if directives. + /// Returns true if this node contains any directives (e.g. #if, #nullable, etc.) within it with the same + /// as . /// - public bool ContainsConditionalDirectives() + public bool ContainsDirective(int rawKind) { - var conditionalDirectiveKind = this.ConditionalDirectiveKind; - var stack = PooledObjects.ArrayBuilder.GetInstance(); stack.Push(this.Green); @@ -484,11 +481,11 @@ public bool ContainsConditionalDirectives() { for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) { - if (leadingTriviaNode.GetSlot(i)?.RawKind == conditionalDirectiveKind) + if (leadingTriviaNode.GetSlot(i)?.RawKind == rawKind) return true; } } - else if (leadingTriviaNode.RawKind == conditionalDirectiveKind) + else if (leadingTriviaNode.RawKind == rawKind) { return true; } diff --git a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt index 9a31aa7a58e6f..92c2ec8a95542 100644 --- a/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/VisualBasic/Portable/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ *REMOVED*Overrides Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions.GetHashCode() -> Integer +Microsoft.CodeAnalysis.VisualBasicExtensions.ContainsDirective(node As Microsoft.CodeAnalysis.SyntaxNode, kind As Microsoft.CodeAnalysis.VisualBasic.SyntaxKind) -> Boolean diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb index fbef84802e0e4..66b3ff6d3c7a1 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb @@ -63,6 +63,11 @@ Namespace Microsoft.CodeAnalysis Return nodeOrToken.RawKind = kind End Function + + Public Function ContainsDirective(node As SyntaxNode, kind As SyntaxKind) As Boolean + Return node.ContainsDirective(CType(kind, Integer)) + End Function + ''' ''' Returns the index of the first node of a specified kind in the node list. ''' From be0e5ef742e9c1b8579ee2eb147fc06b8457a8c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:42:03 -0800 Subject: [PATCH 042/274] Add tests --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 8f918836c8127..b6af04a5688df 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -342,6 +343,98 @@ public void TestGetAllDirectivesUsingDescendantNodes() } } + [Fact] + public void TestContainsDirective() + { + // Empty compilation unit shouldn't have any directives in it. + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + Assert.False(SyntaxFactory.ParseCompilationUnit("").ContainsDirective(kind)); + + // basic file shouldn't have any directives in it. + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); + + TestContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); + TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); + TestContainsHelper1("#if true\r\n#else", SyntaxKind.ElseDirectiveTrivia); + TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); + TestContainsHelper1("#if true\r\n#endif", SyntaxKind.EndIfDirectiveTrivia); + TestContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); + TestContainsHelper1("#region\r\n#endregion", SyntaxKind.EndRegionDirectiveTrivia); + TestContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); + TestContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); + TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); + TestContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); + TestContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); + TestContainsHelper1("#!command", SyntaxKind.ShebangDirectiveTrivia, SourceCodeKind.Script); + TestContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); + TestContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); + + return; + + void TestContainsHelper2(string directive, SyntaxKind directiveKind, CompilationUnitSyntax compilationUnit) + { + Assert.True(compilationUnit.ContainsDirective(directiveKind)); + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + { + if (kind != directiveKind) + Assert.False(compilationUnit.ContainsDirective(kind)); + } + } + + void TestContainsHelper1(string directive, SyntaxKind directiveKind, SourceCodeKind kind = SourceCodeKind.Regular) + { + var options = kind == SourceCodeKind.Regular ? TestOptions.Regular : TestOptions.Script; + + // directive on its own. + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive, options: options)); + + // Two of the same directive back to back. + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + {{directive}} + {{directive}} + """, options: options)); + + if (kind == SourceCodeKind.Regular) + { + // Directive inside a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + namespace N + { +{{directive}} + } + """)); + + // Multiple Directive inside a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + namespace N + { + {{directive}} + {{directive}} + } + """)); + + // Directives on different elements in a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + namespace N + { + {{directive}} + class C + { + } + {{directive}} + class D + { + } + } + """)); + } + } + } + [Fact] public void TestGetAllAnnotatedNodesUsingDescendantNodes() { From 6709066faa87a46a2e6b8cec5ce4f26a155e9c1f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:42:36 -0800 Subject: [PATCH 043/274] Update --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index b6af04a5688df..6a2c4409c5984 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -375,16 +375,6 @@ public void TestContainsDirective() return; - void TestContainsHelper2(string directive, SyntaxKind directiveKind, CompilationUnitSyntax compilationUnit) - { - Assert.True(compilationUnit.ContainsDirective(directiveKind)); - for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) - { - if (kind != directiveKind) - Assert.False(compilationUnit.ContainsDirective(kind)); - } - } - void TestContainsHelper1(string directive, SyntaxKind directiveKind, SourceCodeKind kind = SourceCodeKind.Regular) { var options = kind == SourceCodeKind.Regular ? TestOptions.Regular : TestOptions.Script; @@ -433,6 +423,17 @@ class D """)); } } + + void TestContainsHelper2(string directive, SyntaxKind directiveKind, CompilationUnitSyntax compilationUnit) + { + Assert.True(compilationUnit.ContainsDirectives); + Assert.True(compilationUnit.ContainsDirective(directiveKind)); + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + { + if (kind != directiveKind) + Assert.False(compilationUnit.ContainsDirective(kind)); + } + } } [Fact] From 58829c49ec6707b71aedf6e98b0dd43a5aa03f26 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:43:29 -0800 Subject: [PATCH 044/274] REvert --- src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs index e8c4290d3f88f..f1719695e36d0 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CSharpSyntaxNode.cs @@ -154,9 +154,10 @@ public SyntaxKind Kind() /// /// The language name that this node is syntax of. /// - public override string Language => LanguageNames.CSharp; - - protected override int ConditionalDirectiveKind => (int)SyntaxKind.IfDirectiveTrivia; + public override string Language + { + get { return LanguageNames.CSharp; } + } /// /// The list of trivia that appears before this node in the source code. From 7985f94b390e6533720214446f41eca6c6a8c0dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:43:45 -0800 Subject: [PATCH 045/274] REvert --- src/Compilers/Core/Portable/Syntax/SyntaxList.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxList.cs b/src/Compilers/Core/Portable/Syntax/SyntaxList.cs index 3bceb17f6aba0..2073ef9448ed8 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxList.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxList.cs @@ -15,8 +15,13 @@ internal SyntaxList(InternalSyntax.SyntaxList green, SyntaxNode? parent, int pos { } - public override string Language => throw ExceptionUtilities.Unreachable(); - protected override int ConditionalDirectiveKind => throw ExceptionUtilities.Unreachable(); + public override string Language + { + get + { + throw ExceptionUtilities.Unreachable(); + } + } // https://github.com/dotnet/roslyn/issues/40733 protected override SyntaxTree SyntaxTreeCore => this.Parent!.SyntaxTree; From dd2bf039130fc8e11f5dfc650078e2fd4efea191 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:44:16 -0800 Subject: [PATCH 046/274] Remove --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 9ecbd1b582d05..57981f79d70ed 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -64,12 +64,6 @@ private string GetDebuggerDisplay() /// public abstract string Language { get; } - /// - /// The raw SyntaxKind for this language corresponding to a conditional directive (#if in C# and - /// #If in Visual Basic). - /// - protected abstract int ConditionalDirectiveKind { get; } - internal GreenNode Green { get; } internal int Position { get; } From 7431ed4b2c2253ee2dc542b12ea9838d4633382d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 15:44:47 -0800 Subject: [PATCH 047/274] Remove --- .../VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb index 01aa337a763d9..cddea06481803 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxNode.vb @@ -110,12 +110,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property - Protected Overrides ReadOnly Property ConditionalDirectiveKind As Integer - Get - Return SyntaxKind.IfDirectiveTrivia - End Get - End Property - ''' ''' The parent of this node. ''' From a10b1a7b9472381aadfefc59869cf44d0ca1b38e Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Thu, 8 Dec 2022 11:41:35 -0800 Subject: [PATCH 048/274] Relayer our project system Our existing VisualStudioWorkspace implementation had several interesting bits that weren't actually specific to Visual Studio for Windows. Specifically, it had support for tracking the output files of projects and converting metadata references to project references. It also had support for tracking when we need to add and remove file watches based on various events, as well as batching updates. None of this depends on any specific VS APIs, so this refactors this to allow for better code reuse. The transformations are: 1. VisualStudioProject was renamed to ProjectSystemProject and moved down. Yes, I hate that name too. 2. VisualStudioProjectFactory got some of it's contents moved to a new ProjectSystemProjectFactory; the VS specific bits now call into this later type. 3. All of the stuff in VisualStudioWorkspaceImpl that dealt with applying the edits to a workspace (batching, project-to-project reference conversion) also moved to ProjectSystemProjectFactory. The ordering of the members was kept in order to make tracking the edit in Git a bit better, although it does need a follow up to fix some of the oddly ordered bits. Things that need to happen in follow-up commits: 1. Moving a few more types, like the OptionsProcessor. 2. Moving the tests around -- this still has tests for now reusable code still in the VS layer. 3. Removal of the IProjectSystemDiagnosticSource. It's not clear to me why we can't have the implementation of that also moved down but that'll potential require some additional refactoring. 4. Cleaning up of some of the threading gates in ProjectSystemProjectFactory. We recently added better APIs for making solution changes in the workspace while being under the existing locks; we can now hopefully migrate to that. --- .../CSharpProjectShim.ICSInputSet.cs | 4 +- .../CSharpProjectShim.ICSharpProjectSite.cs | 14 +- ...harpProjectShim.ICSharpVenusProjectSite.cs | 14 +- .../CSharpProjectShim.OptionsProcessor.cs | 11 +- .../ProjectSystemShim/CSharpProjectShim.cs | 4 +- .../CSharpCompilerOptionsTests.cs | 4 +- .../VSTypeScriptVisualStudioProjectWrapper.cs | 5 +- ...geService`2.IVsContainedLanguageFactory.cs | 3 +- .../AbstractLanguageService`2.cs | 3 +- ...osoft.VisualStudio.LanguageServices.csproj | 1 - .../Core/Def/ProjectSystem/AbstractProject.cs | 3 +- .../Legacy/AbstractLegacyProject.cs | 47 +- ...ctLegacyProject_IAnalyzerConfigFileHost.cs | 4 +- .../AbstractLegacyProject_IAnalyzerHost.cs | 8 +- .../AbstractLegacyProject_IProjectSiteEx.cs | 5 +- ...bstractLegacyProject_IVsHierarchyEvents.cs | 4 +- .../Legacy/SolutionEventsBatchScopeCreator.cs | 7 +- .../VisualStudioProjectCreationInfo.cs | 8 +- .../VisualStudioProjectFactory.cs | 73 +- .../VisualStudioProjectOptionsProcessor.cs | 5 +- ...sualStudioWorkspaceImpl.OpenFileTracker.cs | 21 +- ...spaceImpl.SolutionAnalyzerSetterService.cs | 2 +- .../VisualStudioWorkspaceImpl.cs | 583 +--------------- .../TaskList/HostDiagnosticUpdateSource.cs | 5 + .../Core/Def/Venus/ContainedDocument.cs | 5 +- .../Core/Def/Venus/ContainedLanguage.cs | 5 +- .../CPSProject_IProjectCodeModelProvider.cs | 2 +- .../CPSProject_IWorkspaceProjectContext.cs | 121 ++-- .../VisualBasicCompilerOptionsTests.vb | 10 +- .../VisualStudioAnalyzerTests.vb | 5 +- .../VisualBasicLanguageService.vb | 3 +- .../VisualBasicCodeModelInstanceFactory.vb | 2 +- .../VisualBasicProject.OptionsProcessor.vb | 3 +- .../ProjectSystemShim/VisualBasicProject.vb | 34 +- .../Venus/VisualBasicContainedLanguage.vb | 5 +- .../Impl/Implementation/XamlProjectService.cs | 3 +- .../Microsoft.CodeAnalysis.Workspaces.csproj | 3 + .../IProjectSystemDiagnosticSource.cs | 1 + ...stemProject.BatchingDocumentCollection.cs} | 42 +- .../ProjectSystem/ProjectSystemProject.cs} | 139 ++-- .../ProjectSystemProjectCreationInfo.cs | 19 + .../ProjectSystemProjectFactory.cs | 657 ++++++++++++++++++ .../ProjectSystemProjectHostInfo.cs | 17 + .../Workspace}/ProjectSystem/Readme.md | 22 +- .../SolutionChangeAccumulator.cs | 4 +- .../ProjectSystem/VisualStudioAnalyzer.cs | 12 +- .../Portable/Workspace/Workspace_Editor.cs | 2 +- 47 files changed, 1042 insertions(+), 912 deletions(-) rename src/{VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs => Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs} (92%) rename src/{VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs => Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs} (89%) create mode 100644 src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectCreationInfo.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs rename src/{VisualStudio/Core/Def => Workspaces/Core/Portable/Workspace}/ProjectSystem/Readme.md (61%) rename src/{VisualStudio/Core/Def => Workspaces/Core/Portable/Workspace}/ProjectSystem/SolutionChangeAccumulator.cs (97%) rename src/{VisualStudio/Core/Def => Workspaces/Core/Portable/Workspace}/ProjectSystem/VisualStudioAnalyzer.cs (86%) diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs index 147a876e9f44a..0b7b203e7f767 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSInputSet.cs @@ -45,12 +45,12 @@ public void SetOutputFileName(string filename) // Some projects like web projects give us just a filename; those aren't really useful (they're just filler) so we'll ignore them for purposes of tracking the path if (PathUtilities.IsAbsolute(filename)) { - VisualStudioProject.CompilationOutputAssemblyFilePath = filename; + ProjectSystemProject.CompilationOutputAssemblyFilePath = filename; } if (filename != null) { - VisualStudioProject.AssemblyName = Path.GetFileNameWithoutExtension(filename); + ProjectSystemProject.AssemblyName = Path.GetFileNameWithoutExtension(filename); } RefreshBinOutputPath(); diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs index 10daf994f1fd6..b87a4b0820fb1 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpProjectSite.cs @@ -72,7 +72,7 @@ public int OnImportAddedEx(string filename, string project, CompilerOptions opti } var embedInteropTypes = optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA; - VisualStudioProject.AddMetadataReference(filename, new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes)); + ProjectSystemProject.AddMetadataReference(filename, new MetadataReferenceProperties(embedInteropTypes: embedInteropTypes)); return VSConstants.S_OK; } @@ -81,7 +81,7 @@ public void OnImportRemoved(string filename, string project) { filename = FileUtilities.NormalizeAbsolutePath(filename); - VisualStudioProject.RemoveMetadataReference(filename, VisualStudioProject.GetPropertiesForMetadataReference(filename).Single()); + ProjectSystemProject.RemoveMetadataReference(filename, properties: ProjectSystemProject.GetPropertiesForMetadataReference(filename).Single()); } public void OnOutputFileChanged(string filename) @@ -120,7 +120,7 @@ public void OnModuleRemoved(string filename) public int GetValidStartupClasses(IntPtr[] classNames, ref int count) { - var project = Workspace.CurrentSolution.GetRequiredProject(VisualStudioProject.Id); + var project = Workspace.CurrentSolution.GetRequiredProject(ProjectSystemProject.Id); var compilation = project.GetRequiredCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None); var entryPoints = EntryPointFinder.FindEntryPoints(compilation.SourceModule.GlobalNamespace); @@ -164,11 +164,11 @@ public int GetValidStartupClasses(IntPtr[] classNames, ref int count) public void OnAliasesChanged(string file, string project, int previousAliasesCount, string[] previousAliases, int currentAliasesCount, string[] currentAliases) { - using (VisualStudioProject.CreateBatchScope()) + using (ProjectSystemProject.CreateBatchScope()) { - var existingProperties = VisualStudioProject.GetPropertiesForMetadataReference(file).Single(); - VisualStudioProject.RemoveMetadataReference(file, existingProperties); - VisualStudioProject.AddMetadataReference(file, existingProperties.WithAliases(currentAliases)); + var existingProperties = ProjectSystemProject.GetPropertiesForMetadataReference(file).Single(); + ProjectSystemProject.RemoveMetadataReference(file, existingProperties); + ProjectSystemProject.AddMetadataReference(file, existingProperties.WithAliases(currentAliases)); } } } diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs index 84e630af96813..60e60d2839476 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.ICSharpVenusProjectSite.cs @@ -21,7 +21,7 @@ public void RemoveReferenceToCodeDirectory(string assemblyFileName, ICSharpProje { var projectSite = GetProjectSite(project); - var projectReferencesToRemove = VisualStudioProject.GetProjectReferences().Where(p => p.ProjectId == projectSite.VisualStudioProject.Id).ToList(); + var projectReferencesToRemove = ProjectSystemProject.GetProjectReferences().Where(p => p.ProjectId == projectSite.ProjectSystemProject.Id).ToList(); if (projectReferencesToRemove.Count == 0) { @@ -30,7 +30,7 @@ public void RemoveReferenceToCodeDirectory(string assemblyFileName, ICSharpProje foreach (var projectReferenceToRemove in projectReferencesToRemove) { - VisualStudioProject.RemoveProjectReference(new ProjectReference(projectSite.VisualStudioProject.Id)); + ProjectSystemProject.RemoveProjectReference(new ProjectReference(projectSite.ProjectSystemProject.Id)); } } @@ -41,12 +41,12 @@ public void OnCodeDirectoryAliasesChanged(ICSharpProjectRoot project, int previo { var projectSite = GetProjectSite(project); - using (VisualStudioProject.CreateBatchScope()) + using (ProjectSystemProject.CreateBatchScope()) { - var existingProjectReference = VisualStudioProject.GetProjectReferences().Single(p => p.ProjectId == projectSite.VisualStudioProject.Id); + var existingProjectReference = ProjectSystemProject.GetProjectReferences().Single(p => p.ProjectId == projectSite.ProjectSystemProject.Id); - VisualStudioProject.RemoveProjectReference(existingProjectReference); - VisualStudioProject.AddProjectReference(new ProjectReference(existingProjectReference.ProjectId, ImmutableArray.Create(currentAliases), existingProjectReference.EmbedInteropTypes)); + ProjectSystemProject.RemoveProjectReference(existingProjectReference); + ProjectSystemProject.AddProjectReference(new ProjectReference(existingProjectReference.ProjectId, ImmutableArray.Create(currentAliases), existingProjectReference.EmbedInteropTypes)); } } @@ -54,7 +54,7 @@ public void AddReferenceToCodeDirectoryEx(string assemblyFileName, ICSharpProjec { var projectSite = GetProjectSite(projectRoot); - VisualStudioProject.AddProjectReference(new ProjectReference(projectSite.VisualStudioProject.Id, embedInteropTypes: optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA)); + ProjectSystemProject.AddProjectReference(new ProjectReference(projectSite.ProjectSystemProject.Id, embedInteropTypes: optionID == CompilerOptions.OPTID_IMPORTSUSINGNOPIA)); } /// diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs index 133dfaf01e672..84704e93e1593 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Roslyn.Utilities; @@ -19,16 +20,16 @@ internal partial class CSharpProjectShim { private class OptionsProcessor : VisualStudioProjectOptionsProcessor { - private readonly VisualStudioProject _visualStudioProject; + private readonly ProjectSystemProject _projectSystemProject; private readonly object[] _options = new object[(int)CompilerOptions.LARGEST_OPTION_ID]; private string? _mainTypeName; private OutputKind _outputKind; - public OptionsProcessor(VisualStudioProject visualStudioProject, SolutionServices workspaceServices) - : base(visualStudioProject, workspaceServices) + public OptionsProcessor(ProjectSystemProject projectSystemProject, SolutionServices workspaceServices) + : base(projectSystemProject, workspaceServices) { - _visualStudioProject = visualStudioProject; + _projectSystemProject = projectSystemProject; } public object this[CompilerOptions compilerOption] @@ -187,7 +188,7 @@ private bool GetBooleanOption(CompilerOptions optionID) return null; } - var directory = Path.GetDirectoryName(_visualStudioProject.FilePath); + var directory = Path.GetDirectoryName(_projectSystemProject.FilePath); if (!string.IsNullOrEmpty(directory)) { diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs index f8c001cb6a312..f83b0c88d49cc 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.cs @@ -66,8 +66,8 @@ public CSharpProjectShim( var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - this.ProjectCodeModel = componentModel.GetService().CreateProjectCodeModel(VisualStudioProject.Id, this); - this.VisualStudioProjectOptionsProcessor = new OptionsProcessor(this.VisualStudioProject, Workspace.Services.SolutionServices); + this.ProjectCodeModel = componentModel.GetService().CreateProjectCodeModel(ProjectSystemProject.Id, this); + this.VisualStudioProjectOptionsProcessor = new OptionsProcessor(this.ProjectSystemProject, Workspace.Services.SolutionServices); // Ensure the default options are set up ResetAllOptions(); diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs index e8edaf4174ef4..9de1291b29a75 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs @@ -111,7 +111,7 @@ public void ProjectOutputPathAndOutputExeNameChange() Assert.Equal(initialPath, project.GetOutputFileName()); string getCurrentCompilationOutputAssemblyPath() - => environment.Workspace.CurrentSolution.GetRequiredProject(project.Test_VisualStudioProject.Id).CompilationOutputInfo.AssemblyPath; + => environment.Workspace.CurrentSolution.GetRequiredProject(project.Test_ProjectSystemProject.Id).CompilationOutputInfo.AssemblyPath; Assert.Equal(initialPath, getCurrentCompilationOutputAssemblyPath()); @@ -144,7 +144,7 @@ public void ProjectCompilationOutputsChange() var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); string getCurrentCompilationOutputAssemblyPath() - => environment.Workspace.CurrentSolution.GetRequiredProject(project.Test_VisualStudioProject.Id).CompilationOutputInfo.AssemblyPath; + => environment.Workspace.CurrentSolution.GetRequiredProject(project.Test_ProjectSystemProject.Id).CompilationOutputInfo.AssemblyPath; Assert.Null(getCurrentCompilationOutputAssemblyPath()); diff --git a/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptVisualStudioProjectWrapper.cs b/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptVisualStudioProjectWrapper.cs index 19918dcdb1617..b4610c7d635c1 100644 --- a/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptVisualStudioProjectWrapper.cs +++ b/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptVisualStudioProjectWrapper.cs @@ -4,13 +4,14 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api { internal sealed partial class VSTypeScriptVisualStudioProjectWrapper { - public VSTypeScriptVisualStudioProjectWrapper(VisualStudioProject underlyingObject) + public VSTypeScriptVisualStudioProjectWrapper(ProjectSystemProject underlyingObject) => Project = underlyingObject; public ProjectId Id => Project.Id; @@ -39,6 +40,6 @@ public void RemoveSourceTextContainer(SourceTextContainer sourceTextContainer) public void RemoveFromWorkspace() => Project.RemoveFromWorkspace(); - internal VisualStudioProject Project { get; } + internal ProjectSystemProject Project { get; } } } diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs index c9cddf2aee538..072ccf9ee8198 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsContainedLanguageFactory.cs @@ -6,6 +6,7 @@ using System; using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; @@ -14,7 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService { internal abstract partial class AbstractLanguageService : IVsContainedLanguageFactory { - private VisualStudioProject FindMatchingProject(IVsHierarchy hierarchy, uint itemid) + private ProjectSystemProject FindMatchingProject(IVsHierarchy hierarchy, uint itemid) { // Here we must determine the project that this file's document is to be a part of. // Venus creates a separate Project for a .aspx or .ascx file, and so we must associate diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs index c113674a94b9e..3339f0426f026 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; @@ -276,7 +277,7 @@ private void UninitializeLanguageDebugInfo() => this.LanguageDebugInfo = null; protected virtual IVsContainedLanguage CreateContainedLanguage( - IVsTextBufferCoordinator bufferCoordinator, VisualStudioProject project, + IVsTextBufferCoordinator bufferCoordinator, ProjectSystemProject project, IVsHierarchy hierarchy, uint itemid) { return new ContainedLanguage( diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 8fdb536af644c..5481ddd9046fe 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -35,7 +35,6 @@ - diff --git a/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs index 7c3b2e5036177..21b93d26e51be 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; @@ -183,7 +184,7 @@ public string DisplayName #nullable enable - public VisualStudioProject? VisualStudioProject { get; internal set; } + public ProjectSystemProject? VisualStudioProject { get; internal set; } #nullable disable diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs index bca7a6ba1cfee..0543999160655 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; @@ -29,12 +30,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L internal abstract partial class AbstractLegacyProject : ForegroundThreadAffinitizedObject { public IVsHierarchy Hierarchy { get; } - protected VisualStudioProject VisualStudioProject { get; } + protected ProjectSystemProject ProjectSystemProject { get; } internal VisualStudioProjectOptionsProcessor VisualStudioProjectOptionsProcessor { get; set; } protected IProjectCodeModel ProjectCodeModel { get; set; } protected VisualStudioWorkspace Workspace { get; } - internal VisualStudioProject Test_VisualStudioProject => VisualStudioProject; + internal ProjectSystemProject Test_ProjectSystemProject => ProjectSystemProject; /// /// The path to the directory of the project. Read-only, since although you can rename @@ -102,7 +103,7 @@ public AbstractLegacyProject( } var projectFactory = componentModel.GetService(); - VisualStudioProject = threadingContext.JoinableTaskFactory.Run(() => projectFactory.CreateAndAddToWorkspaceAsync( + ProjectSystemProject = threadingContext.JoinableTaskFactory.Run(() => projectFactory.CreateAndAddToWorkspaceAsync( projectSystemName, language, new VisualStudioProjectCreationInfo @@ -117,7 +118,7 @@ public AbstractLegacyProject( CancellationToken.None)); workspaceImpl.AddProjectRuleSetFileToInternalMaps( - VisualStudioProject, + ProjectSystemProject, () => VisualStudioProjectOptionsProcessor.EffectiveRuleSetFilePath); // Right now VB doesn't have the concept of "default namespace". But we conjure one in workspace @@ -125,44 +126,44 @@ public AbstractLegacyProject( // use it for their own purpose. // In the future, we might consider officially exposing "default namespace" for VB project // (e.g. through a msbuild property) - VisualStudioProject.DefaultNamespace = GetRootNamespacePropertyValue(hierarchy); + ProjectSystemProject.DefaultNamespace = GetRootNamespacePropertyValue(hierarchy); if (TryGetPropertyValue(hierarchy, BuildPropertyNames.MaxSupportedLangVersion, out var maxLangVer)) { - VisualStudioProject.MaxLangVersion = maxLangVer; + ProjectSystemProject.MaxLangVersion = maxLangVer; } if (TryGetBoolPropertyValue(hierarchy, BuildPropertyNames.RunAnalyzers, out var runAnayzers)) { - VisualStudioProject.RunAnalyzers = runAnayzers; + ProjectSystemProject.RunAnalyzers = runAnayzers; } if (TryGetBoolPropertyValue(hierarchy, BuildPropertyNames.RunAnalyzersDuringLiveAnalysis, out var runAnayzersDuringLiveAnalysis)) { - VisualStudioProject.RunAnalyzersDuringLiveAnalysis = runAnayzersDuringLiveAnalysis; + ProjectSystemProject.RunAnalyzersDuringLiveAnalysis = runAnayzersDuringLiveAnalysis; } Hierarchy = hierarchy; ConnectHierarchyEvents(); RefreshBinOutputPath(); - _externalErrorReporter = new ProjectExternalErrorReporter(VisualStudioProject.Id, externalErrorReportingPrefix, language, workspaceImpl); + _externalErrorReporter = new ProjectExternalErrorReporter(ProjectSystemProject.Id, externalErrorReportingPrefix, language, workspaceImpl); _batchScopeCreator = componentModel.GetService(); - _batchScopeCreator.StartTrackingProject(VisualStudioProject, Hierarchy); + _batchScopeCreator.StartTrackingProject(ProjectSystemProject, Hierarchy); } - public string AssemblyName => VisualStudioProject.AssemblyName; + public string AssemblyName => ProjectSystemProject.AssemblyName; public string GetOutputFileName() - => VisualStudioProject.CompilationOutputAssemblyFilePath; + => ProjectSystemProject.CompilationOutputAssemblyFilePath; public virtual void Disconnect() { - _batchScopeCreator.StopTrackingProject(VisualStudioProject); + _batchScopeCreator.StopTrackingProject(ProjectSystemProject); VisualStudioProjectOptionsProcessor?.Dispose(); ProjectCodeModel.OnProjectClosed(); - VisualStudioProject.RemoveFromWorkspace(); + ProjectSystemProject.RemoveFromWorkspace(); // Unsubscribe IVsHierarchyEvents DisconnectHierarchyEvents(); @@ -190,7 +191,7 @@ protected void AddFile( folders = GetFolderNamesForDocument(itemid); } - VisualStudioProject.AddSourceFile(filename, sourceCodeKind, folders); + ProjectSystemProject.AddSourceFile(filename, sourceCodeKind, folders); } protected void AddFile( @@ -212,14 +213,14 @@ protected void AddFile( var linkFolderPath = Path.GetDirectoryName(linkMetadata); folders = linkFolderPath.Split(PathSeparatorCharacters, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray(); } - else if (!string.IsNullOrEmpty(VisualStudioProject.FilePath)) + else if (!string.IsNullOrEmpty(ProjectSystemProject.FilePath)) { var relativePath = PathUtilities.GetRelativePath(_projectDirectory, filename); var relativePathParts = relativePath.Split(PathSeparatorCharacters); folders = ImmutableArray.Create(relativePathParts, start: 0, length: relativePathParts.Length - 1); } - VisualStudioProject.AddSourceFile(filename, sourceCodeKind, folders); + ProjectSystemProject.AddSourceFile(filename, sourceCodeKind, folders); } protected void RemoveFile(string filename) @@ -232,7 +233,7 @@ protected void RemoveFile(string filename) return; } - VisualStudioProject.RemoveSourceFile(filename); + ProjectSystemProject.RemoveSourceFile(filename); ProjectCodeModel.OnSourceFileRemoved(filename); } @@ -266,12 +267,12 @@ protected void RefreshBinOutputPath() // web app case if (!PathUtilities.IsAbsolute(outputDirectory)) { - if (VisualStudioProject.FilePath == null) + if (ProjectSystemProject.FilePath == null) { return; } - outputDirectory = FileUtilities.ResolveRelativePath(outputDirectory, Path.GetDirectoryName(VisualStudioProject.FilePath)); + outputDirectory = FileUtilities.ResolveRelativePath(outputDirectory, Path.GetDirectoryName(ProjectSystemProject.FilePath)); } if (outputDirectory == null) @@ -279,15 +280,15 @@ protected void RefreshBinOutputPath() return; } - VisualStudioProject.OutputFilePath = FileUtilities.NormalizeAbsolutePath(Path.Combine(outputDirectory, targetFileName)); + ProjectSystemProject.OutputFilePath = FileUtilities.NormalizeAbsolutePath(Path.Combine(outputDirectory, targetFileName)); if (ErrorHandler.Succeeded(storage.GetPropertyValue("TargetRefPath", null, (uint)_PersistStorageType.PST_PROJECT_FILE, out var targetRefPath)) && !string.IsNullOrEmpty(targetRefPath)) { - VisualStudioProject.OutputRefFilePath = targetRefPath; + ProjectSystemProject.OutputRefFilePath = targetRefPath; } else { - VisualStudioProject.OutputRefFilePath = null; + ProjectSystemProject.OutputRefFilePath = null; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerConfigFileHost.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerConfigFileHost.cs index 6f482fe229e5e..d76b697e2a228 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerConfigFileHost.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerConfigFileHost.cs @@ -11,9 +11,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L internal abstract partial class AbstractLegacyProject : IAnalyzerConfigFileHost { void IAnalyzerConfigFileHost.AddAnalyzerConfigFile(string filePath) - => VisualStudioProject.AddAnalyzerConfigFile(filePath); + => ProjectSystemProject.AddAnalyzerConfigFile(filePath); void IAnalyzerConfigFileHost.RemoveAnalyzerConfigFile(string filePath) - => VisualStudioProject.RemoveAnalyzerConfigFile(filePath); + => ProjectSystemProject.RemoveAnalyzerConfigFile(filePath); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs index 2ad93728581c6..a402c829cea41 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IAnalyzerHost.cs @@ -13,10 +13,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L internal abstract partial class AbstractLegacyProject : IAnalyzerHost { void IAnalyzerHost.AddAnalyzerReference(string analyzerAssemblyFullPath) - => VisualStudioProject.AddAnalyzerReference(analyzerAssemblyFullPath); + => ProjectSystemProject.AddAnalyzerReference(analyzerAssemblyFullPath); void IAnalyzerHost.RemoveAnalyzerReference(string analyzerAssemblyFullPath) - => VisualStudioProject.RemoveAnalyzerReference(analyzerAssemblyFullPath); + => ProjectSystemProject.RemoveAnalyzerReference(analyzerAssemblyFullPath); void IAnalyzerHost.SetRuleSetFile(string ruleSetFileFullPath) { @@ -36,9 +36,9 @@ void IAnalyzerHost.SetRuleSetFile(string ruleSetFileFullPath) } void IAnalyzerHost.AddAdditionalFile(string additionalFilePath) - => VisualStudioProject.AddAdditionalFile(additionalFilePath); + => ProjectSystemProject.AddAdditionalFile(additionalFilePath); void IAnalyzerHost.RemoveAdditionalFile(string additionalFilePath) - => VisualStudioProject.RemoveAdditionalFile(additionalFilePath); + => ProjectSystemProject.RemoveAdditionalFile(additionalFilePath); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IProjectSiteEx.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IProjectSiteEx.cs index 9b2fae18a9f26..901dd26bbe4f8 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IProjectSiteEx.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IProjectSiteEx.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; using Roslyn.Utilities; @@ -14,10 +15,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L { internal abstract partial class AbstractLegacyProject : IProjectSiteEx { - private readonly Stack _batchScopes = new(); + private readonly Stack _batchScopes = new(); public void StartBatch() - => _batchScopes.Push(VisualStudioProject.CreateBatchScope()); + => _batchScopes.Push(ProjectSystemProject.CreateBatchScope()); public void EndBatch() { diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs index bf2bfee67068a..c642cad69efdd 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject_IVsHierarchyEvents.cs @@ -65,12 +65,12 @@ int IVsHierarchyEvents.OnPropertyChanged(uint itemid, int propid, uint flags) if (filePath != null && File.Exists(filePath)) { - VisualStudioProject.FilePath = filePath; + ProjectSystemProject.FilePath = filePath; } if (Hierarchy.TryGetName(out var name)) { - VisualStudioProject.DisplayName = name; + ProjectSystemProject.DisplayName = name; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs index 8a8b4e2408c2f..d20d924255115 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/SolutionEventsBatchScopeCreator.cs @@ -10,6 +10,7 @@ using System.Linq; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; @@ -24,7 +25,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.L [Export(typeof(SolutionEventsBatchScopeCreator))] internal sealed class SolutionEventsBatchScopeCreator : ForegroundThreadAffinitizedObject { - private readonly List<(VisualStudioProject project, IVsHierarchy hierarchy, VisualStudioProject.BatchScope batchScope)> _fullSolutionLoadScopes = new List<(VisualStudioProject, IVsHierarchy, VisualStudioProject.BatchScope)>(); + private readonly List<(ProjectSystemProject project, IVsHierarchy hierarchy, ProjectSystemProject.BatchScope batchScope)> _fullSolutionLoadScopes = new List<(ProjectSystemProject, IVsHierarchy, ProjectSystemProject.BatchScope)>(); private uint? _runningDocumentTableEventsCookie; @@ -41,7 +42,7 @@ public SolutionEventsBatchScopeCreator(IThreadingContext threadingContext, [Impo _serviceProvider = serviceProvider; } - public void StartTrackingProject(VisualStudioProject project, IVsHierarchy hierarchy) + public void StartTrackingProject(ProjectSystemProject project, IVsHierarchy hierarchy) { AssertIsForeground(); @@ -55,7 +56,7 @@ public void StartTrackingProject(VisualStudioProject project, IVsHierarchy hiera } } - public void StopTrackingProject(VisualStudioProject project) + public void StopTrackingProject(ProjectSystemProject project) { AssertIsForeground(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectCreationInfo.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectCreationInfo.cs index b1e8c55109347..f88758b30931c 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectCreationInfo.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectCreationInfo.cs @@ -4,17 +4,13 @@ using System; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { - internal sealed class VisualStudioProjectCreationInfo + internal sealed class VisualStudioProjectCreationInfo : ProjectSystemProjectCreationInfo { - public string? AssemblyName { get; set; } - public CompilationOptions? CompilationOptions { get; set; } - public string? FilePath { get; set; } - public ParseOptions? ParseOptions { get; set; } - public IVsHierarchy? Hierarchy { get; set; } public Guid ProjectGuid { get; set; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 6e48983529a3a..1034e8969eadb 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -9,11 +9,13 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api; using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; @@ -56,10 +58,10 @@ public VisualStudioProjectFactory( _serviceProvider = (Shell.IAsyncServiceProvider)serviceProvider; } - public Task CreateAndAddToWorkspaceAsync(string projectSystemName, string language, CancellationToken cancellationToken) + public Task CreateAndAddToWorkspaceAsync(string projectSystemName, string language, CancellationToken cancellationToken) => CreateAndAddToWorkspaceAsync(projectSystemName, language, new VisualStudioProjectCreationInfo(), cancellationToken); - public async Task CreateAndAddToWorkspaceAsync( + public async Task CreateAndAddToWorkspaceAsync( string projectSystemName, string language, VisualStudioProjectCreationInfo creationInfo, CancellationToken cancellationToken) { // HACK: Fetch this service to ensure it's still created on the UI thread; once this is @@ -92,66 +94,17 @@ public async Task CreateAndAddToWorkspaceAsync( } // From this point on, we start mutating the solution. So make us non cancellable. +#pragma warning disable IDE0059 // Unnecessary assignment of a value cancellationToken = CancellationToken.None; +#pragma warning restore IDE0059 // Unnecessary assignment of a value - var id = ProjectId.CreateNewId(projectSystemName); - var assemblyName = creationInfo.AssemblyName ?? projectSystemName; - - // We will use the project system name as the default display name of the project - var project = new VisualStudioProject( - _visualStudioWorkspaceImpl, - _dynamicFileInfoProviders, - _hostDiagnosticUpdateSource, - vsixAnalyzerProvider, - id, - displayName: projectSystemName, - language, - assemblyName: assemblyName, - compilationOptions: creationInfo.CompilationOptions, - filePath: creationInfo.FilePath, - parseOptions: creationInfo.ParseOptions); - - var versionStamp = creationInfo.FilePath != null ? VersionStamp.Create(File.GetLastWriteTimeUtc(creationInfo.FilePath)) - : VersionStamp.Create(); - - await _visualStudioWorkspaceImpl.ApplyChangeToWorkspaceAsync(w => - { - _visualStudioWorkspaceImpl.AddProjectToInternalMaps_NoLock(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); - - var projectInfo = ProjectInfo.Create( - new ProjectInfo.ProjectAttributes( - id, - versionStamp, - name: projectSystemName, - assemblyName: assemblyName, - language: language, - checksumAlgorithm: SourceHashAlgorithms.Default, // will be updated when command line is set - compilationOutputFilePaths: default, // will be updated when command line is set - filePath: creationInfo.FilePath, - telemetryId: creationInfo.ProjectGuid), - compilationOptions: creationInfo.CompilationOptions, - parseOptions: creationInfo.ParseOptions); - - // If we don't have any projects and this is our first project being added, then we'll create a new SolutionId - // and count this as the solution being added so that event is raised. - if (w.CurrentSolution.ProjectIds.Count == 0) - { - var solutionSessionId = GetSolutionSessionId(); - - w.OnSolutionAdded( - SolutionInfo.Create( - SolutionId.CreateNewId(solutionFilePath), - VersionStamp.Create(), - solutionFilePath, - projects: new[] { projectInfo }, - analyzerReferences: w.CurrentSolution.AnalyzerReferences) - .WithTelemetryId(solutionSessionId)); - } - else - { - w.OnProjectAdded(projectInfo); - } - }); + _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionPath = solutionFilePath; + _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.SolutionTelemetryId = GetSolutionSessionId(); + + var hostInfo = new ProjectSystemHostInfo(_dynamicFileInfoProviders, _hostDiagnosticUpdateSource, vsixAnalyzerProvider); + var project = await _visualStudioWorkspaceImpl.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync(projectSystemName, language, creationInfo, hostInfo); + + _visualStudioWorkspaceImpl.AddProjectToInternalMaps(project, creationInfo.Hierarchy, creationInfo.ProjectGuid, projectSystemName); // Ensure that other VS contexts get accurate information that the UIContext for this language is now active. // This is not cancellable as we have already mutated the solution. diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs index 838b2d5a98d22..a4b870eb4c79c 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -10,13 +10,14 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Scripting.Hosting; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal class VisualStudioProjectOptionsProcessor : IDisposable { - private readonly VisualStudioProject _project; + private readonly ProjectSystemProject _project; private readonly SolutionServices _workspaceServices; private readonly ICommandLineParserService _commandLineParserService; private readonly ITemporaryStorageServiceInternal _temporaryStorageService; @@ -47,7 +48,7 @@ internal class VisualStudioProjectOptionsProcessor : IDisposable private IReferenceCountedDisposable>? _ruleSetFile = null; public VisualStudioProjectOptionsProcessor( - VisualStudioProject project, + ProjectSystemProject project, SolutionServices workspaceServices) { _project = project ?? throw new ArgumentNullException(nameof(project)); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index 1287b2ed072c6..753feab9e38ec 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; @@ -37,6 +38,7 @@ public sealed class OpenFileTracker : IRunningDocumentTableEventListener private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization; private readonly VisualStudioWorkspaceImpl _workspace; + private readonly ProjectSystemProjectFactory _projectSystemProjectFactory; private readonly IAsynchronousOperationListener _asyncOperationListener; private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; @@ -97,9 +99,10 @@ private readonly MultiDictionary private const int CutoffForCheckingAllRunningDocumentTableDocuments = 10; - private OpenFileTracker(VisualStudioWorkspaceImpl workspace, IVsRunningDocumentTable runningDocumentTable, IComponentModel componentModel) + private OpenFileTracker(VisualStudioWorkspaceImpl workspace, ProjectSystemProjectFactory projectSystemProjectFactory, IVsRunningDocumentTable runningDocumentTable, IComponentModel componentModel) { _workspace = workspace; + _projectSystemProjectFactory = projectSystemProjectFactory; _foregroundAffinitization = new ForegroundThreadAffinitizedObject(workspace._threadingContext, assertIsForeground: true); _asyncOperationListener = componentModel.GetService().GetListener(FeatureAttribute.Workspace); _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker(workspace._threadingContext, @@ -123,7 +126,7 @@ void IRunningDocumentTableEventListener.OnRenameDocument(string newMoniker, stri { } - public static async Task CreateAsync(VisualStudioWorkspaceImpl workspace, IAsyncServiceProvider asyncServiceProvider) + public static async Task CreateAsync(VisualStudioWorkspaceImpl workspace, ProjectSystemProjectFactory projectSystemProjectFactory, IAsyncServiceProvider asyncServiceProvider) { var runningDocumentTable = (IVsRunningDocumentTable?)await asyncServiceProvider.GetServiceAsync(typeof(SVsRunningDocumentTable)).ConfigureAwait(true); Assumes.Present(runningDocumentTable); @@ -131,16 +134,16 @@ public static async Task CreateAsync(VisualStudioWorkspaceImpl var componentModel = (IComponentModel?)await asyncServiceProvider.GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true); Assumes.Present(componentModel); - return new OpenFileTracker(workspace, runningDocumentTable, componentModel); + return new OpenFileTracker(workspace, projectSystemProjectFactory, runningDocumentTable, componentModel); } private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy) { _foregroundAffinitization.AssertIsForeground(); - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { - var documentIds = _workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); + var documentIds = _projectSystemProjectFactory.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); if (documentIds.IsDefaultOrEmpty) { return; @@ -166,7 +169,7 @@ private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffe foreach (var documentId in documentIds) { - if (!w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) + if (!w.IsDocumentOpen(documentId) && !_projectSystemProjectFactory.DocumentsNotFromFiles.Contains(documentId)) { var isCurrentContext = documentId.ProjectId == activeContextProjectId; if (w.CurrentSolution.ContainsDocument(documentId)) @@ -284,7 +287,7 @@ private void RefreshContextForMoniker(string moniker, IVsHierarchy hierarchy) { _foregroundAffinitization.AssertIsForeground(); - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { var documentIds = _workspace.CurrentSolution.GetDocumentIdsWithFilePath(moniker); if (documentIds.IsDefaultOrEmpty || documentIds.Length == 1) @@ -326,7 +329,7 @@ private void TryClosingDocumentsForMoniker(string moniker) UnsubscribeFromWatchedHierarchies(moniker); - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { var documentIds = w.CurrentSolution.GetDocumentIdsWithFilePath(moniker); if (documentIds.IsDefaultOrEmpty) @@ -336,7 +339,7 @@ private void TryClosingDocumentsForMoniker(string moniker) foreach (var documentId in documentIds) { - if (w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) + if (w.IsDocumentOpen(documentId) && !_projectSystemProjectFactory.DocumentsNotFromFiles.Contains(documentId)) { var solution = w.CurrentSolution; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs index 51945225467d8..88327564f95a9 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs @@ -35,7 +35,7 @@ public SolutionAnalyzerSetter(VisualStudioWorkspaceImpl workspace) => _workspace = workspace; public void SetAnalyzerReferences(ImmutableArray references) - => _workspace.ApplyChangeToWorkspace(w => w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); + => _workspace.ProjectSystemProjectFactory.ApplyChangeToWorkspace(w => w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); } } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index eeb532930a7db..702a9640be895 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -51,6 +51,7 @@ using Solution = Microsoft.CodeAnalysis.Solution; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.ProjectSystem; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { @@ -73,11 +74,8 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly ITextBufferCloneService _textBufferCloneService; /// - /// The main gate to synchronize updates to this solution. + /// Guards any updates to the maps here that aren't updated via interlocked updates. /// - /// - /// See the Readme.md in this directory for further comments about threading in this area. - /// private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); /// @@ -88,12 +86,6 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private ImmutableDictionary _projectToHierarchyMap = ImmutableDictionary.Empty; private ImmutableDictionary _projectToGuidMap = ImmutableDictionary.Empty; - /// Should be updated with . - private ImmutableDictionary _projectToMaxSupportedLangVersionMap = ImmutableDictionary.Empty; - - /// Should be updated with . - private ImmutableDictionary _projectToDependencyNodeTargetIdentifier = ImmutableDictionary.Empty; - /// /// A map to fetch the path to a rule set file for a project. This right now is only used to implement /// and any other use is extremely suspicious, since direct use of this is out of @@ -102,24 +94,13 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac /// Should be updated with . private ImmutableDictionary> _projectToRuleSetFilePath = ImmutableDictionary>.Empty; - private readonly Dictionary> _projectSystemNameToProjectsMap = new(); + private readonly Dictionary> _projectSystemNameToProjectsMap = new(); /// /// Only safe to use on the UI thread. /// private readonly Dictionary _languageToProjectExistsUIContext = new(); - /// - /// A set of documents that were added by , and aren't otherwise - /// tracked for opening/closing. - /// - private ImmutableHashSet _documentsNotFromFiles = ImmutableHashSet.Empty; - - /// - /// Indicates whether the current solution is closing. - /// - private bool _solutionClosing; - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] internal VisualStudioProjectTracker? _projectTracker; @@ -127,7 +108,8 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private OpenFileTracker? _openFileTracker; internal IFileChangeWatcher FileChangeWatcher { get; } - internal FileWatchedPortableExecutableReferenceFactory FileWatchedReferenceFactory { get; } + + internal ProjectSystemProjectFactory ProjectSystemProjectFactory { get; } private readonly Lazy _projectCodeModelFactory; @@ -156,12 +138,11 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro _textBufferFactoryService.TextBufferCreated += AddTextBufferCloneServiceToBuffer; _projectionBufferFactoryService.ProjectionBufferCreated += AddTextBufferCloneServiceToBuffer; - _ = Task.Run(() => InitializeUIAffinitizedServicesAsync(asyncServiceProvider)); - FileChangeWatcher = exportProvider.GetExportedValue().Watcher; - FileWatchedReferenceFactory = new FileWatchedPortableExecutableReferenceFactory(this.Services.SolutionServices, FileChangeWatcher); - FileWatchedReferenceFactory.ReferenceChanged += this.StartRefreshingMetadataReferencesForFile; + ProjectSystemProjectFactory = new ProjectSystemProjectFactory(this, FileChangeWatcher, QueueCheckForFilesBeingOpen, RemoveProjectFromMaps); + + _ = Task.Run(() => InitializeUIAffinitizedServicesAsync(asyncServiceProvider)); _lazyExternalErrorDiagnosticUpdateSource = new Lazy(() => new ExternalErrorDiagnosticUpdateSource( @@ -229,9 +210,9 @@ public async Task InitializeUIAffinitizedServicesAsync(IAsyncServiceProvider asy var telemetrySession = TelemetryService.DefaultSession; var solutionClosingContext = UIContext.FromUIContextGuid(VSConstants.UICONTEXT.SolutionClosing_guid); - solutionClosingContext.UIContextChanged += (_, e) => _solutionClosing = e.Activated; + solutionClosingContext.UIContextChanged += (_, e) => ProjectSystemProjectFactory.SolutionClosing = e.Activated; - var openFileTracker = await OpenFileTracker.CreateAsync(this, asyncServiceProvider).ConfigureAwait(true); + var openFileTracker = await OpenFileTracker.CreateAsync(this, ProjectSystemProjectFactory, asyncServiceProvider).ConfigureAwait(true); var memoryListener = await VirtualMemoryNotificationListener.CreateAsync(this, _threadingContext, asyncServiceProvider, _globalOptions, _threadingContext.DisposalToken).ConfigureAwait(true); // Update our fields first, so any asynchronous work that needs to use these is able to see the service. @@ -264,33 +245,21 @@ public void QueueCheckForFilesBeingOpen(ImmutableArray newFileNames) public void ProcessQueuedWorkOnUIThread() => _openFileTracker?.ProcessQueuedWorkOnUIThread(); - internal void AddProjectToInternalMaps_NoLock(VisualStudioProject project, IVsHierarchy? hierarchy, Guid guid, string projectSystemName) + internal void AddProjectToInternalMaps(ProjectSystemProject project, IVsHierarchy? hierarchy, Guid guid, string projectSystemName) { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - _projectToHierarchyMap = _projectToHierarchyMap.Add(project.Id, hierarchy); - _projectToGuidMap = _projectToGuidMap.Add(project.Id, guid); - _projectSystemNameToProjectsMap.MultiAdd(projectSystemName, project); + using (_gate.DisposableWait()) + { + _projectToHierarchyMap = _projectToHierarchyMap.Add(project.Id, hierarchy); + _projectToGuidMap = _projectToGuidMap.Add(project.Id, guid); + _projectSystemNameToProjectsMap.MultiAdd(projectSystemName, project); + } } - internal void AddProjectRuleSetFileToInternalMaps(VisualStudioProject project, Func ruleSetFilePathFunc) + internal void AddProjectRuleSetFileToInternalMaps(ProjectSystemProject project, Func ruleSetFilePathFunc) { Contract.ThrowIfFalse(ImmutableInterlocked.TryAdd(ref _projectToRuleSetFilePath, project.Id, ruleSetFilePathFunc)); } - internal void AddDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - _documentsNotFromFiles = _documentsNotFromFiles.Add(documentId); - } - - internal void RemoveDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - _documentsNotFromFiles = _documentsNotFromFiles.Remove(documentId); - } - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] internal VisualStudioProjectTracker GetProjectTrackerAndInitializeIfNecessary() { @@ -308,7 +277,7 @@ internal VisualStudioProjectTracker ProjectTracker } } - internal VisualStudioProject? GetProjectWithHierarchyAndName(IVsHierarchy hierarchy, string projectName) + internal ProjectSystemProject? GetProjectWithHierarchyAndName(IVsHierarchy hierarchy, string projectName) { using (_gate.DisposableWait()) { @@ -316,7 +285,7 @@ internal VisualStudioProjectTracker ProjectTracker } } - private VisualStudioProject? GetProjectWithHierarchyAndName_NoLock(IVsHierarchy hierarchy, string projectName) + private ProjectSystemProject? GetProjectWithHierarchyAndName_NoLock(IVsHierarchy hierarchy, string projectName) { if (_projectSystemNameToProjectsMap.TryGetValue(projectName, out var projects)) { @@ -469,7 +438,7 @@ public override bool CanApplyCompilationOptionChange(CompilationOptions oldOptio public override bool CanApplyParseOptionChange(ParseOptions oldOptions, ParseOptions newOptions, CodeAnalysis.Project project) { - _projectToMaxSupportedLangVersionMap.TryGetValue(project.Id, out var maxSupportLangVersion); + var maxSupportLangVersion = ProjectSystemProjectFactory.TryGetMaxSupportedLanguageVersion(project.Id); return project.Services.GetRequiredService().CanApplyChange(oldOptions, newOptions, maxSupportLangVersion); } @@ -1349,9 +1318,7 @@ internal override Guid GetProjectGuid(ProjectId projectId) internal string? TryGetDependencyNodeTargetIdentifier(ProjectId projectId) { - // This doesn't take a lock since _projectToDependencyNodeTargetIdentifier is immutable - _projectToDependencyNodeTargetIdentifier.TryGetValue(projectId, out var identifier); - return identifier; + return ProjectSystemProjectFactory.TryGetDependencyNodeTargetIdentifier(projectId); } internal override void SetDocumentContext(DocumentId documentId) @@ -1438,7 +1405,6 @@ protected override void Dispose(bool finalize) { _textBufferFactoryService.TextBufferCreated -= AddTextBufferCloneServiceToBuffer; _projectionBufferFactoryService.ProjectionBufferCreated -= AddTextBufferCloneServiceToBuffer; - FileWatchedReferenceFactory.ReferenceChanged -= StartRefreshingMetadataReferencesForFile; if (_lazyExternalErrorDiagnosticUpdateSource.IsValueCreated) { @@ -1553,147 +1519,11 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj return result != (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY; } - /// - /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. - /// - public void ApplyChangeToWorkspace(Action action) + internal void RemoveProjectFromMaps(CodeAnalysis.Project project) { - using (_gate.DisposableWait()) - { - action(this); - } - } - - /// - /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. - /// - public async ValueTask ApplyChangeToWorkspaceAsync(Action action) - { - using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) - { - action(this); - } - } - - /// - /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. - /// - public async ValueTask ApplyChangeToWorkspaceMaybeAsync(bool useAsync, Action action) - { - using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) - { - action(this); - } - } - - /// - /// Applies a solution transformation to the workspace and triggers workspace changed event for specified . - /// The transformation shall only update the project of the solution with the specified . - /// - public void ApplyChangeToWorkspace(ProjectId projectId, Func solutionTransformation) - { - using (_gate.DisposableWait()) - { - SetCurrentSolution(solutionTransformation, WorkspaceChangeKind.ProjectChanged, projectId); - } - } - - /// - public void ApplyBatchChangeToWorkspace(Action mutation) - { - ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: false, mutation).VerifyCompleted(); - } - - /// - public Task ApplyBatchChangeToWorkspaceAsync(Action mutation) - { - return ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: true, mutation); - } - - /// - /// Applies a change to the workspace that can do any number of project changes. - /// - /// This is needed to synchronize with to avoid any races. This - /// method could be moved down to the core Workspace layer and then could use the synchronization lock there. - public async Task ApplyBatchChangeToWorkspaceMaybeAsync(bool useAsync, Action mutation) - { - using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) - { - var solutionChanges = new SolutionChangeAccumulator(CurrentSolution); - mutation(solutionChanges); - - ApplyBatchChangeToWorkspace_NoLock(solutionChanges); - } - } - - private void ApplyBatchChangeToWorkspace_NoLock(SolutionChangeAccumulator solutionChanges) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - if (!solutionChanges.HasChange) - return; - - this.SetCurrentSolution( - _ => solutionChanges.Solution, - solutionChanges.WorkspaceChangeKind, - solutionChanges.WorkspaceChangeProjectId, - solutionChanges.WorkspaceChangeDocumentId, - onBeforeUpdate: (_, _) => - { - // Clear out mutable state not associated with the solution snapshot (for example, which documents are - // currently open). - foreach (var documentId in solutionChanges.DocumentIdsRemoved) - this.ClearDocumentData(documentId); - }); - } - - private readonly Dictionary _projectReferenceInfoMap = new(); - - private ProjectReferenceInformation GetReferenceInfo_NoLock(ProjectId projectId) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - return _projectReferenceInfoMap.GetOrAdd(projectId, _ => new ProjectReferenceInformation()); - } - - /// - /// Removes the project from the various maps this type maintains; it's still up to the caller to actually remove - /// the project in one way or another. - /// - internal void RemoveProjectFromTrackingMaps_NoLock(ProjectId projectId) - { - string? languageName; - - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - languageName = CurrentSolution.GetRequiredProject(projectId).Language; - - if (_projectReferenceInfoMap.TryGetValue(projectId, out var projectReferenceInfo)) - { - // If we still had any output paths, we'll want to remove them to cause conversion back to metadata references. - // The call below implicitly is modifying the collection we've fetched, so we'll make a copy. - var solutionChanges = new SolutionChangeAccumulator(this.CurrentSolution); - - foreach (var outputPath in projectReferenceInfo.OutputPaths.ToList()) - { - RemoveProjectOutputPath_NoLock(solutionChanges, projectId, outputPath); - } - - ApplyBatchChangeToWorkspace_NoLock(solutionChanges); - - _projectReferenceInfoMap.Remove(projectId); - } - - _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); - _projectToGuidMap = _projectToGuidMap.Remove(projectId); - - ImmutableInterlocked.TryRemove(ref _projectToMaxSupportedLangVersionMap, projectId, out _); - ImmutableInterlocked.TryRemove(ref _projectToDependencyNodeTargetIdentifier, projectId, out _); - ImmutableInterlocked.TryRemove(ref _projectToRuleSetFilePath, projectId, out _); - foreach (var (projectName, projects) in _projectSystemNameToProjectsMap) { - if (projects.RemoveAll(p => p.Id == projectId) > 0) + if (projects.RemoveAll(p => p.Id == project.Id) > 0) { if (projects.Count == 0) { @@ -1704,372 +1534,19 @@ internal void RemoveProjectFromTrackingMaps_NoLock(ProjectId projectId) } } - // Try to update the UI context info. But cancel that work if we're shutting down. - _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => - { - using var asyncToken = _workspaceListener.BeginAsyncOperation(nameof(RefreshProjectExistsUIContextForLanguageAsync)); - await RefreshProjectExistsUIContextForLanguageAsync(languageName, cancellationToken).ConfigureAwait(false); - }); - } - - internal void RemoveSolution_NoLock() - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - // At this point, we should have had RemoveProjectFromTrackingMaps_NoLock called for everything else, so it's just the solution itself - // to clean up - Contract.ThrowIfFalse(_projectReferenceInfoMap.Count == 0); - Contract.ThrowIfFalse(_projectToHierarchyMap.Count == 0); - Contract.ThrowIfFalse(_projectToGuidMap.Count == 0); - Contract.ThrowIfFalse(_projectToMaxSupportedLangVersionMap.Count == 0); - Contract.ThrowIfFalse(_projectToDependencyNodeTargetIdentifier.Count == 0); - Contract.ThrowIfFalse(_projectToRuleSetFilePath.Count == 0); - Contract.ThrowIfFalse(_projectSystemNameToProjectsMap.Count == 0); - - // Create a new empty solution and set this; we will reuse the same SolutionId and path since components still may have persistence information they still need - // to look up by that location; we also keep the existing analyzer references around since those are host-level analyzers that were loaded asynchronously. - ClearOpenDocuments(); - - SetCurrentSolution( - solution => CreateSolution( - SolutionInfo.Create( - SolutionId.CreateNewId(), - VersionStamp.Create(), - analyzerReferences: solution.AnalyzerReferences)), - WorkspaceChangeKind.SolutionRemoved); - } + _projectToHierarchyMap = _projectToHierarchyMap.Remove(project.Id); + _projectToGuidMap = _projectToGuidMap.Remove(project.Id); - private sealed class ProjectReferenceInformation - { - public readonly List OutputPaths = new(); - public readonly List<(string path, ProjectReference projectReference)> ConvertedProjectReferences = new List<(string path, ProjectReference)>(); - } + ImmutableInterlocked.TryRemove(ref _projectToRuleSetFilePath, project.Id, out _); - /// - /// A multimap from an output path to the project outputting to it. Ideally, this shouldn't ever - /// actually be a true multimap, since we shouldn't have two projects outputting to the same path, but - /// any bug by a project adding the wrong output path means we could end up with some duplication. - /// In that case, we'll temporarily have two until (hopefully) somebody removes it. - /// - private readonly Dictionary> _projectsByOutputPath = new(StringComparer.OrdinalIgnoreCase); - - public void AddProjectOutputPath_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); - - projectReferenceInformation.OutputPaths.Add(outputPath); - _projectsByOutputPath.MultiAdd(outputPath, projectId); - - var projectsForOutputPath = _projectsByOutputPath[outputPath]; - var distinctProjectsForOutputPath = projectsForOutputPath.Distinct().ToList(); - - // If we have exactly one, then we're definitely good to convert - if (projectsForOutputPath.Count == 1) - { - ConvertMetadataReferencesToProjectReferences_NoLock(solutionChanges, projectId, outputPath); - } - else if (distinctProjectsForOutputPath.Count == 1) - { - // The same project has multiple output paths that are the same. Any project would have already been converted - // by the prior add, so nothing further to do - } - else - { - // We have more than one project outputting to the same path. This shouldn't happen but we'll convert back - // because now we don't know which project to reference. - foreach (var otherProjectId in projectsForOutputPath) - { - // We know that since we're adding a path to projectId and we're here that we couldn't have already - // had a converted reference to us, instead we need to convert things that are pointing to the project - // we're colliding with - if (otherProjectId != projectId) - { - ConvertProjectReferencesToMetadataReferences_NoLock(solutionChanges, otherProjectId, outputPath); - } - } - } - } - - /// - /// Attempts to convert all metadata references to to a project reference to . - /// - /// The of the project that could be referenced in place of the output path. - /// The output path to replace. - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/31306", - Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] - private void ConvertMetadataReferencesToProjectReferences_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectIdToReference, string outputPath) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - foreach (var projectIdToRetarget in solutionChanges.Solution.ProjectIds) - { - if (CanConvertMetadataReferenceToProjectReference(solutionChanges.Solution, projectIdToRetarget, referencedProjectId: projectIdToReference)) - { - // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all - // Project instances to get created. - foreach (PortableExecutableReference reference in solutionChanges.Solution.GetProjectState(projectIdToRetarget)!.MetadataReferences) - { - if (string.Equals(reference.FilePath, outputPath, StringComparison.OrdinalIgnoreCase)) - { - FileWatchedReferenceFactory.StopWatchingReference(reference); - - var projectReference = new ProjectReference(projectIdToReference, reference.Properties.Aliases, reference.Properties.EmbedInteropTypes); - var newSolution = solutionChanges.Solution.RemoveMetadataReference(projectIdToRetarget, reference) - .AddProjectReference(projectIdToRetarget, projectReference); - - solutionChanges.UpdateSolutionForProjectAction(projectIdToRetarget, newSolution); - - GetReferenceInfo_NoLock(projectIdToRetarget).ConvertedProjectReferences.Add( - (reference.FilePath!, projectReference)); - - // We have converted one, but you could have more than one reference with different aliases - // that we need to convert, so we'll keep going - } - } - } - } - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/31306", - Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] - private static bool CanConvertMetadataReferenceToProjectReference(Solution solution, ProjectId projectIdWithMetadataReference, ProjectId referencedProjectId) - { - // We can never make a project reference ourselves. This isn't a meaningful scenario, but if somebody does this by accident - // we do want to throw exceptions. - if (projectIdWithMetadataReference == referencedProjectId) - { - return false; - } - - // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all - // Project instances to get created. - var projectWithMetadataReference = solution.GetProjectState(projectIdWithMetadataReference); - var referencedProject = solution.GetProjectState(referencedProjectId); - - Contract.ThrowIfNull(projectWithMetadataReference); - Contract.ThrowIfNull(referencedProject); - - // We don't want to convert a metadata reference to a project reference if the project being referenced isn't something - // we can create a Compilation for. For example, if we have a C# project, and it's referencing a F# project via a metadata reference - // everything would be fine if we left it a metadata reference. Converting it to a project reference means we couldn't create a Compilation - // anymore in the IDE, since the C# compilation would need to reference an F# compilation. F# projects referencing other F# projects though - // do expect this to work, and so we'll always allow references through of the same language. - if (projectWithMetadataReference.Language != referencedProject.Language) - { - if (projectWithMetadataReference.LanguageServices.GetService() != null && - referencedProject.LanguageServices.GetService() == null) - { - // We're referencing something that we can't create a compilation from something that can, so keep the metadata reference - return false; - } - } - - // If this is going to cause a circular reference, also disallow it - if (solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(referencedProjectId).Contains(projectIdWithMetadataReference)) - { - return false; - } - - return true; - } - - /// - /// Finds all projects that had a project reference to and convert it back to a metadata reference. - /// - /// The of the project being referenced. - /// The output path of the given project to remove the link to. - [PerformanceSensitive( - "https://github.com/dotnet/roslyn/issues/37616", - Constraint = "Update ConvertedProjectReferences in place to avoid duplicate list allocations.")] - private void ConvertProjectReferencesToMetadataReferences_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - foreach (var projectIdToRetarget in solutionChanges.Solution.ProjectIds) - { - var referenceInfo = GetReferenceInfo_NoLock(projectIdToRetarget); - - // Update ConvertedProjectReferences in place to avoid duplicate list allocations - for (var i = 0; i < referenceInfo.ConvertedProjectReferences.Count; i++) - { - var convertedReference = referenceInfo.ConvertedProjectReferences[i]; - - if (string.Equals(convertedReference.path, outputPath, StringComparison.OrdinalIgnoreCase) && - convertedReference.projectReference.ProjectId == projectId) - { - var metadataReference = - FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile( - convertedReference.path, - new MetadataReferenceProperties( - aliases: convertedReference.projectReference.Aliases, - embedInteropTypes: convertedReference.projectReference.EmbedInteropTypes)); - - var newSolution = solutionChanges.Solution.RemoveProjectReference(projectIdToRetarget, convertedReference.projectReference) - .AddMetadataReference(projectIdToRetarget, metadataReference); - - solutionChanges.UpdateSolutionForProjectAction(projectIdToRetarget, newSolution); - - referenceInfo.ConvertedProjectReferences.RemoveAt(i); - - // We have converted one, but you could have more than one reference with different aliases - // that we need to convert, so we'll keep going. Make sure to decrement the index so we don't - // skip any items. - i--; - } - } - } - } - - public ProjectReference? TryCreateConvertedProjectReference_NoLock(ProjectId referencingProject, string path, MetadataReferenceProperties properties) - { - // Any conversion to or from project references must be done under the global workspace lock, - // since that needs to be coordinated with updating all projects simultaneously. - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - if (_projectsByOutputPath.TryGetValue(path, out var ids) && ids.Distinct().Count() == 1) - { - var projectIdToReference = ids.First(); - - if (CanConvertMetadataReferenceToProjectReference(this.CurrentSolution, referencingProject, projectIdToReference)) - { - var projectReference = new ProjectReference( - projectIdToReference, - aliases: properties.Aliases, - embedInteropTypes: properties.EmbedInteropTypes); - - GetReferenceInfo_NoLock(referencingProject).ConvertedProjectReferences.Add((path, projectReference)); - - return projectReference; - } - else - { - return null; - } - } - else - { - return null; - } - } - - public ProjectReference? TryRemoveConvertedProjectReference_NoLock(ProjectId referencingProject, string path, MetadataReferenceProperties properties) - { - // Any conversion to or from project references must be done under the global workspace lock, - // since that needs to be coordinated with updating all projects simultaneously. - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - var projectReferenceInformation = GetReferenceInfo_NoLock(referencingProject); - foreach (var convertedProject in projectReferenceInformation.ConvertedProjectReferences) - { - if (convertedProject.path == path && - convertedProject.projectReference.EmbedInteropTypes == properties.EmbedInteropTypes && - convertedProject.projectReference.Aliases.SequenceEqual(properties.Aliases)) - { - projectReferenceInformation.ConvertedProjectReferences.Remove(convertedProject); - return convertedProject.projectReference; - } - } - - return null; - } - - public void RemoveProjectOutputPath_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) - { - Contract.ThrowIfFalse(_gate.CurrentCount == 0); - - var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); - if (!projectReferenceInformation.OutputPaths.Contains(outputPath)) - { - throw new ArgumentException($"Project does not contain output path '{outputPath}'", nameof(outputPath)); - } - - projectReferenceInformation.OutputPaths.Remove(outputPath); - _projectsByOutputPath.MultiRemove(outputPath, projectId); - - // When a project is closed, we may need to convert project references to metadata references (or vice - // versa). Failure to convert the references could leave a project in the workspace with a project - // reference to a project which is not open. - // - // For the specific case where the entire solution is closing, we do not need to update the state for - // remaining projects as each project closes, because we know those projects will be closed without - // further use. Avoiding reference conversion when the solution is closing improves performance for both - // IDE close scenarios and solution reload scenarios that occur after complex branch switches. - if (!_solutionClosing) - { - if (_projectsByOutputPath.TryGetValue(outputPath, out var remainingProjectsForOutputPath)) - { - var distinctRemainingProjects = remainingProjectsForOutputPath.Distinct(); - if (distinctRemainingProjects.Count() == 1) - { - // We had more than one project outputting to the same path. Now we're back down to one - // so we can reference that one again - ConvertMetadataReferencesToProjectReferences_NoLock(solutionChanges, distinctRemainingProjects.Single(), outputPath); - } - } - else - { - // No projects left, we need to convert back to metadata references - ConvertProjectReferencesToMetadataReferences_NoLock(solutionChanges, projectId, outputPath); - } - } - } - - private void StartRefreshingMetadataReferencesForFile(object sender, string fullFilePath) - { + // Try to update the UI context info. But cancel that work if we're shutting down. _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { - using var asyncToken = _workspaceListener.BeginAsyncOperation(nameof(StartRefreshingMetadataReferencesForFile)); - await ApplyBatchChangeToWorkspaceAsync(solutionChanges => - { - foreach (var project in CurrentSolution.Projects) - { - // Loop to find each reference with the given path. It's possible that there might be multiple references of the same path; - // the project system could concievably add the same reference multiple times but with different aliases. It's also possible - // we might not find the path at all: when we receive the file changed event, we aren't checking if the file is still - // in the workspace at that time; it's possible it might have already been removed. - foreach (var portableExecutableReference in project.MetadataReferences.OfType()) - { - if (portableExecutableReference.FilePath == fullFilePath) - { - FileWatchedReferenceFactory.StopWatchingReference(portableExecutableReference); - - var newPortableExecutableReference = - FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile( - portableExecutableReference.FilePath, - portableExecutableReference.Properties); - - var newSolution = solutionChanges.Solution.RemoveMetadataReference(project.Id, portableExecutableReference) - .AddMetadataReference(project.Id, newPortableExecutableReference); - - solutionChanges.UpdateSolutionForProjectAction(project.Id, newSolution); - - } - } - } - }).ConfigureAwait(false); + using var asyncToken = _workspaceListener.BeginAsyncOperation(nameof(RefreshProjectExistsUIContextForLanguageAsync)); + await RefreshProjectExistsUIContextForLanguageAsync(project.Language, cancellationToken).ConfigureAwait(false); }); } - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54137", AllowLocks = false)] - internal void SetMaxLanguageVersion(ProjectId projectId, string? maxLanguageVersion) - { - ImmutableInterlocked.Update( - ref _projectToMaxSupportedLangVersionMap, - static (map, arg) => map.SetItem(arg.projectId, arg.maxLanguageVersion), - (projectId, maxLanguageVersion)); - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54135", AllowLocks = false)] - internal void SetDependencyNodeTargetIdentifier(ProjectId projectId, string targetIdentifier) - { - ImmutableInterlocked.Update( - ref _projectToDependencyNodeTargetIdentifier, - static (map, arg) => map.SetItem(arg.projectId, arg.targetIdentifier), - (projectId, targetIdentifier)); - } - internal async Task RefreshProjectExistsUIContextForLanguageAsync(string language, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); diff --git a/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs index 43fdb7c0a0e1a..fbc57d001a5ab 100644 --- a/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/HostDiagnosticUpdateSource.cs @@ -127,5 +127,10 @@ public void ClearDiagnosticsForProject(ProjectId projectId, object key) RaiseDiagnosticsRemovedForProject(projectId, key); } } + + public DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId projectId, string language) + { + return DocumentAnalysisExecutor.CreateAnalyzerLoadFailureDiagnostic(e, fullPath, projectId, language); + } } } diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index ba376178ca0d2..bcf1b4e81286e 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs @@ -23,6 +23,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; @@ -87,7 +88,7 @@ public static ContainedDocument TryGetContainedDocument(DocumentId id) private readonly HostType _hostType; private readonly ReiteratedVersionSnapshotTracker _snapshotTracker; private readonly AbstractFormattingRule _vbHelperFormattingRule; - private readonly VisualStudioProject _project; + private readonly ProjectSystemProject _project; public bool SupportsRename { get { return _hostType == HostType.Razor; } } public bool SupportsSemanticSnippets { get { return false; } } @@ -105,7 +106,7 @@ public ContainedDocument( ITextBuffer dataBuffer, IVsTextBufferCoordinator bufferCoordinator, Workspace workspace, - VisualStudioProject project, + ProjectSystemProject project, IComponentModel componentModel, AbstractFormattingRule vbHelperFormattingRule) : base(threadingContext) diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index 2ac7c7555d8c4..e8b67d2c97d42 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -31,7 +32,7 @@ internal partial class ContainedLanguage protected readonly Workspace Workspace; protected readonly IComponentModel ComponentModel; - public VisualStudioProject? Project { get; } + public ProjectSystemProject? Project { get; } protected readonly ContainedDocument ContainedDocument; @@ -71,7 +72,7 @@ internal ContainedLanguage( IComponentModel componentModel, Workspace workspace, ProjectId projectId, - VisualStudioProject? project, + ProjectSystemProject? project, Guid languageServiceGuid, AbstractFormattingRule? vbHelperFormattingRule = null) { diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs index 0f6492f48bcb9..40717cce6d206 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IProjectCodeModelProvider.cs @@ -46,7 +46,7 @@ EnvDTE.FileCodeModel ICodeModelInstanceFactory.TryCreateFileCodeModelThroughProj private EnvDTE.ProjectItem GetProjectItem(string filePath) { - var dteProject = _project._visualStudioWorkspace.TryGetDTEProject(_project._visualStudioProject.Id); + var dteProject = _project._visualStudioWorkspace.TryGetDTEProject(_project._projectSystemProject.Id); if (dteProject == null) { return null; diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs index 60600e4dad016..318bd65d6fbc7 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProject_IWorkspaceProjectContext.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; @@ -20,7 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.C { internal sealed partial class CPSProject : IWorkspaceProjectContext { - private readonly VisualStudioProject _visualStudioProject; + private readonly ProjectSystemProject _projectSystemProject; /// /// The we're using to parse command line options. Null if we don't @@ -32,24 +33,24 @@ internal sealed partial class CPSProject : IWorkspaceProjectContext private readonly IProjectCodeModel _projectCodeModel; private readonly Lazy _externalErrorReporter; - private readonly ConcurrentQueue _batchScopes = new(); + private readonly ConcurrentQueue _batchScopes = new(); public string DisplayName { - get => _visualStudioProject.DisplayName; - set => _visualStudioProject.DisplayName = value; + get => _projectSystemProject.DisplayName; + set => _projectSystemProject.DisplayName = value; } public string? ProjectFilePath { - get => _visualStudioProject.FilePath; - set => _visualStudioProject.FilePath = value; + get => _projectSystemProject.FilePath; + set => _projectSystemProject.FilePath = value; } public bool IsPrimary { - get => _visualStudioProject.IsPrimary; - set => _visualStudioProject.IsPrimary = value; + get => _projectSystemProject.IsPrimary; + set => _projectSystemProject.IsPrimary = value; } public Guid Guid @@ -60,18 +61,18 @@ public Guid Guid public bool LastDesignTimeBuildSucceeded { - get => _visualStudioProject.HasAllInformation; - set => _visualStudioProject.HasAllInformation = value; + get => _projectSystemProject.HasAllInformation; + set => _projectSystemProject.HasAllInformation = value; } - public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspaceImpl visualStudioWorkspace, IProjectCodeModelFactory projectCodeModelFactory, Guid projectGuid) + public CPSProject(ProjectSystemProject projectSystemProject, VisualStudioWorkspaceImpl visualStudioWorkspace, IProjectCodeModelFactory projectCodeModelFactory, Guid projectGuid) { - _visualStudioProject = visualStudioProject; + _projectSystemProject = projectSystemProject; _visualStudioWorkspace = visualStudioWorkspace; _externalErrorReporter = new Lazy(() => { - var prefix = visualStudioProject.Language switch + var prefix = projectSystemProject.Language switch { LanguageNames.CSharp => "CS", LanguageNames.VisualBasic => "BC", @@ -79,17 +80,17 @@ public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspace _ => null }; - return (prefix != null) ? new ProjectExternalErrorReporter(visualStudioProject.Id, prefix, visualStudioProject.Language, visualStudioWorkspace) : null; + return (prefix != null) ? new ProjectExternalErrorReporter(projectSystemProject.Id, prefix, projectSystemProject.Language, visualStudioWorkspace) : null; }); - _projectCodeModel = projectCodeModelFactory.CreateProjectCodeModel(visualStudioProject.Id, new CPSCodeModelInstanceFactory(this)); + _projectCodeModel = projectCodeModelFactory.CreateProjectCodeModel(projectSystemProject.Id, new CPSCodeModelInstanceFactory(this)); // If we have a command line parser service for this language, also set up our ability to process options if they come in - if (visualStudioWorkspace.Services.GetLanguageServices(visualStudioProject.Language).GetService() != null) + if (visualStudioWorkspace.Services.GetLanguageServices(projectSystemProject.Language).GetService() != null) { - _visualStudioProjectOptionsProcessor = new VisualStudioProjectOptionsProcessor(_visualStudioProject, visualStudioWorkspace.Services.SolutionServices); + _visualStudioProjectOptionsProcessor = new VisualStudioProjectOptionsProcessor(_projectSystemProject, visualStudioWorkspace.Services.SolutionServices); _visualStudioWorkspace.AddProjectRuleSetFileToInternalMaps( - visualStudioProject, + projectSystemProject, () => _visualStudioProjectOptionsProcessor.EffectiveRuleSetFilePath); } @@ -98,13 +99,13 @@ public CPSProject(VisualStudioProject visualStudioProject, VisualStudioWorkspace public string? BinOutputPath { - get => _visualStudioProject.OutputFilePath; + get => _projectSystemProject.OutputFilePath; set { // If we don't have a path, always set it to null if (string.IsNullOrEmpty(value)) { - _visualStudioProject.OutputFilePath = null; + _projectSystemProject.OutputFilePath = null; return; } @@ -113,26 +114,26 @@ public string? BinOutputPath // can be removed now, but we still have tests asserting it. if (!PathUtilities.IsAbsolute(value)) { - var rootDirectory = _visualStudioProject.FilePath != null - ? Path.GetDirectoryName(_visualStudioProject.FilePath) + var rootDirectory = _projectSystemProject.FilePath != null + ? Path.GetDirectoryName(_projectSystemProject.FilePath) : Path.GetTempPath(); - _visualStudioProject.OutputFilePath = Path.Combine(rootDirectory, value); + _projectSystemProject.OutputFilePath = Path.Combine(rootDirectory, value); } else { - _visualStudioProject.OutputFilePath = value; + _projectSystemProject.OutputFilePath = value; } } } internal string? CompilationOutputAssemblyFilePath { - get => _visualStudioProject.CompilationOutputAssemblyFilePath; - set => _visualStudioProject.CompilationOutputAssemblyFilePath = value; + get => _projectSystemProject.CompilationOutputAssemblyFilePath; + set => _projectSystemProject.CompilationOutputAssemblyFilePath = value; } - public ProjectId Id => _visualStudioProject.Id; + public ProjectId Id => _projectSystemProject.Id; public void SetOptions(string commandLineForOptions) => _visualStudioProjectOptionsProcessor?.SetCommandLine(commandLineForOptions); @@ -149,32 +150,32 @@ public void SetProperty(string name, string? value) // use it for their own purpose. // In the future, we might consider officially exposing "default namespace" for VB project // (e.g. through a msbuild property) - _visualStudioProject.DefaultNamespace = value; + _projectSystemProject.DefaultNamespace = value; } else if (name == BuildPropertyNames.MaxSupportedLangVersion) { - _visualStudioProject.MaxLangVersion = value; + _projectSystemProject.MaxLangVersion = value; } else if (name == BuildPropertyNames.RunAnalyzers) { var boolValue = bool.TryParse(value, out var parsedBoolValue) ? parsedBoolValue : (bool?)null; - _visualStudioProject.RunAnalyzers = boolValue; + _projectSystemProject.RunAnalyzers = boolValue; } else if (name == BuildPropertyNames.RunAnalyzersDuringLiveAnalysis) { var boolValue = bool.TryParse(value, out var parsedBoolValue) ? parsedBoolValue : (bool?)null; - _visualStudioProject.RunAnalyzersDuringLiveAnalysis = boolValue; + _projectSystemProject.RunAnalyzersDuringLiveAnalysis = boolValue; } else if (name == BuildPropertyNames.TemporaryDependencyNodeTargetIdentifier && !RoslynString.IsNullOrEmpty(value)) { - _visualStudioProject.DependencyNodeTargetIdentifier = value; + _projectSystemProject.DependencyNodeTargetIdentifier = value; } else if (name == BuildPropertyNames.TargetRefPath) { // If we don't have a path, always set it to null if (string.IsNullOrEmpty(value)) { - _visualStudioProject.OutputRefFilePath = null; + _projectSystemProject.OutputRefFilePath = null; } else { @@ -183,15 +184,15 @@ public void SetProperty(string name, string? value) // can be removed now, but we still have tests asserting it. if (!PathUtilities.IsAbsolute(value)) { - var rootDirectory = _visualStudioProject.FilePath != null - ? Path.GetDirectoryName(_visualStudioProject.FilePath) + var rootDirectory = _projectSystemProject.FilePath != null + ? Path.GetDirectoryName(_projectSystemProject.FilePath) : Path.GetTempPath(); - _visualStudioProject.OutputRefFilePath = Path.Combine(rootDirectory, value); + _projectSystemProject.OutputRefFilePath = Path.Combine(rootDirectory, value); } else { - _visualStudioProject.OutputRefFilePath = value; + _projectSystemProject.OutputRefFilePath = value; } } } @@ -200,64 +201,64 @@ public void SetProperty(string name, string? value) public void AddMetadataReference(string referencePath, MetadataReferenceProperties properties) { referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - _visualStudioProject.AddMetadataReference(referencePath, properties); + _projectSystemProject.AddMetadataReference(referencePath, properties); } public void RemoveMetadataReference(string referencePath) { referencePath = FileUtilities.NormalizeAbsolutePath(referencePath); - _visualStudioProject.RemoveMetadataReference(referencePath, _visualStudioProject.GetPropertiesForMetadataReference(referencePath).Single()); + _projectSystemProject.RemoveMetadataReference(referencePath, _projectSystemProject.GetPropertiesForMetadataReference(referencePath).Single()); } public void AddProjectReference(IWorkspaceProjectContext project, MetadataReferenceProperties properties) { - var otherProjectId = ((CPSProject)project)._visualStudioProject.Id; - _visualStudioProject.AddProjectReference(new ProjectReference(otherProjectId, properties.Aliases, properties.EmbedInteropTypes)); + var otherProjectId = ((CPSProject)project)._projectSystemProject.Id; + _projectSystemProject.AddProjectReference(new ProjectReference(otherProjectId, properties.Aliases, properties.EmbedInteropTypes)); } public void RemoveProjectReference(IWorkspaceProjectContext project) { - var otherProjectId = ((CPSProject)project)._visualStudioProject.Id; - var otherProjectReference = _visualStudioProject.GetProjectReferences().Single(pr => pr.ProjectId == otherProjectId); - _visualStudioProject.RemoveProjectReference(otherProjectReference); + var otherProjectId = ((CPSProject)project)._projectSystemProject.Id; + var otherProjectReference = _projectSystemProject.GetProjectReferences().Single(pr => pr.ProjectId == otherProjectId); + _projectSystemProject.RemoveProjectReference(otherProjectReference); } public void AddSourceFile(string filePath, bool isInCurrentContext = true, IEnumerable? folderNames = null, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) - => _visualStudioProject.AddSourceFile(filePath, sourceCodeKind, folderNames.AsImmutableOrNull()); + => _projectSystemProject.AddSourceFile(filePath, sourceCodeKind, folderNames.AsImmutableOrNull()); public void RemoveSourceFile(string filePath) { - _visualStudioProject.RemoveSourceFile(filePath); + _projectSystemProject.RemoveSourceFile(filePath); _projectCodeModel.OnSourceFileRemoved(filePath); } public void AddAdditionalFile(string filePath, bool isInCurrentContext = true) - => _visualStudioProject.AddAdditionalFile(filePath); + => _projectSystemProject.AddAdditionalFile(filePath); public void Dispose() { _projectCodeModel?.OnProjectClosed(); _visualStudioProjectOptionsProcessor?.Dispose(); - _visualStudioProject.RemoveFromWorkspace(); + _projectSystemProject.RemoveFromWorkspace(); } public void AddAnalyzerReference(string referencePath) - => _visualStudioProject.AddAnalyzerReference(referencePath); + => _projectSystemProject.AddAnalyzerReference(referencePath); public void RemoveAnalyzerReference(string referencePath) - => _visualStudioProject.RemoveAnalyzerReference(referencePath); + => _projectSystemProject.RemoveAnalyzerReference(referencePath); public void RemoveAdditionalFile(string filePath) - => _visualStudioProject.RemoveAdditionalFile(filePath); + => _projectSystemProject.RemoveAdditionalFile(filePath); public void AddDynamicFile(string filePath, IEnumerable? folderNames = null) - => _visualStudioProject.AddDynamicSourceFile(filePath, folderNames.ToImmutableArrayOrEmpty()); + => _projectSystemProject.AddDynamicSourceFile(filePath, folderNames.ToImmutableArrayOrEmpty()); public void RemoveDynamicFile(string filePath) - => _visualStudioProject.RemoveDynamicSourceFile(filePath); + => _projectSystemProject.RemoveDynamicSourceFile(filePath); public void StartBatch() - => _batchScopes.Enqueue(_visualStudioProject.CreateBatchScope()); + => _batchScopes.Enqueue(_projectSystemProject.CreateBatchScope()); public ValueTask EndBatchAsync() { @@ -266,17 +267,17 @@ public ValueTask EndBatchAsync() } public void ReorderSourceFiles(IEnumerable? filePaths) - => _visualStudioProject.ReorderSourceFiles(filePaths.ToImmutableArrayOrEmpty()); + => _projectSystemProject.ReorderSourceFiles(filePaths.ToImmutableArrayOrEmpty()); - internal VisualStudioProject GetProject_TestOnly() - => _visualStudioProject; + internal ProjectSystemProject GetProject_TestOnly() + => _projectSystemProject; public void AddAnalyzerConfigFile(string filePath) - => _visualStudioProject.AddAnalyzerConfigFile(filePath); + => _projectSystemProject.AddAnalyzerConfigFile(filePath); public void RemoveAnalyzerConfigFile(string filePath) - => _visualStudioProject.RemoveAnalyzerConfigFile(filePath); + => _projectSystemProject.RemoveAnalyzerConfigFile(filePath); - public IAsyncDisposable CreateBatchScope() => _visualStudioProject.CreateBatchScope(); + public IAsyncDisposable CreateBatchScope() => _projectSystemProject.CreateBatchScope(); } } diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb index d5d79081d415c..72a6637cb94aa 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb @@ -212,7 +212,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal("C:\test.dll", project.GetOutputFileName()) - Assert.Equal("C:\test.dll", project.Test_VisualStudioProject.CompilationOutputAssemblyFilePath) + Assert.Equal("C:\test.dll", project.Test_ProjectSystemProject.CompilationOutputAssemblyFilePath) ' Change output folder from command line arguments - verify that objOutputPath changes. Dim newPath = "C:\NewFolder\test.dll" @@ -222,7 +222,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) - Assert.Equal("C:\NewFolder\test.dll", project.Test_VisualStudioProject.CompilationOutputAssemblyFilePath) + Assert.Equal("C:\NewFolder\test.dll", project.Test_ProjectSystemProject.CompilationOutputAssemblyFilePath) ' Change output file name - verify that outputPath changes. newPath = "C:\NewFolder\test2.dll" @@ -232,7 +232,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) - Assert.Equal("C:\NewFolder\test2.dll", project.Test_VisualStudioProject.CompilationOutputAssemblyFilePath) + Assert.Equal("C:\NewFolder\test2.dll", project.Test_ProjectSystemProject.CompilationOutputAssemblyFilePath) ' Change output file name and folder - verify that outputPath changes. newPath = "C:\NewFolder3\test3.dll" @@ -242,7 +242,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) - Assert.Equal("C:\NewFolder3\test3.dll", project.Test_VisualStudioProject.CompilationOutputAssemblyFilePath) + Assert.Equal("C:\NewFolder3\test3.dll", project.Test_ProjectSystemProject.CompilationOutputAssemblyFilePath) ' Relative path - set by VBIntelliProj in VB Web App project compilerOptions = CreateMinimalCompilerOptions(project) @@ -250,7 +250,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim compilerOptions.wszExeName = "test3.dll" project.SetCompilerOptions(compilerOptions) Assert.Equal(Nothing, project.GetOutputFileName()) - Assert.Equal(Nothing, project.Test_VisualStudioProject.CompilationOutputAssemblyFilePath) + Assert.Equal(Nothing, project.Test_ProjectSystemProject.CompilationOutputAssemblyFilePath) End Using End Sub End Class diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb index 49a6613bca16f..5a705c1da70df 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Workspaces.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList Imports Roslyn.Test.Utilities @@ -34,7 +35,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim registrationService = Assert.IsType(Of MockDiagnosticUpdateSourceRegistrationService)(workspace.GetService(Of IDiagnosticUpdateSourceRegistrationService)()) Dim hostDiagnosticUpdateSource = New HostDiagnosticUpdateSource(lazyWorkspace, registrationService) - Using tempRoot = New TempRoot(), analyzer = New VisualStudioAnalyzer(tempRoot.CreateFile().Path, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) + Using tempRoot = New TempRoot(), analyzer = New ProjectAnalyzerReference(tempRoot.CreateFile().Path, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) Dim reference1 = analyzer.GetReference() Dim reference2 = analyzer.GetReference() @@ -59,7 +60,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim eventHandler = New EventHandlers(file) AddHandler hostDiagnosticUpdateSource.DiagnosticsUpdated, AddressOf eventHandler.DiagnosticAddedTest - Using analyzer = New VisualStudioAnalyzer(file, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) + Using analyzer = New ProjectAnalyzerReference(file, hostDiagnosticUpdateSource, ProjectId.CreateNewId(), LanguageNames.VisualBasic) Dim reference = analyzer.GetReference() reference.GetAnalyzers(LanguageNames.VisualBasic) diff --git a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb index 359a52208be30..84cc9edacb87b 100644 --- a/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb +++ b/src/VisualStudio/VisualBasic/Impl/LanguageService/VisualBasicLanguageService.vb @@ -5,6 +5,7 @@ Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor +Imports Microsoft.CodeAnalysis.Workspaces.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense Imports Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -73,7 +74,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic Protected Overrides Function CreateContainedLanguage( bufferCoordinator As IVsTextBufferCoordinator, - project As VisualStudioProject, + project As ProjectSystemProject, hierarchy As IVsHierarchy, itemid As UInteger ) As IVsContainedLanguage diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb index 02531ca5caeb1..f39475138216e 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicCodeModelInstanceFactory.vb @@ -31,7 +31,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim ' with the correct "parent" object. ' ' Because the VB project system lacks these hooks, we simulate the same operations that those hooks perform. - Dim document = _project.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(Function(d) d.ProjectId Is _project.VisualStudioProject.Id) + Dim document = _project.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(Function(d) d.ProjectId Is _project.ProjectSystemProject.Id) If document Is Nothing Then Throw New ArgumentException(NameOf(filePath)) diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb index 78f53dad6af12..7af845638846a 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb @@ -9,6 +9,7 @@ Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.Workspaces.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop @@ -40,7 +41,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim ''' Private Shared ReadOnly s_importsCache As Dictionary(Of String, GlobalImport) = New Dictionary(Of String, GlobalImport) - Public Sub New(project As VisualStudioProject, workspaceServices As SolutionServices) + Public Sub New(project As ProjectSystemProject, workspaceServices As SolutionServices) MyBase.New(project, workspaceServices) End Sub diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb index 4ab6f39c5bd0e..6ad885a7cdec2 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb @@ -48,8 +48,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Dim componentModel = DirectCast(serviceProvider.GetService(GetType(SComponentModel)), IComponentModel) - ProjectCodeModel = componentModel.GetService(Of IProjectCodeModelFactory).CreateProjectCodeModel(VisualStudioProject.Id, New VisualBasicCodeModelInstanceFactory(Me)) - VisualStudioProjectOptionsProcessor = New OptionsProcessor(VisualStudioProject, Workspace.Services.SolutionServices) + ProjectCodeModel = componentModel.GetService(Of IProjectCodeModelFactory).CreateProjectCodeModel(ProjectSystemProject.Id, New VisualBasicCodeModelInstanceFactory(Me)) + VisualStudioProjectOptionsProcessor = New OptionsProcessor(ProjectSystemProject, Workspace.Services.SolutionServices) End Sub Private Shadows Property VisualStudioProjectOptionsProcessor As OptionsProcessor @@ -70,7 +70,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Sub Public Function AddEmbeddedMetaDataReference(wszFileName As String) As Integer Implements IVbCompilerProject.AddEmbeddedMetaDataReference - VisualStudioProject.AddMetadataReference(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True)) + ProjectSystemProject.AddMetadataReference(wszFileName, New MetadataReferenceProperties(embedInteropTypes:=True)) Return VSConstants.S_OK End Function @@ -80,7 +80,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim _explicitlyAddedRuntimeLibraries.Add(wszFileName) Return VSConstants.S_OK Else - VisualStudioProject.AddMetadataReference(wszFileName, MetadataReferenceProperties.Assembly) + ProjectSystemProject.AddMetadataReference(wszFileName, MetadataReferenceProperties.Assembly) Return VSConstants.S_OK End If End Function @@ -94,7 +94,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) End If - VisualStudioProject.AddProjectReference(New ProjectReference(referencedProject.VisualStudioProject.Id, embedInteropTypes:=True)) + ProjectSystemProject.AddProjectReference(New ProjectReference(referencedProject.ProjectSystemProject.Id, embedInteropTypes:=True)) End Sub Public Shadows Sub AddFile(wszFileName As String, itemid As UInteger, fAddDuringOpen As Boolean) Implements IVbCompilerProject.AddFile @@ -114,7 +114,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) End If - VisualStudioProject.AddProjectReference(New ProjectReference(referencedProject.VisualStudioProject.Id)) + ProjectSystemProject.AddProjectReference(New ProjectReference(referencedProject.ProjectSystemProject.Id)) End Sub Public Sub AddResourceReference(wszFileName As String, wszName As String, fPublic As Boolean, fEmbed As Boolean) Implements IVbCompilerProject.AddResourceReference @@ -198,7 +198,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim End Function Public Sub GetEntryPointsList(cItems As Integer, strList() As String, ByVal pcActualItems As IntPtr) Implements IVbCompilerProject.GetEntryPointsList - Dim project = Workspace.CurrentSolution.GetProject(VisualStudioProject.Id) + Dim project = Workspace.CurrentSolution.GetProject(ProjectSystemProject.Id) Dim compilation = project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None) GetEntryPointsWorker(compilation, cItems, strList, pcActualItems, findFormsOnly:=False) @@ -270,7 +270,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Return End If - VisualStudioProject.RemoveMetadataReference(wszFileName, VisualStudioProject.GetPropertiesForMetadataReference(wszFileName).Single()) + ProjectSystemProject.RemoveMetadataReference(wszFileName, ProjectSystemProject.GetPropertiesForMetadataReference(wszFileName).Single()) End Sub Public Shadows Sub RemoveProjectReference(pReferencedCompilerProject As IVbCompilerProject) Implements IVbCompilerProject.RemoveProjectReference @@ -282,8 +282,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim Throw New ArgumentException("Unknown type of IVbCompilerProject.", NameOf(pReferencedCompilerProject)) End If - Dim projectReference = VisualStudioProject.GetProjectReferences().Single(Function(p) p.ProjectId = referencedProject.VisualStudioProject.Id) - VisualStudioProject.RemoveProjectReference(projectReference) + Dim projectReference = ProjectSystemProject.GetProjectReferences().Single(Function(p) p.ProjectId = referencedProject.ProjectSystemProject.Id) + ProjectSystemProject.RemoveProjectReference(projectReference) End Sub Public Sub RenameDefaultNamespace(bstrDefaultNamespace As String) Implements IVbCompilerProject.RenameDefaultNamespace @@ -315,16 +315,16 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim VisualStudioProjectOptionsProcessor.SetNewRawOptions(pCompilerOptions) If Not String.IsNullOrEmpty(pCompilerOptions.wszExeName) Then - VisualStudioProject.AssemblyName = Path.GetFileNameWithoutExtension(pCompilerOptions.wszExeName) + ProjectSystemProject.AssemblyName = Path.GetFileNameWithoutExtension(pCompilerOptions.wszExeName) ' Some legacy projects (e.g. Venus IntelliSense project) set '\' as the wszOutputPath. ' /src/venus/project/vb/vbprj/vbintelliproj.cpp ' Ignore paths that are not absolute. If Not String.IsNullOrEmpty(pCompilerOptions.wszOutputPath) Then If PathUtilities.IsAbsolute(pCompilerOptions.wszOutputPath) Then - VisualStudioProject.CompilationOutputAssemblyFilePath = Path.Combine(pCompilerOptions.wszOutputPath, pCompilerOptions.wszExeName) + ProjectSystemProject.CompilationOutputAssemblyFilePath = Path.Combine(pCompilerOptions.wszOutputPath, pCompilerOptions.wszExeName) Else - VisualStudioProject.CompilationOutputAssemblyFilePath = Nothing + ProjectSystemProject.CompilationOutputAssemblyFilePath = Nothing End If End If End If @@ -334,14 +334,14 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim _runtimeLibraries = VisualStudioProjectOptionsProcessor.GetRuntimeLibraries(_compilerHost) If Not _runtimeLibraries.SequenceEqual(oldRuntimeLibraries, StringComparer.Ordinal) Then - Using batchScope = VisualStudioProject.CreateBatchScope() + Using batchScope = ProjectSystemProject.CreateBatchScope() ' To keep things simple, we'll just remove everything and add everything back in For Each oldRuntimeLibrary In oldRuntimeLibraries ' If this one was added explicitly in addition to our computation, we don't have to remove it If _explicitlyAddedRuntimeLibraries.Contains(oldRuntimeLibrary) Then _explicitlyAddedRuntimeLibraries.Remove(oldRuntimeLibrary) Else - VisualStudioProject.RemoveMetadataReference(oldRuntimeLibrary, MetadataReferenceProperties.Assembly) + ProjectSystemProject.RemoveMetadataReference(oldRuntimeLibrary, MetadataReferenceProperties.Assembly) End If Next @@ -349,10 +349,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim newRuntimeLibrary = FileUtilities.NormalizeAbsolutePath(newRuntimeLibrary) ' If we already reference this, just skip it - If VisualStudioProject.ContainsMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) Then + If ProjectSystemProject.ContainsMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) Then _explicitlyAddedRuntimeLibraries.Add(newRuntimeLibrary) Else - VisualStudioProject.AddMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) + ProjectSystemProject.AddMetadataReference(newRuntimeLibrary, MetadataReferenceProperties.Assembly) End If Next End Using diff --git a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb index e8657fae010a0..bff16aebe7dc0 100644 --- a/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb +++ b/src/VisualStudio/VisualBasic/Impl/Venus/VisualBasicContainedLanguage.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.Workspaces.ProjectSystem Imports Microsoft.VisualStudio.ComponentModelHost Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.Venus @@ -26,7 +27,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus Public Sub New(bufferCoordinator As IVsTextBufferCoordinator, componentModel As IComponentModel, - project As VisualStudioProject, + project As ProjectSystemProject, hierarchy As IVsHierarchy, itemid As UInteger, languageServiceGuid As Guid) @@ -40,7 +41,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Venus Public Sub New(bufferCoordinator As IVsTextBufferCoordinator, componentModel As IComponentModel, - project As VisualStudioProject, + project As ProjectSystemProject, languageServiceGuid As Guid) MyBase.New( diff --git a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs index 4e2281b555d67..cb38bacce4f12 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/XamlProjectService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.CodeAnalysis.Xaml.Diagnostics.Analyzers; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -33,7 +34,7 @@ internal sealed partial class XamlProjectService private readonly VisualStudioProjectFactory _visualStudioProjectFactory; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly IThreadingContext _threadingContext; - private readonly Dictionary _xamlProjects = new(); + private readonly Dictionary _xamlProjects = new(); private readonly ConcurrentDictionary _documentIds = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private RunningDocumentTable? _rdt; diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index fb91dd96d6348..a1f2adc2b3103 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -147,6 +147,9 @@ + + + diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs index 5eba4548b6aad..45beadea5fcf1 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IProjectSystemDiagnosticSource.cs @@ -14,6 +14,7 @@ internal interface IProjectSystemDiagnosticSource void ClearAllDiagnosticsForProject(ProjectId projectId); void ClearAnalyzerReferenceDiagnostics(AnalyzerFileReference fileReference, string language, ProjectId projectId); void ClearDiagnosticsForProject(ProjectId projectId, object key); + DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId projectId, string language); void UpdateDiagnosticsForProject(ProjectId projectId, object key, IEnumerable items); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs similarity index 92% rename from src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs rename to src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs index bc883d15d3890..c540972705c76 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.BatchingDocumentCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs @@ -17,19 +17,19 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem { - internal sealed partial class VisualStudioProject + internal sealed partial class ProjectSystemProject { /// /// Helper class to manage collections of source-file like things; this exists just to avoid duplicating all the logic for regular source files /// and additional files. /// - /// This class should be free-threaded, and any synchronization is done via . + /// This class should be free-threaded, and any synchronization is done via . /// This class is otherwise free to operate on private members of if needed. private sealed class BatchingDocumentCollection { - private readonly VisualStudioProject _project; + private readonly ProjectSystemProject _project; /// /// The map of file paths to the underlying . This document may exist in or has been @@ -69,7 +69,7 @@ private sealed class BatchingDocumentCollection private readonly Func _documentTextLoaderChangedAction; private readonly WorkspaceChangeKind _documentChangedWorkspaceKind; - public BatchingDocumentCollection(VisualStudioProject project, + public BatchingDocumentCollection(ProjectSystemProject project, Func documentAlreadyInWorkspace, Action documentAddAction, Action documentRemoveAction, @@ -92,7 +92,7 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta } var documentId = DocumentId.CreateNewId(_project.Id, fullPath); - var textLoader = new WorkspaceFileTextLoader(_project._workspace.Services.SolutionServices, fullPath, defaultEncoding: null); + var textLoader = new WorkspaceFileTextLoader(_project._projectSystemProjectFactory.Workspace.Services.SolutionServices, fullPath, defaultEncoding: null); var documentInfo = DocumentInfo.Create( documentId, name: FileNameUtilities.GetFileName(fullPath), @@ -120,8 +120,8 @@ public DocumentId AddFile(string fullPath, SourceCodeKind sourceCodeKind, Immuta } else { - _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); - _project._workspace.QueueCheckForFilesBeingOpen(ImmutableArray.Create(fullPath)); + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); + _project._projectSystemProjectFactory.RaiseOnDocumentsAdded(ImmutableArray.Create(fullPath)); } } @@ -178,9 +178,9 @@ public DocumentId AddTextContainer( } else { - _project._workspace.ApplyChangeToWorkspace(w => + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => { - _project._workspace.AddDocumentToDocumentsNotFromFiles_NoLock(documentInfo.Id); + _project._projectSystemProjectFactory.AddDocumentToDocumentsNotFromFiles_NoLock(documentInfo.Id); _documentAddAction(w, documentInfo); w.OnDocumentOpened(documentInfo.Id, textContainer); }); @@ -226,7 +226,7 @@ public void AddDynamicFile_NoLock(IDynamicFileInfoProvider fileInfoProvider, Dyn else { // right now, assumption is dynamically generated file can never be opened in editor - _project._workspace.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => _documentAddAction(w, documentInfo)); } } @@ -283,7 +283,7 @@ private void RemoveFileInternal(DocumentId documentId, string fullPath) // 1. This file is actually been pushed to the workspace, and we need to remove it (either // as a part of the active batch or immediately) // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch - if (_documentAlreadyInWorkspace(_project._workspace.CurrentSolution, documentId)) + if (_documentAlreadyInWorkspace(_project._projectSystemProjectFactory.Workspace.CurrentSolution, documentId)) { if (_project._activeBatchScopes > 0) { @@ -291,7 +291,7 @@ private void RemoveFileInternal(DocumentId documentId, string fullPath) } else { - _project._workspace.ApplyChangeToWorkspace(w => _documentRemoveAction(w, documentId)); + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => _documentRemoveAction(w, documentId)); } } else @@ -335,7 +335,7 @@ public void RemoveTextContainer(SourceTextContainer textContainer) // 1. This file is actually been pushed to the workspace, and we need to remove it (either // as a part of the active batch or immediately) // 2. It hasn't been pushed yet, but is contained in _documentsAddedInBatch - if (_project._workspace.CurrentSolution.GetDocument(documentId) != null) + if (_project._projectSystemProjectFactory.Workspace.CurrentSolution.GetDocument(documentId) != null) { if (_project._activeBatchScopes > 0) { @@ -343,7 +343,7 @@ public void RemoveTextContainer(SourceTextContainer textContainer) } else { - _project._workspace.ApplyChangeToWorkspace(w => + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => { // Just pass null for the filePath, since this document is immediately being removed // anyways -- whatever we set won't really be read since the next change will @@ -351,7 +351,7 @@ public void RemoveTextContainer(SourceTextContainer textContainer) // TODO: Can't we just remove the document without closing it? w.OnDocumentClosed(documentId, new SourceTextLoader(textContainer, filePath: null)); _documentRemoveAction(w, documentId); - _project._workspace.RemoveDocumentToDocumentsNotFromFiles_NoLock(documentId); + _project._projectSystemProjectFactory.RemoveDocumentToDocumentsNotFromFiles_NoLock(documentId); }); } } @@ -405,7 +405,7 @@ public async ValueTask ProcessRegularFileChangesAsync(ImmutableSegmentedList d.Id == documentId)) { - documentsToChange.Add((documentId, new WorkspaceFileTextLoader(_project._workspace.Services.SolutionServices, filePath, defaultEncoding: null))); + documentsToChange.Add((documentId, new WorkspaceFileTextLoader(_project._projectSystemProjectFactory.Workspace.Services.SolutionServices, filePath, defaultEncoding: null))); } } } @@ -416,11 +416,11 @@ public async ValueTask ProcessRegularFileChangesAsync(ImmutableSegmentedList + await _project._projectSystemProjectFactory.ApplyBatchChangeToWorkspaceAsync(solutionChanges => { foreach (var (documentId, textLoader) in documentsToChange) { - if (!_project._workspace.IsDocumentOpen(documentId)) + if (!_project._projectSystemProjectFactory.Workspace.IsDocumentOpen(documentId)) { solutionChanges.UpdateSolutionForDocumentAction( _documentTextLoaderChangedAction(solutionChanges.Solution, documentId, textLoader), @@ -463,7 +463,7 @@ public void ProcessDynamicFileChange(string projectSystemFilePath, string worksp Contract.ThrowIfFalse(_documentIdToDynamicFileInfoProvider.TryGetValue(documentId, out var fileInfoProvider)); - _project._workspace.ApplyChangeToWorkspace(w => + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(w => { if (w.IsDocumentOpen(documentId)) { @@ -521,7 +521,7 @@ public void ReorderFiles(ImmutableArray filePaths) } else { - _project._workspace.ApplyChangeToWorkspace(_project.Id, solution => solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); + _project._projectSystemProjectFactory.ApplyChangeToWorkspace(_project.Id, solution => solution.WithProjectDocumentsOrder(_project.Id, documentIds.ToImmutable())); } } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs similarity index 89% rename from src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs rename to src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 46a785aee26e7..12c2767ad25be 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -21,26 +21,17 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; -using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Roslyn.Utilities; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem { - internal sealed partial class VisualStudioProject + internal sealed partial class ProjectSystemProject { private static readonly char[] s_directorySeparator = { Path.DirectorySeparatorChar }; private static readonly ImmutableArray s_defaultMetadataReferenceProperties = ImmutableArray.Create(default(MetadataReferenceProperties)); - private readonly VisualStudioWorkspaceImpl _workspace; - private readonly IProjectSystemDiagnosticSource _projectSystemDiagnosticSource; - private readonly IHostDiagnosticAnalyzerProvider _hostAnalyzerProvider; - - /// - /// Provides dynamic source files for files added through . - /// - private readonly ImmutableArray> _dynamicFileInfoProviders; + private readonly ProjectSystemProjectFactory _projectSystemProjectFactory; + private readonly ProjectSystemHostInfo _hostInfo; /// /// A semaphore taken for all mutation of any mutable field in this type. @@ -59,14 +50,14 @@ internal sealed partial class VisualStudioProject private readonly List _projectReferencesAddedInBatch = new(); private readonly List _projectReferencesRemovedInBatch = new(); - private readonly Dictionary _analyzerPathsToAnalyzers = new(); - private readonly List _analyzersAddedInBatch = new(); + private readonly Dictionary _analyzerPathsToAnalyzers = new(); + private readonly List _analyzersAddedInBatch = new(); /// - /// The list of that will be removed in this batch. They have not yet + /// The list of that will be removed in this batch. They have not yet /// been disposed, and will be disposed once the batch is applied. /// - private readonly List _analyzersRemovedInBatch = new(); + private readonly List _analyzersRemovedInBatch = new(); private readonly List> _projectPropertyModificationsInBatch = new(); @@ -148,11 +139,9 @@ internal sealed partial class VisualStudioProject public ProjectId Id { get; } public string Language { get; } - internal VisualStudioProject( - VisualStudioWorkspaceImpl workspace, - ImmutableArray> dynamicFileInfoProviders, - IProjectSystemDiagnosticSource projectSystemDiagnosticSource, - IHostDiagnosticAnalyzerProvider hostAnalyzerProvider, + internal ProjectSystemProject( + ProjectSystemProjectFactory projectSystemProjectFactory, + ProjectSystemHostInfo hostInfo, ProjectId id, string displayName, string language, @@ -161,10 +150,8 @@ internal VisualStudioProject( string? filePath, ParseOptions? parseOptions) { - _workspace = workspace; - _dynamicFileInfoProviders = dynamicFileInfoProviders; - _projectSystemDiagnosticSource = projectSystemDiagnosticSource; - _hostAnalyzerProvider = hostAnalyzerProvider; + _projectSystemProjectFactory = projectSystemProjectFactory; + _hostInfo = hostInfo; Id = id; Language = language; @@ -196,7 +183,7 @@ internal VisualStudioProject( TimeSpan.FromMilliseconds(200), // 200 chosen with absolutely no evidence whatsoever ProcessFileChangesAsync, StringComparer.Ordinal, - workspace.Services.GetRequiredService().GetListener(), + _projectSystemProjectFactory.WorkspaceListener, _asynchronousFileChangeProcessingCancellationTokenSource.Token); _assemblyName = assemblyName; @@ -210,12 +197,12 @@ internal VisualStudioProject( { // Since we have a project directory, we'll just watch all the files under that path; that'll avoid extra overhead of // having to add explicit file watches everywhere. - var projectDirectoryToWatch = new WatchedDirectory(Path.GetDirectoryName(filePath), fileExtensionToWatch); - _documentFileChangeContext = _workspace.FileChangeWatcher.CreateContext(projectDirectoryToWatch); + var projectDirectoryToWatch = new WatchedDirectory(Path.GetDirectoryName(filePath)!, fileExtensionToWatch); + _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(projectDirectoryToWatch); } else { - _documentFileChangeContext = workspace.FileChangeWatcher.CreateContext(); + _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(); } _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; @@ -246,11 +233,11 @@ private void ChangeProjectProperty(ref T field, T newValue, Action(); + var telemetryService = _projectSystemProjectFactory.Workspace.Services.GetService(); if (telemetryService?.HasActiveSession == true) { - var workspaceStatusService = _workspace.Services.GetService(); + var workspaceStatusService = _projectSystemProjectFactory.Workspace.Services.GetService(); // We only log telemetry during solution open @@ -264,7 +251,7 @@ private void ChangeProjectProperty(ref T field, T newValue, Action(ref T field, T newValue, Action + _projectSystemProjectFactory.ApplyBatchChangeToWorkspace(solutionChanges => { updateSolution(solutionChanges, oldValue); }); @@ -324,12 +311,12 @@ private void ChangeProjectOutputPath(ref string? field, string? newValue, Func internal string? MaxLangVersion { - set => _workspace.SetMaxLanguageVersion(Id, value); + set => _projectSystemProjectFactory.SetMaxLanguageVersion(Id, value); } internal string DependencyNodeTargetIdentifier { - set => _workspace.SetDependencyNodeTargetIdentifier(Id, value); + set => _projectSystemProjectFactory.SetDependencyNodeTargetIdentifier(Id, value); } - private bool HasBeenRemoved => !_workspace.CurrentSolution.ContainsProject(Id); + private bool HasBeenRemoved => !_projectSystemProjectFactory.Workspace.CurrentSolution.ContainsProject(Id); #region Batching @@ -481,14 +468,14 @@ public BatchScope CreateBatchScope() public sealed class BatchScope : IDisposable, IAsyncDisposable { - private readonly VisualStudioProject _project; + private readonly ProjectSystemProject _project; /// /// Flag to control if this has already been disposed. Not a boolean only so it can be used with Interlocked.CompareExchange. /// private volatile int _disposed = 0; - internal BatchScope(VisualStudioProject visualStudioProject) + internal BatchScope(ProjectSystemProject visualStudioProject) => _project = visualStudioProject; public void Dispose() @@ -530,7 +517,7 @@ private async Task OnBatchScopeDisposedMaybeAsync(bool useAsync) var additionalDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); var analyzerConfigDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); - await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges => + await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges => { _sourceFiles.UpdateSolutionForBatch( solutionChanges, @@ -572,7 +559,7 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges // a different output path (say bin vs. obj vs. ref). foreach (var (path, properties) in _metadataReferencesRemovedInBatch) { - var projectReference = _workspace.TryRemoveConvertedProjectReference_NoLock(Id, path, properties); + var projectReference = _projectSystemProjectFactory.TryRemoveConvertedProjectReference_NoLock(Id, path, properties); if (projectReference != null) { @@ -583,10 +570,10 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges else { // TODO: find a cleaner way to fetch this - var metadataReference = _workspace.CurrentSolution.GetRequiredProject(Id).MetadataReferences.Cast() + var metadataReference = _projectSystemProjectFactory.Workspace.CurrentSolution.GetRequiredProject(Id).MetadataReferences.Cast() .Single(m => m.FilePath == path && m.Properties == properties); - _workspace.FileWatchedReferenceFactory.StopWatchingReference(metadataReference); + _projectSystemProjectFactory.FileWatchedReferenceFactory.StopWatchingReference(metadataReference); solutionChanges.UpdateSolutionForProjectAction( Id, @@ -604,7 +591,7 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges foreach (var (path, properties) in _metadataReferencesAddedInBatch) { - var projectReference = _workspace.TryCreateConvertedProjectReference_NoLock(Id, path, properties); + var projectReference = _projectSystemProjectFactory.TryCreateConvertedProjectReference_NoLock(Id, path, properties); if (projectReference != null) { @@ -612,7 +599,7 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges } else { - var metadataReference = _workspace.FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile(path, properties); + var metadataReference = _projectSystemProjectFactory.FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile(path, properties); metadataReferencesCreated.Add(metadataReference); } } @@ -670,21 +657,21 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges foreach (var (documentId, textContainer) in documentsToOpen) { - await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnDocumentOpened(documentId, textContainer)).ConfigureAwait(false); + await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } foreach (var (documentId, textContainer) in additionalDocumentsToOpen) { - await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAdditionalDocumentOpened(documentId, textContainer)).ConfigureAwait(false); + await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAdditionalDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } foreach (var (documentId, textContainer) in analyzerConfigDocumentsToOpen) { - await _workspace.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)).ConfigureAwait(false); + await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)).ConfigureAwait(false); } - // Check for those files being opened to start wire-up if necessary - _workspace.QueueCheckForFilesBeingOpen(documentFileNamesAdded.ToImmutable()); + // Give the host the opportunity to check if those files are open + _projectSystemProjectFactory.RaiseOnDocumentsAdded(documentFileNamesAdded.ToImmutable()); } } @@ -770,7 +757,7 @@ public void AddDynamicSourceFile(string dynamicFilePath, ImmutableArray } else { - foreach (var provider in _dynamicFileInfoProviders) + foreach (var provider in _hostInfo.DynamicFileInfoProviders) { // skip unrelated providers if (!provider.Metadata.Extensions.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase))) @@ -864,7 +851,7 @@ public void RemoveDynamicSourceFile(string dynamicFilePath) projectId: Id, projectFilePath: _filePath, filePath: dynamicFilePath, CancellationToken.None).Wait(CancellationToken.None); } - private void OnDynamicFileInfoUpdated(object sender, string dynamicFilePath) + private void OnDynamicFileInfoUpdated(object? sender, string dynamicFilePath) { string? fileInfoPath; @@ -918,9 +905,9 @@ public void AddAnalyzerReference(string fullPath) else { // Nope, we actually need to make a new one. - var visualStudioAnalyzer = new VisualStudioAnalyzer( + var visualStudioAnalyzer = new ProjectAnalyzerReference( mappedFullPath, - _projectSystemDiagnosticSource, + _hostInfo.DiagnosticSource, Id, Language); @@ -932,7 +919,7 @@ public void AddAnalyzerReference(string fullPath) } else { - _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, visualStudioAnalyzer.GetReference())); + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, visualStudioAnalyzer.GetReference())); } } } @@ -981,7 +968,7 @@ public void RemoveAnalyzerReference(string fullPath) } else { - _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference())); + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference())); visualStudioAnalyzer.Dispose(); } @@ -1000,7 +987,7 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) if (fullPath.LastIndexOf(s_razorSourceGeneratorSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_razorSourceGeneratorSdkDirectory.Length - 1 == fullPath.LastIndexOf(Path.DirectorySeparatorChar)) { - var vsixRazorAnalyzers = _hostAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( + var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( predicate: item => item.extensionId == RazorVsixExtensionId, selector: item => item.reference.FullPath); @@ -1020,7 +1007,7 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) #endregion - private void DocumentFileChangeContext_FileChanged(object sender, string fullFilePath) + private void DocumentFileChangeContext_FileChanged(object? sender, string fullFilePath) { _fileChangesToProcess.AddWork(fullFilePath); } @@ -1059,9 +1046,9 @@ public void AddMetadataReference(string fullPath, MetadataReferenceProperties pr } else { - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { - var projectReference = _workspace.TryCreateConvertedProjectReference_NoLock(Id, fullPath, properties); + var projectReference = _projectSystemProjectFactory.TryCreateConvertedProjectReference_NoLock(Id, fullPath, properties); if (projectReference != null) { @@ -1069,7 +1056,7 @@ public void AddMetadataReference(string fullPath, MetadataReferenceProperties pr } else { - var metadataReference = _workspace.FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile(fullPath, properties); + var metadataReference = _projectSystemProjectFactory.FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile(fullPath, properties); w.OnMetadataReferenceAdded(Id, metadataReference); } }); @@ -1129,9 +1116,9 @@ public void RemoveMetadataReference(string fullPath, MetadataReferenceProperties } else { - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { - var projectReference = _workspace.TryRemoveConvertedProjectReference_NoLock(Id, fullPath, properties); + var projectReference = _projectSystemProjectFactory.TryRemoveConvertedProjectReference_NoLock(Id, fullPath, properties); // If this was converted to a project reference, we have now recorded the removal -- let's remove it here too if (projectReference != null) @@ -1144,7 +1131,7 @@ public void RemoveMetadataReference(string fullPath, MetadataReferenceProperties var metadataReference = w.CurrentSolution.GetRequiredProject(Id).MetadataReferences.Cast() .Single(m => m.FilePath == fullPath && m.Properties == properties); - _workspace.FileWatchedReferenceFactory.StopWatchingReference(metadataReference); + _projectSystemProjectFactory.FileWatchedReferenceFactory.StopWatchingReference(metadataReference); w.OnMetadataReferenceRemoved(Id, metadataReference); } }); @@ -1179,7 +1166,7 @@ public void AddProjectReference(ProjectReference projectReference) } else { - _workspace.ApplyChangeToWorkspace(w => w.OnProjectReferenceAdded(Id, projectReference)); + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnProjectReferenceAdded(Id, projectReference)); } } } @@ -1211,7 +1198,7 @@ private bool ContainsProjectReference_NoLock(ProjectReference projectReference) return true; } - return _workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences.Contains(projectReference); + return _projectSystemProjectFactory.Workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences.Contains(projectReference); } public IReadOnlyList GetProjectReferences() @@ -1219,7 +1206,7 @@ public IReadOnlyList GetProjectReferences() using (_gate.DisposableWait()) { // If we're not batching, then this is cheap: just fetch from the workspace and we're done - var projectReferencesInWorkspace = _workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences; + var projectReferencesInWorkspace = _projectSystemProjectFactory.Workspace.CurrentSolution.GetRequiredProject(Id).AllProjectReferences; if (_activeBatchScopes == 0) { @@ -1258,7 +1245,7 @@ public void RemoveProjectReference(ProjectReference projectReference) } else { - _workspace.ApplyChangeToWorkspace(w => w.OnProjectReferenceRemoved(Id, projectReference)); + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnProjectReferenceRemoved(Id, projectReference)); } } } @@ -1269,7 +1256,7 @@ public void RemoveFromWorkspace() { using (_gate.DisposableWait()) { - if (!_workspace.CurrentSolution.ContainsProject(Id)) + if (!_projectSystemProjectFactory.Workspace.CurrentSolution.ContainsProject(Id)) { throw new InvalidOperationException("The project has already been removed."); } @@ -1289,23 +1276,23 @@ public void RemoveFromWorkspace() IReadOnlyList? remainingMetadataReferences = null; - _workspace.ApplyChangeToWorkspace(w => + _projectSystemProjectFactory.ApplyChangeToWorkspace(w => { // Acquire the remaining metadata references inside the workspace lock. This is critical // as another project being removed at the same time could result in project to project // references being converted to metadata references (or vice versa) and we might either // miss stopping a file watcher or might end up double-stopping a file watcher. remainingMetadataReferences = w.CurrentSolution.GetRequiredProject(Id).MetadataReferences; - _workspace.RemoveProjectFromTrackingMaps_NoLock(Id); + _projectSystemProjectFactory.RemoveProjectFromTrackingMaps_NoLock(Id); // If this is our last project, clear the entire solution. if (w.CurrentSolution.ProjectIds.Count == 1) { - _workspace.RemoveSolution_NoLock(); + _projectSystemProjectFactory.RemoveSolution_NoLock(); } else { - _workspace.OnProjectRemoved(Id); + _projectSystemProjectFactory.Workspace.OnProjectRemoved(Id); } }); @@ -1313,7 +1300,7 @@ public void RemoveFromWorkspace() foreach (PortableExecutableReference reference in remainingMetadataReferences) { - _workspace.FileWatchedReferenceFactory.StopWatchingReference(reference); + _projectSystemProjectFactory.FileWatchedReferenceFactory.StopWatchingReference(reference); } // Dispose of any analyzers that might still be around to remove their load diagnostics diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectCreationInfo.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectCreationInfo.cs new file mode 100644 index 0000000000000..51f48ae74ae69 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectCreationInfo.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem +{ + internal class ProjectSystemProjectCreationInfo + { + public string? AssemblyName { get; set; } + public CompilationOptions? CompilationOptions { get; set; } + public string? FilePath { get; set; } + public ParseOptions? ParseOptions { get; set; } + + public Guid TelemetryId { get; set; } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs new file mode 100644 index 0000000000000..efc95bd15920d --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs @@ -0,0 +1,657 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ProjectSystem; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem +{ + internal sealed class ProjectSystemProjectFactory + { + /// + /// The main gate to synchronize updates to this solution. + /// + /// + /// See the Readme.md in this directory for further comments about threading in this area. + /// + // TODO: we should be able to get rid of this gate in favor of just calling the various workspace methods that acquire the Workspace's + // serialization lock and then allow us to update our own state under that lock. + private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1); + + public Workspace Workspace { get; } + public IAsynchronousOperationListener WorkspaceListener { get; } + public IFileChangeWatcher FileChangeWatcher { get; } + public FileWatchedPortableExecutableReferenceFactory FileWatchedReferenceFactory { get; } + + private readonly Action> _onDocumentsAdded; + private readonly Action _onProjectRemoved; + + /// + /// A set of documents that were added by , and aren't otherwise + /// tracked for opening/closing. + /// + public ImmutableHashSet DocumentsNotFromFiles { get; private set; } = ImmutableHashSet.Empty; + + /// Should be updated with . + private ImmutableDictionary _projectToMaxSupportedLangVersionMap = ImmutableDictionary.Empty; + + /// Should be updated with . + private ImmutableDictionary _projectToDependencyNodeTargetIdentifier = ImmutableDictionary.Empty; + + /// + /// Set by the host if the solution is currently closing; this can be used to optimize some things there. + /// + public bool SolutionClosing { get; set; } + + /// + /// The current path to the solution. Currently this is only used to update the solution path when the first project is added -- we don't have a concept + /// of the solution path changing in the middle while a bunch of projects are loaded. + /// + public string? SolutionPath { get; set; } + public Guid SolutionTelemetryId { get; set; } + + public ProjectSystemProjectFactory(Workspace workspace, IFileChangeWatcher fileChangeWatcher, Action> onDocumentsAdded, Action onProjectRemoved) + { + Workspace = workspace; + WorkspaceListener = workspace.Services.GetRequiredService().GetListener(); + + FileChangeWatcher = fileChangeWatcher; + FileWatchedReferenceFactory = new FileWatchedPortableExecutableReferenceFactory(workspace.Services.SolutionServices, fileChangeWatcher); + FileWatchedReferenceFactory.ReferenceChanged += this.StartRefreshingMetadataReferencesForFile; + + _onDocumentsAdded = onDocumentsAdded; + _onProjectRemoved = onProjectRemoved; + } + + public async Task CreateAndAddToWorkspaceAsync(string projectSystemName, string language, ProjectSystemProjectCreationInfo creationInfo, ProjectSystemHostInfo hostInfo) + { + var id = ProjectId.CreateNewId(projectSystemName); + var assemblyName = creationInfo.AssemblyName ?? projectSystemName; + + // We will use the project system name as the default display name of the project + var project = new ProjectSystemProject( + this, + hostInfo, + id, + displayName: projectSystemName, + language, + assemblyName: assemblyName, + compilationOptions: creationInfo.CompilationOptions, + filePath: creationInfo.FilePath, + parseOptions: creationInfo.ParseOptions); + + var versionStamp = creationInfo.FilePath != null ? VersionStamp.Create(File.GetLastWriteTimeUtc(creationInfo.FilePath)) + : VersionStamp.Create(); + + await ApplyChangeToWorkspaceAsync(w => + { + var projectInfo = ProjectInfo.Create( + new ProjectInfo.ProjectAttributes( + id, + versionStamp, + name: projectSystemName, + assemblyName: assemblyName, + language: language, + checksumAlgorithm: SourceHashAlgorithms.Default, // will be updated when command line is set + compilationOutputFilePaths: default, // will be updated when command line is set + filePath: creationInfo.FilePath, + telemetryId: creationInfo.TelemetryId), + compilationOptions: creationInfo.CompilationOptions, + parseOptions: creationInfo.ParseOptions); + + // If we don't have any projects and this is our first project being added, then we'll create a new SolutionId + // and count this as the solution being added so that event is raised. + if (w.CurrentSolution.ProjectIds.Count == 0) + { + w.OnSolutionAdded( + SolutionInfo.Create( + SolutionId.CreateNewId(SolutionPath), + VersionStamp.Create(), + SolutionPath, + projects: new[] { projectInfo }, + analyzerReferences: w.CurrentSolution.AnalyzerReferences) + .WithTelemetryId(SolutionTelemetryId)); + } + else + { + w.OnProjectAdded(projectInfo); + } + }).ConfigureAwait(false); + + return project; + } + + public string? TryGetDependencyNodeTargetIdentifier(ProjectId projectId) + { + // This doesn't take a lock since _projectToDependencyNodeTargetIdentifier is immutable + _projectToDependencyNodeTargetIdentifier.TryGetValue(projectId, out var identifier); + return identifier; + } + + public string? TryGetMaxSupportedLanguageVersion(ProjectId projectId) + { + // This doesn't take a lock since _projectToMaxSupportedLangVersionMap is immutable + _projectToMaxSupportedLangVersionMap.TryGetValue(projectId, out var identifier); + return identifier; + } + + internal void AddDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + DocumentsNotFromFiles = DocumentsNotFromFiles.Add(documentId); + } + + internal void RemoveDocumentToDocumentsNotFromFiles_NoLock(DocumentId documentId) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + DocumentsNotFromFiles = DocumentsNotFromFiles.Remove(documentId); + } + /// + /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. + /// + public void ApplyChangeToWorkspace(Action action) + { + using (_gate.DisposableWait()) + { + action(Workspace); + } + } + + /// + /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. + /// + public async ValueTask ApplyChangeToWorkspaceAsync(Action action) + { + using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) + { + action(Workspace); + } + } + + /// + /// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods. + /// + public async ValueTask ApplyChangeToWorkspaceMaybeAsync(bool useAsync, Action action) + { + using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) + { + action(Workspace); + } + } + + /// + /// Applies a solution transformation to the workspace and triggers workspace changed event for specified . + /// The transformation shall only update the project of the solution with the specified . + /// + public void ApplyChangeToWorkspace(ProjectId projectId, Func solutionTransformation) + { + using (_gate.DisposableWait()) + { + Workspace.SetCurrentSolution(solutionTransformation, WorkspaceChangeKind.ProjectChanged, projectId); + } + } + + /// + public void ApplyBatchChangeToWorkspace(Action mutation) + { + ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: false, mutation).VerifyCompleted(); + } + + /// + public Task ApplyBatchChangeToWorkspaceAsync(Action mutation) + { + return ApplyBatchChangeToWorkspaceMaybeAsync(useAsync: true, mutation); + } + + /// + /// Applies a change to the workspace that can do any number of project changes. + /// + /// This is needed to synchronize with to avoid any races. This + /// method could be moved down to the core Workspace layer and then could use the synchronization lock there. + public async Task ApplyBatchChangeToWorkspaceMaybeAsync(bool useAsync, Action mutation) + { + using (useAsync ? await _gate.DisposableWaitAsync().ConfigureAwait(false) : _gate.DisposableWait()) + { + var solutionChanges = new SolutionChangeAccumulator(Workspace.CurrentSolution); + mutation(solutionChanges); + + ApplyBatchChangeToWorkspace_NoLock(solutionChanges); + } + } + + private void ApplyBatchChangeToWorkspace_NoLock(SolutionChangeAccumulator solutionChanges) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + if (!solutionChanges.HasChange) + return; + + Workspace.SetCurrentSolution( + _ => solutionChanges.Solution, + solutionChanges.WorkspaceChangeKind, + solutionChanges.WorkspaceChangeProjectId, + solutionChanges.WorkspaceChangeDocumentId, + onBeforeUpdate: (_, _) => + { + // Clear out mutable state not associated with the solution snapshot (for example, which documents are + // currently open). + foreach (var documentId in solutionChanges.DocumentIdsRemoved) + Workspace.ClearDocumentData(documentId); + }); + } + + private readonly Dictionary _projectReferenceInfoMap = new(); + + private ProjectReferenceInformation GetReferenceInfo_NoLock(ProjectId projectId) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + return _projectReferenceInfoMap.GetOrAdd(projectId, _ => new ProjectReferenceInformation()); + } + + /// + /// Removes the project from the various maps this type maintains; it's still up to the caller to actually remove + /// the project in one way or another. + /// + internal void RemoveProjectFromTrackingMaps_NoLock(ProjectId projectId) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + var project = Workspace.CurrentSolution.GetRequiredProject(projectId); + + if (_projectReferenceInfoMap.TryGetValue(projectId, out var projectReferenceInfo)) + { + // If we still had any output paths, we'll want to remove them to cause conversion back to metadata references. + // The call below implicitly is modifying the collection we've fetched, so we'll make a copy. + var solutionChanges = new SolutionChangeAccumulator(Workspace.CurrentSolution); + + foreach (var outputPath in projectReferenceInfo.OutputPaths.ToList()) + { + RemoveProjectOutputPath_NoLock(solutionChanges, projectId, outputPath); + } + + ApplyBatchChangeToWorkspace_NoLock(solutionChanges); + + _projectReferenceInfoMap.Remove(projectId); + } + + ImmutableInterlocked.TryRemove(ref _projectToMaxSupportedLangVersionMap, projectId, out _); + ImmutableInterlocked.TryRemove(ref _projectToDependencyNodeTargetIdentifier, projectId, out _); + + _onProjectRemoved?.Invoke(project); + } + + internal void RemoveSolution_NoLock() + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + // At this point, we should have had RemoveProjectFromTrackingMaps_NoLock called for everything else, so it's just the solution itself + // to clean up + Contract.ThrowIfFalse(_projectReferenceInfoMap.Count == 0); + Contract.ThrowIfFalse(_projectToMaxSupportedLangVersionMap.Count == 0); + Contract.ThrowIfFalse(_projectToDependencyNodeTargetIdentifier.Count == 0); + + // Create a new empty solution and set this; we will reuse the same SolutionId and path since components still may have persistence information they still need + // to look up by that location; we also keep the existing analyzer references around since those are host-level analyzers that were loaded asynchronously. + Workspace.ClearOpenDocuments(); + + Workspace.SetCurrentSolution( + solution => Workspace.CreateSolution( + SolutionInfo.Create( + SolutionId.CreateNewId(), + VersionStamp.Create(), + analyzerReferences: solution.AnalyzerReferences)), + WorkspaceChangeKind.SolutionRemoved); + } + + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54137", AllowLocks = false)] + internal void SetMaxLanguageVersion(ProjectId projectId, string? maxLanguageVersion) + { + ImmutableInterlocked.Update( + ref _projectToMaxSupportedLangVersionMap, + static (map, arg) => map.SetItem(arg.projectId, arg.maxLanguageVersion), + (projectId, maxLanguageVersion)); + } + + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/54135", AllowLocks = false)] + internal void SetDependencyNodeTargetIdentifier(ProjectId projectId, string targetIdentifier) + { + ImmutableInterlocked.Update( + ref _projectToDependencyNodeTargetIdentifier, + static (map, arg) => map.SetItem(arg.projectId, arg.targetIdentifier), + (projectId, targetIdentifier)); + } + + private sealed class ProjectReferenceInformation + { + public readonly List OutputPaths = new(); + public readonly List<(string path, ProjectReference projectReference)> ConvertedProjectReferences = new List<(string path, ProjectReference)>(); + } + + /// + /// A multimap from an output path to the project outputting to it. Ideally, this shouldn't ever + /// actually be a true multimap, since we shouldn't have two projects outputting to the same path, but + /// any bug by a project adding the wrong output path means we could end up with some duplication. + /// In that case, we'll temporarily have two until (hopefully) somebody removes it. + /// + private readonly Dictionary> _projectsByOutputPath = new(StringComparer.OrdinalIgnoreCase); + + public void AddProjectOutputPath_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + + projectReferenceInformation.OutputPaths.Add(outputPath); + _projectsByOutputPath.MultiAdd(outputPath, projectId); + + var projectsForOutputPath = _projectsByOutputPath[outputPath]; + var distinctProjectsForOutputPath = projectsForOutputPath.Distinct().ToList(); + + // If we have exactly one, then we're definitely good to convert + if (projectsForOutputPath.Count == 1) + { + ConvertMetadataReferencesToProjectReferences_NoLock(solutionChanges, projectId, outputPath); + } + else if (distinctProjectsForOutputPath.Count == 1) + { + // The same project has multiple output paths that are the same. Any project would have already been converted + // by the prior add, so nothing further to do + } + else + { + // We have more than one project outputting to the same path. This shouldn't happen but we'll convert back + // because now we don't know which project to reference. + foreach (var otherProjectId in projectsForOutputPath) + { + // We know that since we're adding a path to projectId and we're here that we couldn't have already + // had a converted reference to us, instead we need to convert things that are pointing to the project + // we're colliding with + if (otherProjectId != projectId) + { + ConvertProjectReferencesToMetadataReferences_NoLock(solutionChanges, otherProjectId, outputPath); + } + } + } + } + + /// + /// Attempts to convert all metadata references to to a project reference to . + /// + /// The of the project that could be referenced in place of the output path. + /// The output path to replace. + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/31306", + Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] + private void ConvertMetadataReferencesToProjectReferences_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectIdToReference, string outputPath) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + foreach (var projectIdToRetarget in solutionChanges.Solution.ProjectIds) + { + if (CanConvertMetadataReferenceToProjectReference(solutionChanges.Solution, projectIdToRetarget, referencedProjectId: projectIdToReference)) + { + // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all + // Project instances to get created. + foreach (PortableExecutableReference reference in solutionChanges.Solution.GetProjectState(projectIdToRetarget)!.MetadataReferences) + { + if (string.Equals(reference.FilePath, outputPath, StringComparison.OrdinalIgnoreCase)) + { + FileWatchedReferenceFactory.StopWatchingReference(reference); + + var projectReference = new ProjectReference(projectIdToReference, reference.Properties.Aliases, reference.Properties.EmbedInteropTypes); + var newSolution = solutionChanges.Solution.RemoveMetadataReference(projectIdToRetarget, reference) + .AddProjectReference(projectIdToRetarget, projectReference); + + solutionChanges.UpdateSolutionForProjectAction(projectIdToRetarget, newSolution); + + GetReferenceInfo_NoLock(projectIdToRetarget).ConvertedProjectReferences.Add( + (reference.FilePath!, projectReference)); + + // We have converted one, but you could have more than one reference with different aliases + // that we need to convert, so we'll keep going + } + } + } + } + } + + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/31306", + Constraint = "Avoid calling " + nameof(CodeAnalysis.Solution.GetProject) + " to avoid realizing all projects.")] + private static bool CanConvertMetadataReferenceToProjectReference(Solution solution, ProjectId projectIdWithMetadataReference, ProjectId referencedProjectId) + { + // We can never make a project reference ourselves. This isn't a meaningful scenario, but if somebody does this by accident + // we do want to throw exceptions. + if (projectIdWithMetadataReference == referencedProjectId) + { + return false; + } + + // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all + // Project instances to get created. + var projectWithMetadataReference = solution.GetProjectState(projectIdWithMetadataReference); + var referencedProject = solution.GetProjectState(referencedProjectId); + + Contract.ThrowIfNull(projectWithMetadataReference); + Contract.ThrowIfNull(referencedProject); + + // We don't want to convert a metadata reference to a project reference if the project being referenced isn't something + // we can create a Compilation for. For example, if we have a C# project, and it's referencing a F# project via a metadata reference + // everything would be fine if we left it a metadata reference. Converting it to a project reference means we couldn't create a Compilation + // anymore in the IDE, since the C# compilation would need to reference an F# compilation. F# projects referencing other F# projects though + // do expect this to work, and so we'll always allow references through of the same language. + if (projectWithMetadataReference.Language != referencedProject.Language) + { + if (projectWithMetadataReference.LanguageServices.GetService() != null && + referencedProject.LanguageServices.GetService() == null) + { + // We're referencing something that we can't create a compilation from something that can, so keep the metadata reference + return false; + } + } + + // If this is going to cause a circular reference, also disallow it + if (solution.GetProjectDependencyGraph().GetProjectsThatThisProjectTransitivelyDependsOn(referencedProjectId).Contains(projectIdWithMetadataReference)) + { + return false; + } + + return true; + } + + /// + /// Finds all projects that had a project reference to and convert it back to a metadata reference. + /// + /// The of the project being referenced. + /// The output path of the given project to remove the link to. + [PerformanceSensitive( + "https://github.com/dotnet/roslyn/issues/37616", + Constraint = "Update ConvertedProjectReferences in place to avoid duplicate list allocations.")] + private void ConvertProjectReferencesToMetadataReferences_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + foreach (var projectIdToRetarget in solutionChanges.Solution.ProjectIds) + { + var referenceInfo = GetReferenceInfo_NoLock(projectIdToRetarget); + + // Update ConvertedProjectReferences in place to avoid duplicate list allocations + for (var i = 0; i < referenceInfo.ConvertedProjectReferences.Count; i++) + { + var convertedReference = referenceInfo.ConvertedProjectReferences[i]; + + if (string.Equals(convertedReference.path, outputPath, StringComparison.OrdinalIgnoreCase) && + convertedReference.projectReference.ProjectId == projectId) + { + var metadataReference = + FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile( + convertedReference.path, + new MetadataReferenceProperties( + aliases: convertedReference.projectReference.Aliases, + embedInteropTypes: convertedReference.projectReference.EmbedInteropTypes)); + + var newSolution = solutionChanges.Solution.RemoveProjectReference(projectIdToRetarget, convertedReference.projectReference) + .AddMetadataReference(projectIdToRetarget, metadataReference); + + solutionChanges.UpdateSolutionForProjectAction(projectIdToRetarget, newSolution); + + referenceInfo.ConvertedProjectReferences.RemoveAt(i); + + // We have converted one, but you could have more than one reference with different aliases + // that we need to convert, so we'll keep going. Make sure to decrement the index so we don't + // skip any items. + i--; + } + } + } + } + + public ProjectReference? TryCreateConvertedProjectReference_NoLock(ProjectId referencingProject, string path, MetadataReferenceProperties properties) + { + // Any conversion to or from project references must be done under the global workspace lock, + // since that needs to be coordinated with updating all projects simultaneously. + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + if (_projectsByOutputPath.TryGetValue(path, out var ids) && ids.Distinct().Count() == 1) + { + var projectIdToReference = ids.First(); + + if (CanConvertMetadataReferenceToProjectReference(Workspace.CurrentSolution, referencingProject, projectIdToReference)) + { + var projectReference = new ProjectReference( + projectIdToReference, + aliases: properties.Aliases, + embedInteropTypes: properties.EmbedInteropTypes); + + GetReferenceInfo_NoLock(referencingProject).ConvertedProjectReferences.Add((path, projectReference)); + + return projectReference; + } + else + { + return null; + } + } + else + { + return null; + } + } + + public ProjectReference? TryRemoveConvertedProjectReference_NoLock(ProjectId referencingProject, string path, MetadataReferenceProperties properties) + { + // Any conversion to or from project references must be done under the global workspace lock, + // since that needs to be coordinated with updating all projects simultaneously. + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + var projectReferenceInformation = GetReferenceInfo_NoLock(referencingProject); + foreach (var convertedProject in projectReferenceInformation.ConvertedProjectReferences) + { + if (convertedProject.path == path && + convertedProject.projectReference.EmbedInteropTypes == properties.EmbedInteropTypes && + convertedProject.projectReference.Aliases.SequenceEqual(properties.Aliases)) + { + projectReferenceInformation.ConvertedProjectReferences.Remove(convertedProject); + return convertedProject.projectReference; + } + } + + return null; + } + + public void RemoveProjectOutputPath_NoLock(SolutionChangeAccumulator solutionChanges, ProjectId projectId, string outputPath) + { + Contract.ThrowIfFalse(_gate.CurrentCount == 0); + + var projectReferenceInformation = GetReferenceInfo_NoLock(projectId); + if (!projectReferenceInformation.OutputPaths.Contains(outputPath)) + { + throw new ArgumentException($"Project does not contain output path '{outputPath}'", nameof(outputPath)); + } + + projectReferenceInformation.OutputPaths.Remove(outputPath); + _projectsByOutputPath.MultiRemove(outputPath, projectId); + + // When a project is closed, we may need to convert project references to metadata references (or vice + // versa). Failure to convert the references could leave a project in the workspace with a project + // reference to a project which is not open. + // + // For the specific case where the entire solution is closing, we do not need to update the state for + // remaining projects as each project closes, because we know those projects will be closed without + // further use. Avoiding reference conversion when the solution is closing improves performance for both + // IDE close scenarios and solution reload scenarios that occur after complex branch switches. + if (!SolutionClosing) + { + if (_projectsByOutputPath.TryGetValue(outputPath, out var remainingProjectsForOutputPath)) + { + var distinctRemainingProjects = remainingProjectsForOutputPath.Distinct(); + if (distinctRemainingProjects.Count() == 1) + { + // We had more than one project outputting to the same path. Now we're back down to one + // so we can reference that one again + ConvertMetadataReferencesToProjectReferences_NoLock(solutionChanges, distinctRemainingProjects.Single(), outputPath); + } + } + else + { + // No projects left, we need to convert back to metadata references + ConvertProjectReferencesToMetadataReferences_NoLock(solutionChanges, projectId, outputPath); + } + } + } + +#pragma warning disable VSTHRD100 // Avoid async void methods + private async void StartRefreshingMetadataReferencesForFile(object? sender, string fullFilePath) +#pragma warning restore VSTHRD100 // Avoid async void methods + { + using var asyncToken = WorkspaceListener.BeginAsyncOperation(nameof(StartRefreshingMetadataReferencesForFile)); + + await ApplyBatchChangeToWorkspaceAsync(solutionChanges => + { + foreach (var project in Workspace.CurrentSolution.Projects) + { + // Loop to find each reference with the given path. It's possible that there might be multiple references of the same path; + // the project system could concievably add the same reference multiple times but with different aliases. It's also possible + // we might not find the path at all: when we receive the file changed event, we aren't checking if the file is still + // in the workspace at that time; it's possible it might have already been removed. + foreach (var portableExecutableReference in project.MetadataReferences.OfType()) + { + if (portableExecutableReference.FilePath == fullFilePath) + { + FileWatchedReferenceFactory.StopWatchingReference(portableExecutableReference); + + var newPortableExecutableReference = + FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile( + portableExecutableReference.FilePath, + portableExecutableReference.Properties); + + var newSolution = solutionChanges.Solution.RemoveMetadataReference(project.Id, portableExecutableReference) + .AddMetadataReference(project.Id, newPortableExecutableReference); + + solutionChanges.UpdateSolutionForProjectAction(project.Id, newSolution); + + } + } + } + }).ConfigureAwait(false); + } + + internal void RaiseOnDocumentsAdded(ImmutableArray filePaths) + { + _onDocumentsAdded(filePaths); + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs new file mode 100644 index 0000000000000..0ff8335234652 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectHostInfo.cs @@ -0,0 +1,17 @@ +// 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.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem +{ + internal record ProjectSystemHostInfo( + ImmutableArray> DynamicFileInfoProviders, + IProjectSystemDiagnosticSource DiagnosticSource, + IHostDiagnosticAnalyzerProvider HostDiagnosticAnalyzerProvider); +} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Readme.md b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/Readme.md similarity index 61% rename from src/VisualStudio/Core/Def/ProjectSystem/Readme.md rename to src/Workspaces/Core/Portable/Workspace/ProjectSystem/Readme.md index 86d0ae5b3a6f7..7e343a2a4c643 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Readme.md +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/Readme.md @@ -2,40 +2,40 @@ There are two core types in this folder, each of which has their own lock. -Each project from the project system is represented by a VisualStudioProject which has as SemaphoreSlim _gate. This lock is taken +Each project from the project system is represented by a ProjectSystemProject which has as SemaphoreSlim _gate. This lock is taken any time a mutation happens to that individual project, to ensure that the type itself is safe to use concurrently. The expectation though is that simultaneous use from multiple threads by a project system isn't common, so no real effort has been expended to try to make the locking fine-grained there; each function just acquires the _gate and then does what it needs to do. -The workspace (VisualStudioWorkspaceImpl) also has it's own SemaphoreSlim _gate. This is acquired any time a change is being made +The project factory (ProjectSystemProjectFactory) also has it's own SemaphoreSlim _gate. This is acquired any time a change is being made to the workspace. As much as possible, try to acquire this gate in an asynchronous fashion, since during solution load this lock will have a lot of things trying to acquire it at once on a bunch of threads, and we may starve off the thread pool if we're not careful. Unfortunately however, we still have legacy project systems that aren't async friendly; they still may apply changes or batches synchronously so in those cases we still acquire the gate in a synchronous fashion. -There is a strict lock hierarchy: a VisualStudioProject may try to acquire the workspace lock while holding it's lock, -but to prevent deadlocks a holder of the VisualStudioWorkspaceImpl lock should never call a function on VisualStudioProject that +There is a strict lock hierarchy: a ProjectSystemProject may try to acquire the workspace lock while holding it's lock, +but to prevent deadlocks a holder of the ProjectSystemProjectFactory lock should never call a function on ProjectSystemProject that would acquire a project lock. To this end, a few bits of information that may seem to be "project specific" are actually stored -in maps in the VisualStudioWorkspaceImpl; specifically we maintain a list of the output paths of a project which we use to convert +in maps in the ProjectSystemProjectFactory; specifically we maintain a list of the output paths of a project which we use to convert metadata references to project references. This list is maintained in the workspace itself to avoid having to reach back to a project and ask it for information which might violate this lock hierarchy. -When a VisualStudioProject needs to make a change to the workspace, there's a number of Apply methods that can be called that +When a ProjectSystemProject needs to make a change to the workspace, there's a number of Apply methods that can be called that acquire the global workspace lock and then call a lambda to do the work that's needed. In some cases there are public methods on -VisualStudioWorkspaceImpl which are suffixed wtih _NoLock; these exist to be called inside one of these Apply methods; they all +ProjectSystemProjectFactory which are suffixed wtih _NoLock; these exist to be called inside one of these Apply methods; they all assert that the workspace lock is already being held. -There is a nested class of VisualStudioProject called BatchingDocumentCollection which manages all of logic around adding and removing +There is a nested class of ProjectSystemProject called BatchingDocumentCollection which manages all of logic around adding and removing documents, and dealing with changes. The nested class exists simply because each project has multiple sets of documents (regular documents, additional files, and .editorconfig files) that all behave the same way, so this allows for a common abstraction -to reuse most of the logic. A BatchingDocumentCollection does not have a lock of it's own, it just acquires the VisualStudioProject +to reuse most of the logic. A BatchingDocumentCollection does not have a lock of it's own, it just acquires the ProjectSystemProject lock whenever needed. There's a few ancillary types that also have their own locks: VisualStudioProjectOptionsTracker is a helper type which takes compiler command line strings and converts it to ParseOptions and -CompilationOptions. It holds onto a VisualStudioProject, and may call VisualStudioProject methods while holding it's lock. Nothing -else holds onto a VisualStudioProjectOptionsTracker that has a lock, so we avoid any deadlocks there. +CompilationOptions. It holds onto a ProjectSystemProject, and may call ProjectSystemProject methods while holding it's lock. Nothing +else holds onto a ProjectSystemProjectOptionsTracker that has a lock, so we avoid any deadlocks there. VisualStudioWorkspaceImpl has a nested type OpenFileTracker that has it's own lock to guard it's own fields. It should call nothing outside of itself while holding that lock. \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/ProjectSystem/SolutionChangeAccumulator.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/SolutionChangeAccumulator.cs similarity index 97% rename from src/VisualStudio/Core/Def/ProjectSystem/SolutionChangeAccumulator.cs rename to src/Workspaces/Core/Portable/Workspace/ProjectSystem/SolutionChangeAccumulator.cs index fd6566fb106c1..4285aae577177 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/SolutionChangeAccumulator.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/SolutionChangeAccumulator.cs @@ -2,11 +2,9 @@ // 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.Collections.Generic; -using Microsoft.CodeAnalysis; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem { /// /// A little helper type to hold onto the being updated in a batch, which also diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs similarity index 86% rename from src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs rename to src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs index 4d72b708d3c07..2b41f36c0219e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/VisualStudioAnalyzer.cs @@ -5,15 +5,13 @@ using System; using System.Collections.Immutable; using System.IO; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem { // TODO: Remove. This is only needed to support Solution Explorer Analyzer node population. // Analyzers should not be loaded in devenv process (see https://github.com/dotnet/roslyn/issues/43008). - internal sealed class VisualStudioAnalyzer : IDisposable + internal sealed class ProjectAnalyzerReference : IDisposable { // Shadow copy analyzer files coming from packages to avoid locking the files in NuGet cache. // NOTE: It is important that we share the same shadow copy assembly loader for all VisualStudioAnalyzer instances. @@ -30,7 +28,7 @@ internal sealed class VisualStudioAnalyzer : IDisposable private AnalyzerReference? _analyzerReference; private ImmutableArray _analyzerLoadErrors = ImmutableArray.Empty; - public VisualStudioAnalyzer(string fullPath, IProjectSystemDiagnosticSource projectSystemDiagnosticSource, ProjectId projectId, string language) + public ProjectAnalyzerReference(string fullPath, IProjectSystemDiagnosticSource projectSystemDiagnosticSource, ProjectId projectId, string language) { FullPath = fullPath; _projectSystemDiagnosticSource = projectSystemDiagnosticSource; @@ -58,9 +56,9 @@ public AnalyzerReference GetReference() } } - private void OnAnalyzerLoadError(object sender, AnalyzerLoadFailureEventArgs e) + private void OnAnalyzerLoadError(object? sender, AnalyzerLoadFailureEventArgs e) { - var data = DocumentAnalysisExecutor.CreateAnalyzerLoadFailureDiagnostic(e, FullPath, _projectId, _language); + var data = _projectSystemDiagnosticSource.CreateAnalyzerLoadFailureDiagnostic(e, FullPath, _projectId, _language); lock (_gate) { diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs index a7fffb6712e92..1a84f8ad6e274 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_Editor.cs @@ -51,7 +51,7 @@ public abstract partial class Workspace /// internal virtual bool CanChangeActiveContextDocument => false; - private protected void ClearOpenDocuments() + internal void ClearOpenDocuments() { List docIds; using (_stateLock.DisposableWait()) From 4e225c252b9ee252fe0f3b27df4059a3b7cfe34f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 16:05:32 -0800 Subject: [PATCH 049/274] Fix --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 42 ++++++++-------- .../Core/Portable/Syntax/SyntaxNode.cs | 5 +- .../Test/Syntax/TestSyntaxNodes.vb | 50 +++++++++++++++++++ 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 6a2c4409c5984..c656fdd50bfb0 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -355,7 +355,7 @@ public void TestContainsDirective() Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); TestContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); - TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.ElifDirectiveTrivia); TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.ElifDirectiveTrivia); TestContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); @@ -369,37 +369,38 @@ public void TestContainsDirective() TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); TestContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); TestContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); - TestContainsHelper1("#!command", SyntaxKind.ShebangDirectiveTrivia, SourceCodeKind.Script); TestContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); TestContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); + TestContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); + TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); + TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); + return; - void TestContainsHelper1(string directive, SyntaxKind directiveKind, SourceCodeKind kind = SourceCodeKind.Regular) + void TestContainsHelper1(string directive, params SyntaxKind[] directiveKinds) { - var options = kind == SourceCodeKind.Regular ? TestOptions.Regular : TestOptions.Script; + Assert.True(directiveKinds.Length > 0); // directive on its own. - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive, options: options)); + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)); // Two of the same directive back to back. - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" {{directive}} {{directive}} - """, options: options)); + """)); - if (kind == SourceCodeKind.Regular) - { - // Directive inside a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + // Directive inside a namespace + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} } """)); - // Multiple Directive inside a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + // Multiple Directive inside a namespace + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -407,8 +408,8 @@ namespace N } """)); - // Directives on different elements in a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit($$""" + // Directives on different elements in a namespace + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -421,16 +422,17 @@ class D } } """)); - } } - void TestContainsHelper2(string directive, SyntaxKind directiveKind, CompilationUnitSyntax compilationUnit) + void TestContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) { Assert.True(compilationUnit.ContainsDirectives); - Assert.True(compilationUnit.ContainsDirective(directiveKind)); - for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + foreach (var directiveKind in directiveKinds) + Assert.True(compilationUnit.ContainsDirective(directiveKind)); + + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedType; kind++) { - if (kind != directiveKind) + if (!directiveKinds.Contains(kind)) Assert.False(compilationUnit.ContainsDirective(kind)); } } diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 57981f79d70ed..2aefe0705b2fa 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -475,11 +475,12 @@ public bool ContainsDirective(int rawKind) { for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) { - if (leadingTriviaNode.GetSlot(i)?.RawKind == rawKind) + var child = leadingTriviaNode.GetSlot(i); + if (child != null && child.IsDirective && child.RawKind == rawKind) return true; } } - else if (leadingTriviaNode.RawKind == rawKind) + else if (leadingTriviaNode.IsDirective && leadingTriviaNode.RawKind == rawKind) { return true; } diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index 6d5f095778559..90ef27d952504 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2095,6 +2095,56 @@ End Module End Sub + + Public Sub TestContainsDirective() + ' Empty compilation unit shouldn't have any directives in it. + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia + Assert.False(SyntaxFactory.ParseCompilationUnit("").ContainsDirective(currentKind)) + Next + + ' basic file shouldn't have any directives in it. + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace").ContainsDirective(currentKind)) + Next + + Dim TestContainsHelper2 = Sub(directive As String, directiveKind As SyntaxKind, compilationUnit As CompilationUnitSyntax) + Assert.True(compilationUnit.ContainsDirectives) + Assert.True(compilationUnit.ContainsDirective(directiveKind)) + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia + If currentKind <> directiveKind Then + Assert.False(compilationUnit.ContainsDirective(currentKind)) + End If + Next + End Sub + + Dim TestContainsHelper1 = Sub(directive As String, directiveKind As SyntaxKind) + ' directive on its own. + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive)) + + ' Two of the same directive back to back. + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive & vbCrLf & directive)) + + ' Directive inside a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "end namespace")) + + ' Multiple Directive inside a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & directive & vbCrLf & "end namespace")) + + ' Directives on different elements in a namespace + TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "class C" & vbCrLf & "end class" & directive & vbCrLf & "class D" & vbCrLf & "end class" & vbCrLf & "end namespace")) + End Sub + + TestContainsHelper1("#const x", SyntaxKind.ConstDirectiveTrivia) + TestContainsHelper1("#if true" & vbCrLf & "#else", SyntaxKind.ElseDirectiveTrivia) + TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia) + TestContainsHelper1("#if true" & vbCrLf & "#end if", SyntaxKind.EndIfDirectiveTrivia) + TestContainsHelper1("#end if", SyntaxKind.BadDirectiveTrivia) + TestContainsHelper1("#region" & vbCrLf & "#end region", SyntaxKind.EndRegionDirectiveTrivia) + TestContainsHelper1("#end region", SyntaxKind.BadDirectiveTrivia) + TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia) + TestContainsHelper1("#region", SyntaxKind.RegionDirectiveTrivia) + End Sub + Public Sub TestNodeTokenConversion01() From b5481d9966ba809978572061db0dbaa798572430 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 16:06:47 -0800 Subject: [PATCH 050/274] Enhance tests --- .../CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index c656fdd50bfb0..f614ad5773e4f 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -356,14 +356,14 @@ public void TestContainsDirective() TestContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); TestContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#else", SyntaxKind.ElseDirectiveTrivia); + TestContainsHelper1("#if true\r\n#else", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia); TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#endif", SyntaxKind.EndIfDirectiveTrivia); + TestContainsHelper1("#if true\r\n#endif", SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia); TestContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#region\r\n#endregion", SyntaxKind.EndRegionDirectiveTrivia); + TestContainsHelper1("#region\r\n#endregion", SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia); TestContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); TestContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); From 19f9666c1b90ecd3e910bf590e7db42b8e332f71 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 16:10:46 -0800 Subject: [PATCH 051/274] VB tests --- .../Test/Syntax/TestSyntaxNodes.vb | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index 90ef27d952504..82ee3235d01ed 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2107,42 +2107,45 @@ End Module Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace").ContainsDirective(currentKind)) Next - Dim TestContainsHelper2 = Sub(directive As String, directiveKind As SyntaxKind, compilationUnit As CompilationUnitSyntax) + Dim TestContainsHelper2 = Sub(directiveKinds As SyntaxKind(), compilationUnit As CompilationUnitSyntax) Assert.True(compilationUnit.ContainsDirectives) - Assert.True(compilationUnit.ContainsDirective(directiveKind)) + For Each currentKind In directiveKinds + Assert.True(compilationUnit.ContainsDirective(currentKind)) + Next + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia - If currentKind <> directiveKind Then + If Not directiveKinds.Contains(currentKind) Then Assert.False(compilationUnit.ContainsDirective(currentKind)) End If Next End Sub - Dim TestContainsHelper1 = Sub(directive As String, directiveKind As SyntaxKind) + Dim TestContainsHelper1 = Sub(directive As String, directiveKinds As SyntaxKind()) ' directive on its own. - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive)) + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)) ' Two of the same directive back to back. - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit(directive & vbCrLf & directive)) + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive & vbCrLf & directive)) ' Directive inside a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "end namespace")) + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "end namespace")) ' Multiple Directive inside a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & directive & vbCrLf & "end namespace")) + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & directive & vbCrLf & "end namespace")) ' Directives on different elements in a namespace - TestContainsHelper2(directive, directiveKind, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "class C" & vbCrLf & "end class" & directive & vbCrLf & "class D" & vbCrLf & "end class" & vbCrLf & "end namespace")) + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "class C" & vbCrLf & "end class" & directive & vbCrLf & "class D" & vbCrLf & "end class" & vbCrLf & "end namespace")) End Sub - TestContainsHelper1("#const x", SyntaxKind.ConstDirectiveTrivia) - TestContainsHelper1("#if true" & vbCrLf & "#else", SyntaxKind.ElseDirectiveTrivia) - TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia) - TestContainsHelper1("#if true" & vbCrLf & "#end if", SyntaxKind.EndIfDirectiveTrivia) - TestContainsHelper1("#end if", SyntaxKind.BadDirectiveTrivia) - TestContainsHelper1("#region" & vbCrLf & "#end region", SyntaxKind.EndRegionDirectiveTrivia) - TestContainsHelper1("#end region", SyntaxKind.BadDirectiveTrivia) - TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia) - TestContainsHelper1("#region", SyntaxKind.RegionDirectiveTrivia) + TestContainsHelper1("#const x", {SyntaxKind.ConstDirectiveTrivia}) + TestContainsHelper1("#if true" & vbCrLf & "#else", {SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia}) + TestContainsHelper1("#else", {SyntaxKind.ElseDirectiveTrivia}) + TestContainsHelper1("#if true" & vbCrLf & "#end if", {SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia}) + TestContainsHelper1("#end if", {SyntaxKind.EndIfDirectiveTrivia}) + TestContainsHelper1("#region" & vbCrLf & "#end region", {SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia}) + TestContainsHelper1("#end region", {SyntaxKind.EndRegionDirectiveTrivia}) + TestContainsHelper1("#if true", {SyntaxKind.IfDirectiveTrivia}) + TestContainsHelper1("#region", {SyntaxKind.RegionDirectiveTrivia}) End Sub From ad3dea64c52b075d5edaec112891bfcf56c3e0b0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 16:13:38 -0800 Subject: [PATCH 052/274] Docs --- src/Compilers/CSharp/Portable/CSharpExtensions.cs | 1 + src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 3 +-- src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpExtensions.cs b/src/Compilers/CSharp/Portable/CSharpExtensions.cs index c4ba7c3bc22a2..3e50a0db77a2c 100644 --- a/src/Compilers/CSharp/Portable/CSharpExtensions.cs +++ b/src/Compilers/CSharp/Portable/CSharpExtensions.cs @@ -60,6 +60,7 @@ public static bool IsKind(this SyntaxNodeOrToken nodeOrToken, SyntaxKind kind) return nodeOrToken.RawKind == (int)kind; } + /// public static bool ContainsDirective(this SyntaxNode node, SyntaxKind kind) => node.ContainsDirective((int)kind); diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 2aefe0705b2fa..3900f731432e3 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -442,8 +442,7 @@ public bool ContainsDiagnostics public bool ContainsDirectives => this.Green.ContainsDirectives; /// - /// Returns true if this node contains any directives (e.g. #if, #nullable, etc.) within it with the same - /// as . + /// Returns true if this node contains any directives (e.g. #if, #nullable, etc.) within it with a matching kind. /// public bool ContainsDirective(int rawKind) { diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb index 66b3ff6d3c7a1..502b016a80669 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb @@ -63,6 +63,7 @@ Namespace Microsoft.CodeAnalysis Return nodeOrToken.RawKind = kind End Function + ''' Public Function ContainsDirective(node As SyntaxNode, kind As SyntaxKind) As Boolean Return node.ContainsDirective(CType(kind, Integer)) From fe45665f3730a5ab1cba69aa4bee4450fed8d7d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 16:16:56 -0800 Subject: [PATCH 053/274] Add tests --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 31 +++++++++++++++++++ .../Test/Syntax/TestSyntaxNodes.vb | 6 ++++ 2 files changed, 37 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index f614ad5773e4f..b15f553f6a646 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -372,6 +372,7 @@ public void TestContainsDirective() TestContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); TestContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); + // !# is special and is only recognized at start of a script file and nowhere else. TestContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); @@ -391,6 +392,12 @@ void TestContainsHelper1(string directive, params SyntaxKind[] directiveKinds) {{directive}} """)); + // Two of the same directive back to back with additional trivia + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + {{directive}} + {{directive}} + """)); + // Directive inside a namespace TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N @@ -408,6 +415,15 @@ namespace N } """)); + // Multiple Directive inside a namespace with additional trivia + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + namespace N + { + {{directive}} + {{directive}} + } + """)); + // Directives on different elements in a namespace TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N @@ -422,6 +438,21 @@ class D } } """)); + + // Directives on different elements in a namespace with additional trivia + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + namespace N + { + {{directive}} + class C + { + } + {{directive}} + class D + { + } + } + """)); } void TestContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index 82ee3235d01ed..ab881ea5d8273 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2127,12 +2127,18 @@ End Module ' Two of the same directive back to back. TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive & vbCrLf & directive)) + ' Two of the same directive back to back with additional trivia + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(" " & directive & vbCrLf & " " & directive)) + ' Directive inside a namespace TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "end namespace")) ' Multiple Directive inside a namespace TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & directive & vbCrLf & "end namespace")) + ' Multiple Directive inside a namespace with additional trivia + TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & " " & directive & vbCrLf & " " & directive & vbCrLf & "end namespace")) + ' Directives on different elements in a namespace TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & directive & vbCrLf & "class C" & vbCrLf & "end class" & directive & vbCrLf & "class D" & vbCrLf & "end class" & vbCrLf & "end namespace")) End Sub From 77775bb6e35509eaecdb1c63f56b55cbaf757690 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 17:04:37 -0800 Subject: [PATCH 054/274] Tests --- .../Workspace/Solution/DocumentState.cs | 15 +-- .../CoreTest/SolutionTests/SolutionTests.cs | 93 ++++++++++++++++++- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index f6526211aabf0..d439448168e2a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -567,7 +568,8 @@ private static async Task TryReuseSiblingTreeAsync( var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - if (CanReuseSiblingRoot(parseOptions, siblingTree.Options, siblingRoot)) + var syntaxKinds = languageServices.GetRequiredService(); + if (CanReuseSiblingRoot(syntaxKinds.IfDirectiveTrivia, parseOptions, siblingTree.Options, siblingRoot)) { var treeFactory = languageServices.GetRequiredService(); @@ -602,7 +604,8 @@ private static TreeAndVersion TryReuseSiblingTree( var siblingRoot = siblingTree.GetRoot(cancellationToken); - if (CanReuseSiblingRoot(parseOptions, siblingTree.Options, siblingRoot)) + var syntaxKinds = languageServices.GetRequiredService(); + if (CanReuseSiblingRoot(syntaxKinds.IfDirectiveTrivia, parseOptions, siblingTree.Options, siblingRoot)) { var treeFactory = languageServices.GetRequiredService(); @@ -623,6 +626,7 @@ private static TreeAndVersion TryReuseSiblingTree( } private static bool CanReuseSiblingRoot( + int ifDirectiveKind, ParseOptions parseOptions, ParseOptions siblingParseOptions, SyntaxNode siblingRoot) @@ -639,11 +643,10 @@ private static bool CanReuseSiblingRoot( if (!siblingRoot.ContainsDirectives) return true; -#if false - // It's ok to contain directives like #nullable, or #region. They don't affect parsing. - if (!siblingRoot.ContainsConditionalDirectives()) + // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a + // `#if` we can't share as each side might parse this differently. + if (!siblingRoot.ContainsDirective(ifDirectiveKind)) return true; -#endif // If the tree contains a #if directive, and the pp-symbol-names are different, then the files // absolutely may be parsed differently, and so they should not be shared. diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index df08774cae909..adc540dfaeaec 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -60,8 +60,11 @@ private static Workspace CreateWorkspaceWithProjectAndDocuments() return workspace; } - private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments(string docContents) + private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments( + string docContents, ParseOptions? parseOptions1 = null, ParseOptions? parseOptions2 = null) { + parseOptions1 ??= CSharpParseOptions.Default; + parseOptions2 ??= CSharpParseOptions.Default; var projectId1 = ProjectId.CreateNewId(); var projectId2 = ProjectId.CreateNewId(); @@ -71,11 +74,11 @@ private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments(string doc // they will still be treated as unique as the workspace only has the concept of linked docs for normal // docs. Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution - .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp) + .AddProject(projectId1, "proj1", "proj1.dll", LanguageNames.CSharp).WithProjectParseOptions(projectId1, parseOptions1) .AddDocument(DocumentId.CreateNewId(projectId1), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId1), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId1), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b") - .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp) + .AddProject(projectId2, "proj2", "proj2.dll", LanguageNames.CSharp).WithProjectParseOptions(projectId2, parseOptions2) .AddDocument(DocumentId.CreateNewId(projectId2), "goo.cs", SourceText.From(docContents, Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "goo.cs") .AddAdditionalDocument(DocumentId.CreateNewId(projectId2), "add.txt", SourceText.From("text", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "add.txt") .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId2), "editorcfg", SourceText.From("config", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: "/a/b"))); @@ -473,7 +476,7 @@ public async Task WithDocumentText_LinkedFiles( } [Theory, CombinatorialData] - public async Task WithDocumentText_LinkedFiles_PPConditionalDirective( + public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_SameParseOptions( PreservationMode mode, TextUpdateType updateType) { @@ -499,6 +502,88 @@ public class Goo { } Assert.Equal(text1.ToString(), text2.ToString()); Assert.Equal(version1, version2); + // We can reuse trees with conditional directives if the parse options are the same. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + + var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // We get different red and green nodes entirely + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We can reuse trees with conditional directives if the parse options are the same. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + } + + [Theory, CombinatorialData] + public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions( + PreservationMode mode, + TextUpdateType updateType) + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(""" + #if NETSTANDARD + public class Goo { } + """, CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME"), CSharpParseOptions.Default); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + // We can never reuse trees with conditional directives. Assert.NotEqual(root1, root2); Assert.False(root1.IsIncrementallyIdenticalTo(root2)); From b85ae1d421c0996761f15ad2674478acebcc5cf2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 17:07:27 -0800 Subject: [PATCH 055/274] Add tests --- .../CoreTest/SolutionTests/SolutionTests.cs | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index adc540dfaeaec..7d33a904f49b9 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -558,7 +558,7 @@ public class Goo { } } [Theory, CombinatorialData] - public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions( + public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions1( PreservationMode mode, TextUpdateType updateType) { @@ -588,7 +588,91 @@ public class Goo { } Assert.NotEqual(root1, root2); Assert.False(root1.IsIncrementallyIdenticalTo(root2)); - var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); + // Because we removed pp directives, we'll be able to reuse after this. + var text = SourceText.From("new text without pp directives", encoding: null, SourceHashAlgorithm.Sha1); + var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; + + // because we only forked one doc, the text/versions should be different in this interim solution. + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.NotEqual(text1.ToString(), text2.ToString()); + Assert.NotEqual(version1, version2); + + // We get different red and green nodes entirely + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. + workspace.TryApplyChanges(solution); + solution = workspace.CurrentSolution; + + document1 = solution.GetRequiredDocument(documentId1); + document2 = solution.GetRequiredDocument(documentId2); + + text1 = await document1.GetTextAsync(); + text2 = await document2.GetTextAsync(); + version1 = await document1.GetTextVersionAsync(); + version2 = await document2.GetTextVersionAsync(); + root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We can reuse trees once they don't have conditional directives. + Assert.NotEqual(root1, root2); + Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + } + + [Theory, CombinatorialData] + public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentParseOptions2( + PreservationMode mode, + TextUpdateType updateType) + { + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(""" + #if NETSTANDARD + public class Goo { } + """, CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME"), CSharpParseOptions.Default); + var solution = workspace.CurrentSolution; + + var documentId1 = solution.Projects.First().DocumentIds.Single(); + var documentId2 = solution.Projects.Last().DocumentIds.Single(); + + var document1 = solution.GetRequiredDocument(documentId1); + var document2 = solution.GetRequiredDocument(documentId2); + + var text1 = await document1.GetTextAsync(); + var text2 = await document2.GetTextAsync(); + var version1 = await document1.GetTextVersionAsync(); + var version2 = await document2.GetTextVersionAsync(); + var root1 = await document1.GetRequiredSyntaxRootAsync(CancellationToken.None); + var root2 = await document2.GetRequiredSyntaxRootAsync(CancellationToken.None); + + Assert.Equal(text1.ToString(), text2.ToString()); + Assert.Equal(version1, version2); + + // We can never reuse trees with conditional directives. + Assert.NotEqual(root1, root2); + Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + + // Because we still have pp directives, we'll still not be able to reuse the file. + var text = SourceText.From("#if true", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); solution = updateType switch { From 7fcaa6d07e64e8a3b06c9d772917b628d75d12bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 17:20:33 -0800 Subject: [PATCH 056/274] Simplify --- .../Workspace/Solution/DocumentState.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index d439448168e2a..9ff867cfa0577 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -517,7 +517,17 @@ internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTe // each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers to the // provided source, gets the tree from it, and then wraps its root in a new tree for us. - var newTreeSource = GetReuseTreeSource(this, siblingTextSource, siblingTreeSource); + // copy data from this entity, so we don't keep this green node alive. + + var filePath = this.Attributes.SyntaxTreeFilePath; + var languageServices = this.LanguageServices; + var loadTextOptions = this.LoadTextOptions; + var parseOptions = this.ParseOptions; + var textAndVersionSource = this.TextAndVersionSource; + var treeSource = this.TreeSource; + + var newTreeSource = GetReuseTreeSource( + filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource); return new DocumentState( LanguageServices, @@ -528,29 +538,23 @@ internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTe siblingTextSource, LoadTextOptions, treeSource: newTreeSource); - } - // Static so we don't accidentally capture "this" green documentstate node in the async lazy. + // Static so we don't accidentally capture "this" green documentstate node in the async lazy. - private static AsyncLazy GetReuseTreeSource( - DocumentState documentState, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource) - { - // copy data from this entity, so we don't keep this green node alive. - Contract.ThrowIfFalse(documentState.SupportsSyntaxTree); - - var filePath = documentState.Attributes.SyntaxTreeFilePath; - var languageServices = documentState.LanguageServices; - var loadTextOptions = documentState.LoadTextOptions; - var parseOptions = documentState.ParseOptions; - var textAndVersionSource = documentState.TextAndVersionSource; - var treeSource = documentState.TreeSource; - - return new AsyncLazy( - cancellationToken => TryReuseSiblingTreeAsync(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), - cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), - cacheResult: true); + static AsyncLazy GetReuseTreeSource( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource) + { + return new AsyncLazy( + cancellationToken => TryReuseSiblingTreeAsync(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cacheResult: true); + } } private static async Task TryReuseSiblingTreeAsync( From 8dcb86dd69b94871677f5bef31a69074022a9416 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 17:28:32 -0800 Subject: [PATCH 057/274] Simplify --- .../Workspace/Solution/DocumentState.cs | 152 +++++++++--------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9ff867cfa0577..e65f73d5a95d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -511,13 +511,15 @@ internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTe Contract.ThrowIfNull(siblingTreeSource); - // if a tree source is provided, then we'll want to use the tree it creates, to share as much memory as - // possible with linked files. However, we can't point at that source directly. If we did, we'd produce - // the *exact* same tree-reference as another file. That would be bad as it would break the invariant that - // each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers to the - // provided source, gets the tree from it, and then wraps its root in a new tree for us. + // Always pass along the sibling text. We will always be in sync with that. - // copy data from this entity, so we don't keep this green node alive. + // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as + // much memory as possible with linked files. However, we can't point at that source directly. If we did, + // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the + // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers + // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. + + // copy data from this entity, and pass to static helper, so we don't keep this green node alive. var filePath = this.Attributes.SyntaxTreeFilePath; var languageServices = this.LanguageServices; @@ -537,9 +539,7 @@ internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTe _options, siblingTextSource, LoadTextOptions, - treeSource: newTreeSource); - - // Static so we don't accidentally capture "this" green documentstate node in the async lazy. + newTreeSource); static AsyncLazy GetReuseTreeSource( string filePath, @@ -557,10 +557,68 @@ static AsyncLazy GetReuseTreeSource( } } + private static bool TryReuseSiblingRoot( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + SyntaxNode siblingRoot, + VersionStamp siblingVersion, + [NotNullWhen(true)] out TreeAndVersion? newTreeAndVersion) + { + var siblingTree = siblingRoot.SyntaxTree; + + // Look for things that disqualify us from being able to use our sibling's root. + if (!CanReuseSiblingRoot()) + { + newTreeAndVersion = null; + return false; + } + + var treeFactory = languageServices.GetRequiredService(); + + var newTree = treeFactory.CreateSyntaxTree( + filePath, + parseOptions, + siblingTree.Encoding, + loadTextOptions.ChecksumAlgorithm, + siblingRoot); + + newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); + return true; + + bool CanReuseSiblingRoot() + { + var siblingParseOptions = siblingTree.Options; + + var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; + var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; + + // If both documents have the same preprocessor directives defined, then they'll always produce the + // same trees. So we can trivially reuse the tree from one for the other. + if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) + return true; + + // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. + if (!siblingRoot.ContainsDirectives) + return true; + + // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a + // `#if` we can't share as each side might parse this differently. + var syntaxKinds = languageServices.GetRequiredService(); + if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) + return true; + + // If the tree contains a #if directive, and the pp-symbol-names are different, then the files + // absolutely may be parsed differently, and so they should not be shared. + return false; + } + } + private static async Task TryReuseSiblingTreeAsync( string filePath, HostLanguageServices languageServices, - LoadTextOptions options, + LoadTextOptions loadTextOptions, ParseOptions parseOptions, ValueSource treeSource, ITextAndVersionSource siblingTextSource, @@ -572,31 +630,17 @@ private static async Task TryReuseSiblingTreeAsync( var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxKinds = languageServices.GetRequiredService(); - if (CanReuseSiblingRoot(syntaxKinds.IfDirectiveTrivia, parseOptions, siblingTree.Options, siblingRoot)) - { - var treeFactory = languageServices.GetRequiredService(); - - var newTree = treeFactory.CreateSyntaxTree( - filePath, - parseOptions, - siblingTree.Encoding, - options.ChecksumAlgorithm, - siblingRoot); + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; - return new TreeAndVersion(newTree, siblingTreeAndVersion.Version); - } - else - { - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, options, cancellationToken).ConfigureAwait(false); - } + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, loadTextOptions, cancellationToken).ConfigureAwait(false); } private static TreeAndVersion TryReuseSiblingTree( string filePath, HostLanguageServices languageServices, - LoadTextOptions options, + LoadTextOptions loadTextOptions, ParseOptions parseOptions, ValueSource treeSource, ITextAndVersionSource siblingTextSource, @@ -608,53 +652,11 @@ private static TreeAndVersion TryReuseSiblingTree( var siblingRoot = siblingTree.GetRoot(cancellationToken); - var syntaxKinds = languageServices.GetRequiredService(); - if (CanReuseSiblingRoot(syntaxKinds.IfDirectiveTrivia, parseOptions, siblingTree.Options, siblingRoot)) - { - var treeFactory = languageServices.GetRequiredService(); - - var newTree = treeFactory.CreateSyntaxTree( - filePath, - parseOptions, - siblingTree.Encoding, - options.ChecksumAlgorithm, - siblingRoot); + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; - return new TreeAndVersion(newTree, siblingTreeAndVersion.Version); - } - else - { - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return IncrementallyParseTree(treeSource, siblingTextSource, options, cancellationToken); - } - } - - private static bool CanReuseSiblingRoot( - int ifDirectiveKind, - ParseOptions parseOptions, - ParseOptions siblingParseOptions, - SyntaxNode siblingRoot) - { - var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; - var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; - - // If both documents have the same preprocessor directives defined, then they'll always produce the - // same trees. So we can trivially reuse the tree from one for the other. - if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) - return true; - - // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. - if (!siblingRoot.ContainsDirectives) - return true; - - // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a - // `#if` we can't share as each side might parse this differently. - if (!siblingRoot.ContainsDirective(ifDirectiveKind)) - return true; - - // If the tree contains a #if directive, and the pp-symbol-names are different, then the files - // absolutely may be parsed differently, and so they should not be shared. - return false; + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); } #if false From b2a97d75925dcc5a8a95577fdb49dc987dd77313 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Dec 2022 17:31:13 -0800 Subject: [PATCH 058/274] Break out into separate file --- .../Workspace/Solution/DocumentState.cs | 275 ------------------ .../Solution/DocumentState_LinkedFileReuse.cs | 187 ++++++++++++ 2 files changed, 187 insertions(+), 275 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index e65f73d5a95d8..e9cf7318c4e8b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -494,280 +493,6 @@ protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSou treeSource: newTreeSource); } - internal DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTextSource, ValueSource? siblingTreeSource) - { - if (!SupportsSyntaxTree) - { - return new DocumentState( - LanguageServices, - solutionServices, - Services, - Attributes, - _options, - siblingTextSource, - LoadTextOptions, - treeSource: null); - } - - Contract.ThrowIfNull(siblingTreeSource); - - // Always pass along the sibling text. We will always be in sync with that. - - // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as - // much memory as possible with linked files. However, we can't point at that source directly. If we did, - // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the - // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers - // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. - - // copy data from this entity, and pass to static helper, so we don't keep this green node alive. - - var filePath = this.Attributes.SyntaxTreeFilePath; - var languageServices = this.LanguageServices; - var loadTextOptions = this.LoadTextOptions; - var parseOptions = this.ParseOptions; - var textAndVersionSource = this.TextAndVersionSource; - var treeSource = this.TreeSource; - - var newTreeSource = GetReuseTreeSource( - filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource); - - return new DocumentState( - LanguageServices, - solutionServices, - Services, - Attributes, - _options, - siblingTextSource, - LoadTextOptions, - newTreeSource); - - static AsyncLazy GetReuseTreeSource( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - ValueSource treeSource, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource) - { - return new AsyncLazy( - cancellationToken => TryReuseSiblingTreeAsync(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), - cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), - cacheResult: true); - } - } - - private static bool TryReuseSiblingRoot( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - SyntaxNode siblingRoot, - VersionStamp siblingVersion, - [NotNullWhen(true)] out TreeAndVersion? newTreeAndVersion) - { - var siblingTree = siblingRoot.SyntaxTree; - - // Look for things that disqualify us from being able to use our sibling's root. - if (!CanReuseSiblingRoot()) - { - newTreeAndVersion = null; - return false; - } - - var treeFactory = languageServices.GetRequiredService(); - - var newTree = treeFactory.CreateSyntaxTree( - filePath, - parseOptions, - siblingTree.Encoding, - loadTextOptions.ChecksumAlgorithm, - siblingRoot); - - newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); - return true; - - bool CanReuseSiblingRoot() - { - var siblingParseOptions = siblingTree.Options; - - var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; - var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; - - // If both documents have the same preprocessor directives defined, then they'll always produce the - // same trees. So we can trivially reuse the tree from one for the other. - if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) - return true; - - // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. - if (!siblingRoot.ContainsDirectives) - return true; - - // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a - // `#if` we can't share as each side might parse this differently. - var syntaxKinds = languageServices.GetRequiredService(); - if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) - return true; - - // If the tree contains a #if directive, and the pp-symbol-names are different, then the files - // absolutely may be parsed differently, and so they should not be shared. - return false; - } - } - - private static async Task TryReuseSiblingTreeAsync( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - ValueSource treeSource, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource, - CancellationToken cancellationToken) - { - var siblingTreeAndVersion = await siblingTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); - var siblingTree = siblingTreeAndVersion.Tree; - - var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - - if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) - return newTreeAndVersion; - - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, loadTextOptions, cancellationToken).ConfigureAwait(false); - } - - private static TreeAndVersion TryReuseSiblingTree( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - ValueSource treeSource, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource, - CancellationToken cancellationToken) - { - var siblingTreeAndVersion = siblingTreeSource.GetValue(cancellationToken); - var siblingTree = siblingTreeAndVersion.Tree; - - var siblingRoot = siblingTree.GetRoot(cancellationToken); - - if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) - return newTreeAndVersion; - - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); - } - -#if false - private static void TryInitializeTreeSourceFromRelatedDocument( - SolutionState solution, DocumentState document, ValueSource treeSource) - { - s_tryShareSyntaxTreeCount++; - if (document.FilePath == null) - return; - - var relatedDocumentIds = solution.GetDocumentIdsWithFilePath(document.FilePath); - foreach (var docId in relatedDocumentIds) - { - // ignore this document when looking at siblings. We can't initialize ourself with ourself. - if (docId == document.Id) - continue; - - var otherProject = solution.GetProjectState(docId.ProjectId); - if (otherProject == null) - continue; - - var otherDocument = otherProject.DocumentStates.GetState(docId); - if (otherDocument == null) - continue; - - // Now, see if the linked doc actually has its tree readily available. - if (otherDocument._treeSource == null || !otherDocument._treeSource.TryGetValue(out var otherTreeAndVersion)) - continue; - - // And see if its root is there as well. Note: we only need the root to determine if the tree contains - // pp directives. If this could be stored on the tree itself, that would remove the need for having to have - // the actual root available. - - var otherTree = otherTreeAndVersion.Tree; - if (!otherTree.TryGetRoot(out var otherRoot)) - continue; - - // If the processor directives are not compatible between the other document and this one, we definitely - // can't reuse the tree. - if (!HasCompatiblePreprocessorDirectives(document, otherDocument, otherRoot)) - continue; - - // Note: even if the pp directives are compatible, it may *technically* not be safe to reuse the tree. - // For example, C# parses some things differently across language version. like `record Goo() { }` is a - // method prior to 9.0, and a record from 9.0 onwards. *However*, code that actually contains - // constructs that would be parsed differently is considered pathological by us. e.g. we do not believe - // it is a realistic scenario that users would genuinely write such a construct and need it to have - // different syntactic meaning like this across versions. So we allow for this reuse even though the - // above it a possibility, since we do not consider it important or relevant to support. - -#if false - - // Want to make sure that these two docs are pointing at the same text. Technically it's possible - // (though unpleasant) to have linked docs pointing to different text. This is because our in-memory - // model doesn't enforce any invariants here. So it's trivially possible to take two linked documents - // and do things like `doc1.WithSomeText(text1)` and `doc2.WithSomeText(text2)` and now have them be - // inconsistent in that regard. They will eventually become consistent, but there can be periods when - // they are not. In this case, we don't want a forked doc to grab a tree from another doc that may be - // looking at some different text. So we conservatively only allow for the case where we are certain - // things are ok. - // - // https://github.com/dotnet/roslyn/issues/65797 tracks a cleaner model where the workspace would - // enforce that all linked docs would share the same source and we would not need this conservative - // check here. - var textsAreEquivalent = (document.TextAndVersionSource, otherDocument.TextAndVersionSource) switch - { - // For constant sources (like what we have that wraps open documents, or explicitly forked docs) we - // can reuse if the SourceTexts are clearly identical. - (ConstantTextAndVersionSource constant1, ConstantTextAndVersionSource constant2) => constant1.Value.Text == constant2.Value.Text, - // For loadable sources (like what we have for docs loaded from disk) we know they should have the - // same text since they correspond to the same final physical entity on the machine. Note: this is - // not strictly true as technically it's possible to race here with event notifications where a file - // changes, one doc sees it and updates its text loader, and the other linked doc hasn't done this - // yet. However, this race has always existed and we accept that it could cause inconsistencies - // anyways. - (LoadableTextAndVersionSource loadable1, LoadableTextAndVersionSource loadable2) => loadable1.Loader.FilePath == loadable2.Loader.FilePath, - - // Anything else, and we presume we can't share this root. - _ => false, - }; - - if (!textsAreEquivalent) - { - Console.WriteLine($"Texts are not equivalent: {document.TextAndVersionSource.GetType().Name}-{otherDocument.TextAndVersionSource.GetType().Name}"); - continue; - } - - // Console.WriteLine("Texts are equivalent"); - -#endif - - var factory = document.LanguageServices.GetRequiredService(); - var newTree = factory.CreateSyntaxTree( - document.FilePath, otherTree.Options, otherTree.Encoding, document.LoadTextOptions.ChecksumAlgorithm, otherRoot); - - // Ok, now try to set out value-source to this newly created tree. This may fail if some other thread - // beat us here. That's ok, our caller (GetSyntaxTreeAsync) will read the source itself. So we'll only - // ever have one source of truth here. - treeSource.TrySetValue(new TreeAndVersion(newTree, otherTreeAndVersion.Version)); - s_successfullySharedSyntaxTreeCount++; - return; - } - - return; - - static bool HasCompatiblePreprocessorDirectives(DocumentState document1, DocumentState document2, SyntaxNode root) - { - } - } -#endif - internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) { if (!SupportsSyntaxTree) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs new file mode 100644 index 0000000000000..1363a0df93d7b --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -0,0 +1,187 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageService; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + internal partial class DocumentState + { + /// + /// Returns a new instance of this document state that points to as the + /// text contents of the document, and which will produce a syntax tree that reuses from if possible, or which will incrementally parse the current tree to bring it up to + /// date with otherwise. + /// + public DocumentState UpdateTextAndTreeContents(ITextAndVersionSource siblingTextSource, ValueSource? siblingTreeSource) + { + if (!SupportsSyntaxTree) + { + return new DocumentState( + LanguageServices, + solutionServices, + Services, + Attributes, + _options, + siblingTextSource, + LoadTextOptions, + treeSource: null); + } + + Contract.ThrowIfNull(siblingTreeSource); + + // Always pass along the sibling text. We will always be in sync with that. + + // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as + // much memory as possible with linked files. However, we can't point at that source directly. If we did, + // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the + // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers + // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. + + // copy data from this entity, and pass to static helper, so we don't keep this green node alive. + + var filePath = this.Attributes.SyntaxTreeFilePath; + var languageServices = this.LanguageServices; + var loadTextOptions = this.LoadTextOptions; + var parseOptions = this.ParseOptions; + var textAndVersionSource = this.TextAndVersionSource; + var treeSource = this.TreeSource; + + var newTreeSource = GetReuseTreeSource( + filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource); + + return new DocumentState( + LanguageServices, + solutionServices, + Services, + Attributes, + _options, + siblingTextSource, + LoadTextOptions, + newTreeSource); + + static AsyncLazy GetReuseTreeSource( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource) + { + return new AsyncLazy( + cancellationToken => TryReuseSiblingTreeAsync(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), + cacheResult: true); + } + } + + private static bool TryReuseSiblingRoot( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + SyntaxNode siblingRoot, + VersionStamp siblingVersion, + [NotNullWhen(true)] out TreeAndVersion? newTreeAndVersion) + { + var siblingTree = siblingRoot.SyntaxTree; + + // Look for things that disqualify us from being able to use our sibling's root. + if (!CanReuseSiblingRoot()) + { + newTreeAndVersion = null; + return false; + } + + var treeFactory = languageServices.GetRequiredService(); + + var newTree = treeFactory.CreateSyntaxTree( + filePath, + parseOptions, + siblingTree.Encoding, + loadTextOptions.ChecksumAlgorithm, + siblingRoot); + + newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); + return true; + + bool CanReuseSiblingRoot() + { + var siblingParseOptions = siblingTree.Options; + + var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; + var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; + + // If both documents have the same preprocessor directives defined, then they'll always produce the + // same trees. So we can trivially reuse the tree from one for the other. + if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) + return true; + + // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. + if (!siblingRoot.ContainsDirectives) + return true; + + // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a + // `#if` we can't share as each side might parse this differently. + var syntaxKinds = languageServices.GetRequiredService(); + if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) + return true; + + // If the tree contains a #if directive, and the pp-symbol-names are different, then the files + // absolutely may be parsed differently, and so they should not be shared. + return false; + } + } + + private static async Task TryReuseSiblingTreeAsync( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) + { + var siblingTreeAndVersion = await siblingTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); + var siblingTree = siblingTreeAndVersion.Tree; + + var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; + + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, loadTextOptions, cancellationToken).ConfigureAwait(false); + } + + private static TreeAndVersion TryReuseSiblingTree( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) + { + var siblingTreeAndVersion = siblingTreeSource.GetValue(cancellationToken); + var siblingTree = siblingTreeAndVersion.Tree; + + var siblingRoot = siblingTree.GetRoot(cancellationToken); + + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; + + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); + } + } +} From c2de7a01166091d02f6ebfe8500821d2c51c846d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Dec 2022 17:50:08 -0800 Subject: [PATCH 059/274] Only update links if we update the original --- .../Core/Portable/Workspace/Workspace.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 8e0e18533f3b9..f6138e37dc570 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -1002,22 +1002,24 @@ private void OnAnyDocumentTextChanged( var previousSolution = newSolution; newSolution = data.updateSolutionWithText(newSolution, data.documentId, data.arg); if (previousSolution != newSolution) + { updatedDocumentIds.Add(data.documentId); - // Now, see if that document is linked to anything else. If so, update their document-state to point - // at the exact text/tree-source as the doc we just made. This way we can share text/trees and not - // allocate for them unnecessarily. This is only for regular documents, not additional-docs or - // analyzer config, as those don't support links). If so - var linkedDocumentIds = oldSolution.GetRelatedDocumentIds(data.documentId); - if (linkedDocumentIds.Length > 0) - { - var newDocument = newSolution.GetRequiredDocument(data.documentId); - foreach (var linkedDocumentId in linkedDocumentIds) + // Now, see if that document is linked to anything else. If so, update their document-state to point + // at the exact text/tree-source as the doc we just made. This way we can share text/trees and not + // allocate for them unnecessarily. This is only for regular documents, not additional-docs or + // analyzer config, as those don't support links). If so + var linkedDocumentIds = oldSolution.GetRelatedDocumentIds(data.documentId); + if (linkedDocumentIds.Length > 0) { - previousSolution = newSolution; - newSolution = newSolution.WithDocumentContentsFrom(linkedDocumentId, newDocument.DocumentState); - if (previousSolution != newSolution) - updatedDocumentIds.Add(linkedDocumentId); + var newDocument = newSolution.GetRequiredDocument(data.documentId); + foreach (var linkedDocumentId in linkedDocumentIds) + { + previousSolution = newSolution; + newSolution = newSolution.WithDocumentContentsFrom(linkedDocumentId, newDocument.DocumentState); + if (previousSolution != newSolution) + updatedDocumentIds.Add(linkedDocumentId); + } } } From 107dcf6336f4a1d0af0f458c141b9d14d47f252d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Dec 2022 17:58:42 -0800 Subject: [PATCH 060/274] Update docs --- .../Core/Portable/Workspace/Workspace.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index f6138e37dc570..d3a08ef7c6be0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -188,13 +188,19 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio /// /// Applies specified transformation to , updates to - /// the new value and raises a workspace change event of the specified kind. + /// the new value and raises a workspace change event of the specified kind. All linked documents in the + /// solution (which normally will have the same content values) will be updated to to have the same content + /// *identity*. In other words, they will point at the same instances, + /// allowing that memory to be shared. /// /// Solution transformation. /// The kind of workspace change event to raise. - /// The id of the project updated by to be passed to the workspace change event. - /// The id of the document updated by to be passed to the workspace change event. - /// True if was set to the transformed solution, false if the transformation did not change the solution. + /// The id of the project updated by to be passed to + /// the workspace change event. + /// The id of the document updated by to be passed to + /// the workspace change event. + /// True if was set to the transformed solution, false if the + /// transformation did not change the solution. internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( Func transformation, WorkspaceChangeKind kind, From 261f5cbcf89052e5ec1139e9330535edf217c7f8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Dec 2022 18:22:39 -0800 Subject: [PATCH 061/274] Add docs --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 1363a0df93d7b..0db1a00cd47ee 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -136,6 +136,10 @@ bool CanReuseSiblingRoot() // If the tree contains a #if directive, and the pp-symbol-names are different, then the files // absolutely may be parsed differently, and so they should not be shared. + // + // TODO(cyrusn): We could potentially be smarter here as well. We can check what pp-symbols the file + // actually uses. (e.g. 'DEBUG'/'NETCORE'/etc.) and see if the project parse options actually differ + // for these values. If not, we could reuse the trees even then. return false; } } From 38efb86488d3619a0e76f857ef5eaa3bccf8c539 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 10 Dec 2022 14:26:36 -0800 Subject: [PATCH 062/274] playing around with ResolveType --- .../ExtractMethod/CSharpMethodExtractor.Analyzer.cs | 1 + .../CSharpMethodExtractor.CSharpCodeGenerator.cs | 2 +- .../Portable/ExtractMethod/MethodExtractor.Analyzer.cs | 2 +- .../ExtractMethod/MethodExtractor.AnalyzerResult.cs | 8 ++++---- .../ExtractMethod/MethodExtractor.CodeGenerator.cs | 2 +- .../ExtractMethod/MethodExtractor.VariableInfo.cs | 4 ++-- .../Core/Portable/ExtractMethod/MethodExtractor.cs | 2 +- ...VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb | 2 +- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs index e25e1fd93c07f..12df63a119bd6 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs @@ -139,6 +139,7 @@ protected override ITypeSymbol GetSymbolType(SemanticModel semanticModel, ISymbo // Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise // we can modify the annotation to be NotAnnotated to code that more likely matches the user's intent. if (selectionOperation is not null && + NullableHelpers.IsSymbolAssignedPossiblyNullValue(semanticModel, selectionOperation, symbol) == false) { return base.GetSymbolType(semanticModel, symbol).WithNullableAnnotation(NullableAnnotation.NotAnnotated); diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index e1c34d8cf553a..d71c4c62e0c04 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -657,7 +657,7 @@ protected override StatementSyntax CreateDeclarationStatement( ExpressionSyntax initialValue, CancellationToken cancellationToken) { - var type = variable.GetVariableType(SemanticDocument); + var type = variable.GetVariableType(); var typeNode = type.GenerateTypeSyntax(); var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index 743fe3c251dcd..519ce960d837e 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -252,7 +252,7 @@ private void WrapReturnTypeInTask(SemanticModel model, ref ITypeSymbol returnTyp var parameters = MarkVariableInfoToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap.Values)); var variableToUseAsReturnValue = parameters.FirstOrDefault(v => v.UseAsReturnValue); var returnType = variableToUseAsReturnValue != null - ? variableToUseAsReturnValue.GetVariableType(_semanticDocument) + ? variableToUseAsReturnValue.GetVariableType() : compilation.GetSpecialType(SpecialType.System_Void); var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs index 0612dfb286ba0..fe94b811974ee 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs @@ -35,17 +35,17 @@ public AnalyzerResult( bool endOfSelectionReachable, OperationStatus status) { - var semanticModel = document.SemanticModel; + _ = document.SemanticModel; UseInstanceMember = instanceMemberIsUsed; ShouldBeReadOnly = shouldBeReadOnly; EndOfSelectionReachable = endOfSelectionReachable; AwaitTaskReturn = awaitTaskReturn; SemanticDocument = document; - _typeParametersInDeclaration = typeParametersInDeclaration.Select(s => semanticModel.ResolveType(s)).ToList(); - _typeParametersInConstraintList = typeParametersInConstraintList.Select(s => semanticModel.ResolveType(s)).ToList(); + _typeParametersInDeclaration = (IList)typeParametersInDeclaration; + _typeParametersInConstraintList = (IList)typeParametersInConstraintList; _variables = variables; - ReturnType = semanticModel.ResolveType(returnType); + ReturnType = returnType; _variableToUseAsReturnValue = variableToUseAsReturnValue; Status = status; } diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index a4b6851e90720..b2b262321793e 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -350,7 +350,7 @@ protected ImmutableArray CreateMethodParameters() if (!isLocalFunction || !parameter.CanBeCapturedByLocalFunction) { var refKind = GetRefKind(parameter.ParameterModifier); - var type = parameter.GetVariableType(SemanticDocument); + var type = parameter.GetVariableType(); parameters.Add( CodeGenerationSymbolFactory.CreateParameterSymbol( diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs index aa24d6f902298..f7bf30cf8195c 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs @@ -121,8 +121,8 @@ public bool CanBeCapturedByLocalFunction public ITypeSymbol OriginalType => _variableSymbol.OriginalType; - public ITypeSymbol GetVariableType(SemanticDocument document) - => document.SemanticModel.ResolveType(_variableSymbol.OriginalType); + public ITypeSymbol GetVariableType() + => _variableSymbol.OriginalType; public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) => document.GetTokenWithAnnotation(_variableSymbol.IdentifierTokenAnnotation); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index af4564a992731..35e8e4573bcb5 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -173,7 +173,7 @@ private async Task> TryCheckVariableTypeAsync( foreach (var variable in variables) { - var originalType = variable.GetVariableType(document); + var originalType = variable.GetVariableType(); var result = await CheckTypeAsync(document.Document, contextNode, location, originalType, cancellationToken).ConfigureAwait(false); if (result.FailedWithNoBestEffortSuggestion()) { diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index b18200622ecbc..882887b2ae452 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -401,7 +401,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Dim initializer = If(givenInitializer, If(shouldInitializeWithNothing, SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword)), Nothing)) - Dim variableType = variable.GetVariableType(Me.SemanticDocument) + Dim variableType = variable.GetVariableType() Dim typeNode = variableType.GenerateTypeSyntax() Dim names = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(variable.Name))) From 07589175069f0f73286427116fab626174ceac73 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 11 Dec 2022 09:57:59 -0800 Subject: [PATCH 063/274] trying new things --- .../ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs | 2 +- .../Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs | 2 +- .../Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs | 2 +- .../Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs | 2 +- src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs | 2 +- .../VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index d71c4c62e0c04..e1c34d8cf553a 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -657,7 +657,7 @@ protected override StatementSyntax CreateDeclarationStatement( ExpressionSyntax initialValue, CancellationToken cancellationToken) { - var type = variable.GetVariableType(); + var type = variable.GetVariableType(SemanticDocument); var typeNode = type.GenerateTypeSyntax(); var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index 519ce960d837e..743fe3c251dcd 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -252,7 +252,7 @@ private void WrapReturnTypeInTask(SemanticModel model, ref ITypeSymbol returnTyp var parameters = MarkVariableInfoToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap.Values)); var variableToUseAsReturnValue = parameters.FirstOrDefault(v => v.UseAsReturnValue); var returnType = variableToUseAsReturnValue != null - ? variableToUseAsReturnValue.GetVariableType() + ? variableToUseAsReturnValue.GetVariableType(_semanticDocument) : compilation.GetSpecialType(SpecialType.System_Void); var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index b2b262321793e..a4b6851e90720 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -350,7 +350,7 @@ protected ImmutableArray CreateMethodParameters() if (!isLocalFunction || !parameter.CanBeCapturedByLocalFunction) { var refKind = GetRefKind(parameter.ParameterModifier); - var type = parameter.GetVariableType(); + var type = parameter.GetVariableType(SemanticDocument); parameters.Add( CodeGenerationSymbolFactory.CreateParameterSymbol( diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs index f7bf30cf8195c..077e78ad30b0c 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs @@ -121,7 +121,7 @@ public bool CanBeCapturedByLocalFunction public ITypeSymbol OriginalType => _variableSymbol.OriginalType; - public ITypeSymbol GetVariableType() + public ITypeSymbol GetVariableType(SemanticDocument document) => _variableSymbol.OriginalType; public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index 35e8e4573bcb5..af4564a992731 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -173,7 +173,7 @@ private async Task> TryCheckVariableTypeAsync( foreach (var variable in variables) { - var originalType = variable.GetVariableType(); + var originalType = variable.GetVariableType(document); var result = await CheckTypeAsync(document.Document, contextNode, location, originalType, cancellationToken).ConfigureAwait(false); if (result.FailedWithNoBestEffortSuggestion()) { diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index 882887b2ae452..b18200622ecbc 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -401,7 +401,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Dim initializer = If(givenInitializer, If(shouldInitializeWithNothing, SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword)), Nothing)) - Dim variableType = variable.GetVariableType() + Dim variableType = variable.GetVariableType(Me.SemanticDocument) Dim typeNode = variableType.GenerateTypeSyntax() Dim names = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(variable.Name))) From 65b49bfa1a218e2474f80dfb2710ec1adde51a5a Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 11 Dec 2022 15:52:03 -0800 Subject: [PATCH 064/274] new test passed, also helps WI38127 --- .../ExtractMethod/ExtractMethodTests.cs | 2 +- .../ExtractMethod/ExtractMethodTests.cs | 36 +++++++++++++++++++ .../ExtractMethod/CSharpMethodExtractor.cs | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs index 6471658d24a3b..0c839c35051f3 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs @@ -3410,7 +3410,7 @@ class C return x; } - private async Task NewMethod() + private async Task NewMethod() { return await DoSomethingAsync(); } diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs index 09d1d85ad7d4b..5ed87c0b307cb 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.cs @@ -8915,6 +8915,42 @@ private static int GetY(S x) await TestExtractMethodAsync(code, expected); } + [Fact] + public async Task NullabilityTypeParameters() + { + var code = @" +#nullable enable + +using System.Collections.Generic; + +public class Test +{ + public int M(Dictionary v) + { + [|return v.Count;|] + } +}"; + var expected = @" +#nullable enable + +using System.Collections.Generic; + +public class Test +{ + public int M(Dictionary v) + { + return NewMethod(v); + } + + private static int NewMethod(Dictionary v) + { + return v.Count; + } +}"; + + await TestExtractMethodAsync(code, expected); + } + [Fact, WorkItem(543012, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543012")] public async Task TypeParametersInConstraintBestEffort() { diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs index 10629b4ebe93c..8b6a56a1c0dd8 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.cs @@ -140,7 +140,7 @@ protected override async Task CheckTypeAsync( { var typeName = SyntaxFactory.ParseTypeName(typeParameter.Name); var currentType = semanticModel.GetSpeculativeTypeInfo(contextNode.SpanStart, typeName, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; - if (currentType == null || !SymbolEqualityComparer.Default.Equals(currentType, typeParameter)) + if (currentType == null || !SymbolEqualityComparer.Default.Equals(currentType, semanticModel.ResolveType(typeParameter))) { return new OperationStatus(OperationStatusFlag.BestEffort, string.Format(FeaturesResources.Type_parameter_0_is_hidden_by_another_type_parameter_1, From 50ad93e430a66907400d3650c0647011d6cd5d10 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 11 Dec 2022 16:06:42 -0800 Subject: [PATCH 065/274] Youssef suggested changes/clean up --- .../ExtractMethod/CSharpMethodExtractor.Analyzer.cs | 1 - .../CSharpMethodExtractor.CSharpCodeGenerator.cs | 2 +- .../Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs | 2 +- .../ExtractMethod/MethodExtractor.AnalyzerResult.cs | 6 ++---- .../Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs | 2 +- .../Portable/ExtractMethod/MethodExtractor.VariableInfo.cs | 2 +- src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs | 2 +- .../VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb | 2 +- 8 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs index 12df63a119bd6..e25e1fd93c07f 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs @@ -139,7 +139,6 @@ protected override ITypeSymbol GetSymbolType(SemanticModel semanticModel, ISymbo // Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise // we can modify the annotation to be NotAnnotated to code that more likely matches the user's intent. if (selectionOperation is not null && - NullableHelpers.IsSymbolAssignedPossiblyNullValue(semanticModel, selectionOperation, symbol) == false) { return base.GetSymbolType(semanticModel, symbol).WithNullableAnnotation(NullableAnnotation.NotAnnotated); diff --git a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs index e1c34d8cf553a..d71c4c62e0c04 100644 --- a/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs +++ b/src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs @@ -657,7 +657,7 @@ protected override StatementSyntax CreateDeclarationStatement( ExpressionSyntax initialValue, CancellationToken cancellationToken) { - var type = variable.GetVariableType(SemanticDocument); + var type = variable.GetVariableType(); var typeNode = type.GenerateTypeSyntax(); var equalsValueClause = initialValue == null ? null : SyntaxFactory.EqualsValueClause(value: initialValue); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs index 743fe3c251dcd..519ce960d837e 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs @@ -252,7 +252,7 @@ private void WrapReturnTypeInTask(SemanticModel model, ref ITypeSymbol returnTyp var parameters = MarkVariableInfoToUseAsReturnValueIfPossible(GetMethodParameters(variableInfoMap.Values)); var variableToUseAsReturnValue = parameters.FirstOrDefault(v => v.UseAsReturnValue); var returnType = variableToUseAsReturnValue != null - ? variableToUseAsReturnValue.GetVariableType(_semanticDocument) + ? variableToUseAsReturnValue.GetVariableType() : compilation.GetSpecialType(SpecialType.System_Void); var unsafeAddressTakenUsed = ContainsVariableUnsafeAddressTaken(dataFlowAnalysisData, variableInfoMap.Keys); diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs index fe94b811974ee..2268bd1f11655 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.AnalyzerResult.cs @@ -35,15 +35,13 @@ public AnalyzerResult( bool endOfSelectionReachable, OperationStatus status) { - _ = document.SemanticModel; - UseInstanceMember = instanceMemberIsUsed; ShouldBeReadOnly = shouldBeReadOnly; EndOfSelectionReachable = endOfSelectionReachable; AwaitTaskReturn = awaitTaskReturn; SemanticDocument = document; - _typeParametersInDeclaration = (IList)typeParametersInDeclaration; - _typeParametersInConstraintList = (IList)typeParametersInConstraintList; + _typeParametersInDeclaration = typeParametersInDeclaration.ToList(); + _typeParametersInConstraintList = typeParametersInConstraintList.ToList(); _variables = variables; ReturnType = returnType; _variableToUseAsReturnValue = variableToUseAsReturnValue; diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index a4b6851e90720..b2b262321793e 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -350,7 +350,7 @@ protected ImmutableArray CreateMethodParameters() if (!isLocalFunction || !parameter.CanBeCapturedByLocalFunction) { var refKind = GetRefKind(parameter.ParameterModifier); - var type = parameter.GetVariableType(SemanticDocument); + var type = parameter.GetVariableType(); parameters.Add( CodeGenerationSymbolFactory.CreateParameterSymbol( diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs index 077e78ad30b0c..f7bf30cf8195c 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.VariableInfo.cs @@ -121,7 +121,7 @@ public bool CanBeCapturedByLocalFunction public ITypeSymbol OriginalType => _variableSymbol.OriginalType; - public ITypeSymbol GetVariableType(SemanticDocument document) + public ITypeSymbol GetVariableType() => _variableSymbol.OriginalType; public SyntaxToken GetIdentifierTokenAtDeclaration(SemanticDocument document) diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index af4564a992731..35e8e4573bcb5 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -173,7 +173,7 @@ private async Task> TryCheckVariableTypeAsync( foreach (var variable in variables) { - var originalType = variable.GetVariableType(document); + var originalType = variable.GetVariableType(); var result = await CheckTypeAsync(document.Document, contextNode, location, originalType, cancellationToken).ConfigureAwait(false); if (result.FailedWithNoBestEffortSuggestion()) { diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb index b18200622ecbc..882887b2ae452 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.VisualBasicCodeGenerator.vb @@ -401,7 +401,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Dim initializer = If(givenInitializer, If(shouldInitializeWithNothing, SyntaxFactory.NothingLiteralExpression(SyntaxFactory.Token(SyntaxKind.NothingKeyword)), Nothing)) - Dim variableType = variable.GetVariableType(Me.SemanticDocument) + Dim variableType = variable.GetVariableType() Dim typeNode = variableType.GenerateTypeSyntax() Dim names = SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ModifiedIdentifier(SyntaxFactory.Identifier(variable.Name))) From d55758532e8200e5aaeb6d84426bc2c477c16d08 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 11 Dec 2022 21:56:42 -0800 Subject: [PATCH 066/274] vb change --- .../Portable/ExtractMethod/VisualBasicMethodExtractor.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb index 517b9feb0380f..b7a769f619cba 100644 --- a/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb +++ b/src/Features/VisualBasic/Portable/ExtractMethod/VisualBasicMethodExtractor.vb @@ -105,7 +105,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod Dim symbolInfo = binding.GetSpeculativeSymbolInfo(contextNode.SpanStart, typeName, SpeculativeBindingOption.BindAsTypeOrNamespace) Dim currentType = TryCast(symbolInfo.Symbol, ITypeSymbol) - If Not SymbolEqualityComparer.Default.Equals(currentType, typeParameter) Then + If Not SymbolEqualityComparer.Default.Equals(currentType, binding.ResolveType(typeParameter)) Then Return New OperationStatus(OperationStatusFlag.BestEffort, String.Format(FeaturesResources.Type_parameter_0_is_hidden_by_another_type_parameter_1, typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), From e5772dc9bb6a3058d8113ee356c26a12fc166eda Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 10:37:52 -0800 Subject: [PATCH 067/274] Make local functions --- .../WorkspaceConfigurationOptionsStorage.cs | 4 + .../IWorkspaceConfigurationService.cs | 6 +- .../Solution/DocumentState_LinkedFileReuse.cs | 188 +++++++++--------- 3 files changed, 102 insertions(+), 96 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs index fc2a7b87d3191..ec4d1b54ebc7b 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs @@ -36,6 +36,10 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi "WorkspaceConfigurationOptions", "DisableBackgroundCompilation", WorkspaceConfigurationOptions.Default.DisableBackgroundCompilation, new FeatureFlagStorageLocation("Roslyn.DisableBackgroundCompilation")); + public static readonly Option2 DisableSharedSyntaxTrees = new( + "WorkspaceConfigurationOptions", "DisableSharedSyntaxTrees", WorkspaceConfigurationOptions.Default.DisableSharedSyntaxTrees, + new FeatureFlagStorageLocation("Roslyn.DisableSharedSyntaxTrees")); + /// /// This option allows the user to enable this. We are putting this behind a feature flag for now since we could have extensions /// surprised by this and we want some time to work through those issues. diff --git a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs index 532d6e4451476..01176ec8aed89 100644 --- a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs +++ b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs @@ -27,7 +27,8 @@ internal readonly record struct WorkspaceConfigurationOptions( [property: DataMember(Order = 1)] bool EnableOpeningSourceGeneratedFiles = false, [property: DataMember(Order = 2)] bool DisableCloneWhenProducingSkeletonReferences = false, [property: DataMember(Order = 3)] bool DisableReferenceManagerRecoverableMetadata = false, - [property: DataMember(Order = 4)] bool DisableBackgroundCompilation = false) + [property: DataMember(Order = 4)] bool DisableBackgroundCompilation = false, + [property: DataMember(Order = 5)] bool DisableSharedSyntaxTrees = false) { public WorkspaceConfigurationOptions() : this(CacheStorage: StorageDatabase.SQLite) @@ -45,6 +46,7 @@ public WorkspaceConfigurationOptions() EnableOpeningSourceGeneratedFiles: false, DisableCloneWhenProducingSkeletonReferences: false, DisableReferenceManagerRecoverableMetadata: false, - DisableBackgroundCompilation: false); + DisableBackgroundCompilation: false, + DisableSharedSyntaxTrees: false); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 0db1a00cd47ee..cd20afcc751bd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -80,112 +80,112 @@ static AsyncLazy GetReuseTreeSource( cancellationToken => TryReuseSiblingTree(filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, cancellationToken), cacheResult: true); } - } - - private static bool TryReuseSiblingRoot( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - SyntaxNode siblingRoot, - VersionStamp siblingVersion, - [NotNullWhen(true)] out TreeAndVersion? newTreeAndVersion) - { - var siblingTree = siblingRoot.SyntaxTree; - // Look for things that disqualify us from being able to use our sibling's root. - if (!CanReuseSiblingRoot()) + static bool TryReuseSiblingRoot( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + SyntaxNode siblingRoot, + VersionStamp siblingVersion, + [NotNullWhen(true)] out TreeAndVersion? newTreeAndVersion) { - newTreeAndVersion = null; - return false; + var siblingTree = siblingRoot.SyntaxTree; + + // Look for things that disqualify us from being able to use our sibling's root. + if (!CanReuseSiblingRoot()) + { + newTreeAndVersion = null; + return false; + } + + var treeFactory = languageServices.GetRequiredService(); + + var newTree = treeFactory.CreateSyntaxTree( + filePath, + parseOptions, + siblingTree.Encoding, + loadTextOptions.ChecksumAlgorithm, + siblingRoot); + + newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); + return true; + + bool CanReuseSiblingRoot() + { + var siblingParseOptions = siblingTree.Options; + + var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; + var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; + + // If both documents have the same preprocessor directives defined, then they'll always produce the + // same trees. So we can trivially reuse the tree from one for the other. + if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) + return true; + + // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. + if (!siblingRoot.ContainsDirectives) + return true; + + // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a + // `#if` we can't share as each side might parse this differently. + var syntaxKinds = languageServices.GetRequiredService(); + if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) + return true; + + // If the tree contains a #if directive, and the pp-symbol-names are different, then the files + // absolutely may be parsed differently, and so they should not be shared. + // + // TODO(cyrusn): We could potentially be smarter here as well. We can check what pp-symbols the file + // actually uses. (e.g. 'DEBUG'/'NETCORE'/etc.) and see if the project parse options actually differ + // for these values. If not, we could reuse the trees even then. + return false; + } } - var treeFactory = languageServices.GetRequiredService(); - - var newTree = treeFactory.CreateSyntaxTree( - filePath, - parseOptions, - siblingTree.Encoding, - loadTextOptions.ChecksumAlgorithm, - siblingRoot); - - newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); - return true; - - bool CanReuseSiblingRoot() + static async Task TryReuseSiblingTreeAsync( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) { - var siblingParseOptions = siblingTree.Options; - - var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; - var ppSymbolsNames2 = siblingParseOptions.PreprocessorSymbolNames; - - // If both documents have the same preprocessor directives defined, then they'll always produce the - // same trees. So we can trivially reuse the tree from one for the other. - if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) - return true; - - // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. - if (!siblingRoot.ContainsDirectives) - return true; - - // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a - // `#if` we can't share as each side might parse this differently. - var syntaxKinds = languageServices.GetRequiredService(); - if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) - return true; - - // If the tree contains a #if directive, and the pp-symbol-names are different, then the files - // absolutely may be parsed differently, and so they should not be shared. - // - // TODO(cyrusn): We could potentially be smarter here as well. We can check what pp-symbols the file - // actually uses. (e.g. 'DEBUG'/'NETCORE'/etc.) and see if the project parse options actually differ - // for these values. If not, we could reuse the trees even then. - return false; - } - } - - private static async Task TryReuseSiblingTreeAsync( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - ValueSource treeSource, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource, - CancellationToken cancellationToken) - { - var siblingTreeAndVersion = await siblingTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); - var siblingTree = siblingTreeAndVersion.Tree; + var siblingTreeAndVersion = await siblingTreeSource.GetValueAsync(cancellationToken).ConfigureAwait(false); + var siblingTree = siblingTreeAndVersion.Tree; - var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) - return newTreeAndVersion; + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, loadTextOptions, cancellationToken).ConfigureAwait(false); - } + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return await IncrementallyParseTreeAsync(treeSource, siblingTextSource, loadTextOptions, cancellationToken).ConfigureAwait(false); + } - private static TreeAndVersion TryReuseSiblingTree( - string filePath, - HostLanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - ValueSource treeSource, - ITextAndVersionSource siblingTextSource, - ValueSource siblingTreeSource, - CancellationToken cancellationToken) - { - var siblingTreeAndVersion = siblingTreeSource.GetValue(cancellationToken); - var siblingTree = siblingTreeAndVersion.Tree; + static TreeAndVersion TryReuseSiblingTree( + string filePath, + HostLanguageServices languageServices, + LoadTextOptions loadTextOptions, + ParseOptions parseOptions, + ValueSource treeSource, + ITextAndVersionSource siblingTextSource, + ValueSource siblingTreeSource, + CancellationToken cancellationToken) + { + var siblingTreeAndVersion = siblingTreeSource.GetValue(cancellationToken); + var siblingTree = siblingTreeAndVersion.Tree; - var siblingRoot = siblingTree.GetRoot(cancellationToken); + var siblingRoot = siblingTree.GetRoot(cancellationToken); - if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) - return newTreeAndVersion; + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, out var newTreeAndVersion)) + return newTreeAndVersion; - // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. - return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); + // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. + return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); + } } } } From df96e19605434b6f175daeee2df8671a47dd0673 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 10:52:57 -0800 Subject: [PATCH 068/274] Simplify code --- .../Core/Portable/Workspace/Workspace.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index d3a08ef7c6be0..51fb981e6a330 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -213,6 +213,12 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( transformation: static (oldSolution, data) => { var newSolution = data.transformation(oldSolution); + + // Attempt to unify the syntax trees in the new solution (unless the option is set disabling that). + var service = oldSolution.Services.GetRequiredService(); + if (service.Options.DisableSharedSyntaxTrees) + return newSolution; + return UnifyLinkedDocumentContents(oldSolution, newSolution); }, data: (@this: this, transformation, onBeforeUpdate, onAfterUpdate, kind, projectId, documentId), @@ -1007,22 +1013,35 @@ private void OnAnyDocumentTextChanged( var newSolution = oldSolution; var previousSolution = newSolution; newSolution = data.updateSolutionWithText(newSolution, data.documentId, data.arg); + if (previousSolution != newSolution) { updatedDocumentIds.Add(data.documentId); - // Now, see if that document is linked to anything else. If so, update their document-state to point - // at the exact text/tree-source as the doc we just made. This way we can share text/trees and not - // allocate for them unnecessarily. This is only for regular documents, not additional-docs or - // analyzer config, as those don't support links). If so + // Now go update the linked docs to have the same doc contents. var linkedDocumentIds = oldSolution.GetRelatedDocumentIds(data.documentId); if (linkedDocumentIds.Length > 0) { + // Two options for updating linked docs (legacy and new). + // + // Legacy behavior: update each linked doc to point at the same SourceText instance. Each + // doc will reparse itself however it wants (and thus not share any tree contents). + // + // Modern behavior: attempt to actually have the linked documents point *into* the same + // instance data that the initial document points at. This way things like tree data can be + // shared across docs. + + var service = oldSolution.Services.GetRequiredService(); + var shareSyntaxTrees = !service.Options.DisableSharedSyntaxTrees; + var newDocument = newSolution.GetRequiredDocument(data.documentId); foreach (var linkedDocumentId in linkedDocumentIds) { previousSolution = newSolution; - newSolution = newSolution.WithDocumentContentsFrom(linkedDocumentId, newDocument.DocumentState); + newSolution = shareSyntaxTrees + ? newSolution.WithDocumentContentsFrom(linkedDocumentId, newDocument.DocumentState) + : data.updateSolutionWithText(newSolution, linkedDocumentId, data.arg); + if (previousSolution != newSolution) updatedDocumentIds.Add(linkedDocumentId); } From efb23a7b80be758fab57389a9ad04c0f6f2041f9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 12:02:16 -0800 Subject: [PATCH 069/274] Add benchmark --- .../IdeCoreBenchmarks/NavigateToBenchmarks.cs | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index b778f4e9372b3..6d6c131a041bd 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -99,9 +99,9 @@ private void LoadSolution() // Force a storage instance to be created. This makes it simple to go examine it prior to any operations we // perform, including seeing how big the initial string table is. - var storageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); - if (storageService == null) - throw new ArgumentException("Couldn't get storage service"); + //var storageService = _workspace.Services.SolutionServices.GetPersistentStorageService(); + //if (storageService == null) + // throw new ArgumentException("Couldn't get storage service"); } [IterationCleanup] @@ -111,6 +111,38 @@ public void IterationCleanup() _workspace = null; } + [Benchmark] + public async Task RunSerialParsing() + { + Console.WriteLine("start profiling now"); + Thread.Sleep(10000); + Console.WriteLine("Starting serial parsing."); + var start = DateTime.Now; + var roots = new List(); + foreach (var project in _workspace.CurrentSolution.Projects) + { + foreach (var document in project.Documents) + { + // await WalkTree(document); + roots.Add(await document.GetSyntaxRootAsync()); + } + } + + Console.WriteLine("Serial: " + (DateTime.Now - start)); + //Console.WriteLine($"{nameof(DocumentState.s_tryShareSyntaxTreeCount)} - {DocumentState.s_tryShareSyntaxTreeCount}"); + //Console.WriteLine($"{nameof(DocumentState.s_successfullySharedSyntaxTreeCount)} - {DocumentState.s_successfullySharedSyntaxTreeCount}"); + + for (var i = 0; i < 10; i++) + { + GC.Collect(0, GCCollectionMode.Forced, blocking: true); + GC.Collect(1, GCCollectionMode.Forced, blocking: true); + GC.Collect(2, GCCollectionMode.Forced, blocking: true); + } + + Console.ReadLine(); + GC.KeepAlive(roots); + } + // [Benchmark] public async Task RunSerialIndexing() { @@ -151,7 +183,7 @@ public async Task RunProjectParallelIndexing() Console.ReadLine(); } - [Benchmark] + // [Benchmark] public async Task RunFullParallelIndexing() { Console.WriteLine("Attach now"); From 6090392321b568d9c6c4247461f4e48044df36bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 12:12:50 -0800 Subject: [PATCH 070/274] Add stats --- .../IdeCoreBenchmarks/NavigateToBenchmarks.cs | 7 +++++-- .../Solution/DocumentState_LinkedFileReuse.cs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index 6d6c131a041bd..aeb77e6a880e5 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -129,8 +129,11 @@ public async Task RunSerialParsing() } Console.WriteLine("Serial: " + (DateTime.Now - start)); - //Console.WriteLine($"{nameof(DocumentState.s_tryShareSyntaxTreeCount)} - {DocumentState.s_tryShareSyntaxTreeCount}"); - //Console.WriteLine($"{nameof(DocumentState.s_successfullySharedSyntaxTreeCount)} - {DocumentState.s_successfullySharedSyntaxTreeCount}"); + Console.WriteLine($"{nameof(DocumentState.s_tryReuseSyntaxTree)} - {DocumentState.s_tryReuseSyntaxTree}"); + Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfEqualPPNames)} - {DocumentState.s_couldReuseBecauseOfEqualPPNames}"); + Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfNoDirectives)} - {DocumentState.s_couldReuseBecauseOfNoDirectives}"); + Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfNoPPDirectives)} - {DocumentState.s_couldReuseBecauseOfNoPPDirectives}"); + Console.WriteLine($"{nameof(DocumentState.s_couldNotReuse)} - {DocumentState.s_couldNotReuse}"); for (var i = 0; i < 10; i++) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index cd20afcc751bd..cb9deca32f4e1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -113,6 +113,7 @@ static bool TryReuseSiblingRoot( bool CanReuseSiblingRoot() { + s_tryReuseSyntaxTree++; var siblingParseOptions = siblingTree.Options; var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; @@ -121,17 +122,26 @@ bool CanReuseSiblingRoot() // If both documents have the same preprocessor directives defined, then they'll always produce the // same trees. So we can trivially reuse the tree from one for the other. if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) + { + s_couldReuseBecauseOfEqualPPNames++; return true; + } // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. if (!siblingRoot.ContainsDirectives) + { + s_couldReuseBecauseOfNoDirectives++; return true; + } // It's ok to contain directives like #nullable, or #region. They don't affect parsing. But if we have a // `#if` we can't share as each side might parse this differently. var syntaxKinds = languageServices.GetRequiredService(); if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) + { + s_couldReuseBecauseOfNoPPDirectives++; return true; + } // If the tree contains a #if directive, and the pp-symbol-names are different, then the files // absolutely may be parsed differently, and so they should not be shared. @@ -139,6 +149,7 @@ bool CanReuseSiblingRoot() // TODO(cyrusn): We could potentially be smarter here as well. We can check what pp-symbols the file // actually uses. (e.g. 'DEBUG'/'NETCORE'/etc.) and see if the project parse options actually differ // for these values. If not, we could reuse the trees even then. + s_couldNotReuse++; return false; } } @@ -187,5 +198,11 @@ static TreeAndVersion TryReuseSiblingTree( return IncrementallyParseTree(treeSource, siblingTextSource, loadTextOptions, cancellationToken); } } + + public static int s_tryReuseSyntaxTree; + public static int s_couldNotReuse; + public static int s_couldReuseBecauseOfEqualPPNames; + public static int s_couldReuseBecauseOfNoDirectives; + public static int s_couldReuseBecauseOfNoPPDirectives; } } From 3bd0930307b8e2d34606afa34b276f23e1f6152a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 14:42:37 -0800 Subject: [PATCH 071/274] Update remote workspace --- src/Workspaces/Core/Portable/Workspace/Workspace.cs | 9 +++++---- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 51fb981e6a330..de10d585e54e6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -201,9 +201,9 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio /// the workspace change event. /// True if was set to the transformed solution, false if the /// transformation did not change the solution. - internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( + internal (bool updated, Solution newSolution) SetCurrentSolutionAndUnifyLinkedDocumentContents( Func transformation, - WorkspaceChangeKind kind, + WorkspaceChangeKind? kind, ProjectId? projectId = null, DocumentId? documentId = null, Action? onBeforeUpdate = null, @@ -233,10 +233,11 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( // Queue the event but don't execute its handlers on this thread. // Doing so under the serialization lock guarantees the same ordering of the events // as the order of the changes made to the solution. - data.@this.RaiseWorkspaceChangedEventAsync(data.kind, oldSolution, newSolution, data.projectId, data.documentId); + if (data.kind != null) + data.@this.RaiseWorkspaceChangedEventAsync(data.kind.Value, oldSolution, newSolution, data.projectId, data.documentId); }); - return oldSolution != newSolution; + return (oldSolution != newSolution, newSolution); static Solution UnifyLinkedDocumentContents(Solution oldSolution, Solution newSolution) { diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index bee7f094897bc..a8c4be9fdadcc 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -308,10 +308,10 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( // Ensure we update newSolution with the result of SetCurrentSolution. It will be the one appropriately // 'attached' to this workspace. - (_, newSolution) = this.SetCurrentSolution( - (oldSolution, _) => newSolution, - data: /*unused*/0, - onBeforeUpdate: (oldSolution, newSolution, _) => + (_, newSolution) = this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + _ => newSolution, + kind: null, // we'll figure out the change kind in onAfterUpdate below. + onBeforeUpdate: (oldSolution, newSolution) => { if (IsAddingSolution(oldSolution, newSolution)) { @@ -321,7 +321,7 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( this.ClearSolutionData(); } }, - onAfterUpdate: (oldSolution, newSolution, _) => + onAfterUpdate: (oldSolution, newSolution) => { RaiseWorkspaceChangedEventAsync( IsAddingSolution(oldSolution, newSolution) ? WorkspaceChangeKind.SolutionAdded : WorkspaceChangeKind.SolutionChanged, From 3b7f2ef1063b6c20e95915afd41241ef7f2d338f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 14:49:19 -0800 Subject: [PATCH 072/274] Update remote workspace --- .../Core/Portable/Workspace/Workspace.cs | 28 +++++++++++++++---- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 10 ++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index de10d585e54e6..78d451b9931f4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -186,6 +186,24 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio } } + /// + internal (bool updated, Solution newSolution) SetCurrentSolutionAndUnifyLinkedDocumentContents( + Func transformation, + WorkspaceChangeKind changeKind, + ProjectId? projectId = null, + DocumentId? documentId = null, + Action? onBeforeUpdate = null, + Action? onAfterUpdate = null) + { + return SetCurrentSolutionAndUnifyLinkedDocumentContents( + transformation, + (_, _) => changeKind, + projectId, + documentId, + onBeforeUpdate, + onAfterUpdate); + } + /// /// Applies specified transformation to , updates to /// the new value and raises a workspace change event of the specified kind. All linked documents in the @@ -194,7 +212,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio /// allowing that memory to be shared. /// /// Solution transformation. - /// The kind of workspace change event to raise. + /// The kind of workspace change event to raise. /// The id of the project updated by to be passed to /// the workspace change event. /// The id of the document updated by to be passed to @@ -203,7 +221,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio /// transformation did not change the solution. internal (bool updated, Solution newSolution) SetCurrentSolutionAndUnifyLinkedDocumentContents( Func transformation, - WorkspaceChangeKind? kind, + Func changeKind, ProjectId? projectId = null, DocumentId? documentId = null, Action? onBeforeUpdate = null, @@ -221,7 +239,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio return UnifyLinkedDocumentContents(oldSolution, newSolution); }, - data: (@this: this, transformation, onBeforeUpdate, onAfterUpdate, kind, projectId, documentId), + data: (@this: this, transformation, onBeforeUpdate, onAfterUpdate, changeKind, projectId, documentId), onBeforeUpdate: static (oldSolution, newSolution, data) => { data.onBeforeUpdate?.Invoke(oldSolution, newSolution); @@ -233,8 +251,8 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio // Queue the event but don't execute its handlers on this thread. // Doing so under the serialization lock guarantees the same ordering of the events // as the order of the changes made to the solution. - if (data.kind != null) - data.@this.RaiseWorkspaceChangedEventAsync(data.kind.Value, oldSolution, newSolution, data.projectId, data.documentId); + var kind = data.changeKind(oldSolution, newSolution); + data.@this.RaiseWorkspaceChangedEventAsync(kind, oldSolution, newSolution, data.projectId, data.documentId); }); return (oldSolution != newSolution, newSolution); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index a8c4be9fdadcc..47df3cb58cf5f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -310,7 +310,9 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( // 'attached' to this workspace. (_, newSolution) = this.SetCurrentSolutionAndUnifyLinkedDocumentContents( _ => newSolution, - kind: null, // we'll figure out the change kind in onAfterUpdate below. + changeKind: static (oldSolution, newSolution) => IsAddingSolution(oldSolution, newSolution) + ? WorkspaceChangeKind.SolutionAdded + : WorkspaceChangeKind.SolutionChanged onBeforeUpdate: (oldSolution, newSolution) => { if (IsAddingSolution(oldSolution, newSolution)) @@ -320,12 +322,6 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( // this seems suspect as the remote workspace should not be tracking any open document state. this.ClearSolutionData(); } - }, - onAfterUpdate: (oldSolution, newSolution) => - { - RaiseWorkspaceChangedEventAsync( - IsAddingSolution(oldSolution, newSolution) ? WorkspaceChangeKind.SolutionAdded : WorkspaceChangeKind.SolutionChanged, - oldSolution, newSolution); }); return (newSolution, updated: true); From 0fba3808946b71f46de96de01212af6eac004589 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 14:49:41 -0800 Subject: [PATCH 073/274] Simplify --- src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 47df3cb58cf5f..f2325a7414d60 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -312,7 +312,7 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( _ => newSolution, changeKind: static (oldSolution, newSolution) => IsAddingSolution(oldSolution, newSolution) ? WorkspaceChangeKind.SolutionAdded - : WorkspaceChangeKind.SolutionChanged + : WorkspaceChangeKind.SolutionChanged, onBeforeUpdate: (oldSolution, newSolution) => { if (IsAddingSolution(oldSolution, newSolution)) From b5b80ba7599181e14c799b9bbd69f1c3a99ceda2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 14:52:11 -0800 Subject: [PATCH 074/274] Update --- src/Workspaces/Core/Portable/Workspace/Workspace.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 78d451b9931f4..28ad70c19c9a9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -187,7 +187,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio } /// - internal (bool updated, Solution newSolution) SetCurrentSolutionAndUnifyLinkedDocumentContents( + internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( Func transformation, WorkspaceChangeKind changeKind, ProjectId? projectId = null, @@ -195,13 +195,14 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio Action? onBeforeUpdate = null, Action? onAfterUpdate = null) { - return SetCurrentSolutionAndUnifyLinkedDocumentContents( + var (updated, _) = SetCurrentSolutionAndUnifyLinkedDocumentContents( transformation, (_, _) => changeKind, projectId, documentId, onBeforeUpdate, onAfterUpdate); + return updated; } /// From a2e8bb147ca0c162e15ea366f896b25b170a2857 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 12 Dec 2022 15:05:14 -0800 Subject: [PATCH 075/274] Switch some code from HostWorkspaceServices to SolutionServices --- .../MSBuild/MSBuildProjectLoader.Worker.cs | 12 ++++++------ ...uildProjectLoader.Worker_ResolveReferences.cs | 2 +- .../Core/MSBuild/MSBuild/MSBuildProjectLoader.cs | 16 ++++++++-------- .../Core/MSBuild/MSBuild/MSBuildWorkspace.cs | 4 ++-- .../MSBuild/ProjectFile/ProjectFileLoader.cs | 4 ++-- .../ProjectFile/ProjectFileLoaderRegistry.cs | 14 +++++++------- .../Portable/Workspace/Host/SolutionServices.cs | 3 +++ 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index a83f54039c58e..32b6db85b655d 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -24,7 +24,7 @@ public partial class MSBuildProjectLoader { private partial class Worker { - private readonly HostWorkspaceServices _workspaceServices; + private readonly SolutionServices _solutionServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly PathResolver _pathResolver; private readonly ProjectFileLoaderRegistry _projectFileLoaderRegistry; @@ -72,7 +72,7 @@ private partial class Worker private readonly Dictionary> _pathToDiscoveredProjectInfosMap; public Worker( - HostWorkspaceServices services, + SolutionServices services, DiagnosticReporter diagnosticReporter, PathResolver pathResolver, ProjectFileLoaderRegistry projectFileLoaderRegistry, @@ -86,7 +86,7 @@ public Worker( DiagnosticReportingOptions discoveredProjectOptions, bool preferMetadataForReferencesOfDiscoveredProjects) { - _workspaceServices = services; + _solutionServices = services; _diagnosticReporter = diagnosticReporter; _pathResolver = pathResolver; _projectFileLoaderRegistry = projectFileLoaderRegistry; @@ -452,7 +452,7 @@ private ImmutableArray CreateDocumentInfos(IReadOnlyList documents, private TLanguageService? GetLanguageService(string languageName) where TLanguageService : ILanguageService - => _workspaceServices + => _solutionServices .GetLanguageServices(languageName) .GetService(); private TWorkspaceService? GetWorkspaceService() where TWorkspaceService : IWorkspaceService - => _workspaceServices + => _solutionServices .GetService(); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs index a4b7d2ddfa68f..6a1cbd3d44c9b 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker_ResolveReferences.cs @@ -188,7 +188,7 @@ private async Task ResolveReferencesAsync(ProjectId id, Proj // First, gather all of the metadata references from the command-line arguments. var resolvedMetadataReferences = commandLineArgs.ResolveMetadataReferences( new WorkspaceMetadataFileReferenceResolver( - metadataService: _workspaceServices.GetRequiredService(), + metadataService: _solutionServices.GetRequiredService(), pathResolver: new RelativePathResolver(commandLineArgs.ReferencePaths, commandLineArgs.BaseDirectory))); var builder = new ResolvedReferencesBuilder(resolvedMetadataReferences); diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs index 2f323d373ff82..52c2c65450278 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs @@ -20,8 +20,8 @@ namespace Microsoft.CodeAnalysis.MSBuild /// public partial class MSBuildProjectLoader { - // the workspace that the projects and solutions are intended to be loaded into. - private readonly HostWorkspaceServices _workspaceServices; + // the services for the projects and solutions are intended to be loaded into. + private readonly SolutionServices _solutionServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly PathResolver _pathResolver; @@ -31,15 +31,15 @@ public partial class MSBuildProjectLoader private readonly NonReentrantLock _dataGuard = new(); internal MSBuildProjectLoader( - HostWorkspaceServices workspaceServices, + SolutionServices solutionServices, DiagnosticReporter diagnosticReporter, ProjectFileLoaderRegistry? projectFileLoaderRegistry, ImmutableDictionary? properties) { - _workspaceServices = workspaceServices; + _solutionServices = solutionServices; _diagnosticReporter = diagnosticReporter; _pathResolver = new PathResolver(_diagnosticReporter); - _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(workspaceServices, _diagnosticReporter); + _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(solutionServices, _diagnosticReporter); Properties = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); @@ -56,7 +56,7 @@ internal MSBuildProjectLoader( /// An optional dictionary of additional MSBuild properties and values to use when loading projects. /// These are the same properties that are passed to msbuild via the /property:<n>=<v> command line argument. public MSBuildProjectLoader(Workspace workspace, ImmutableDictionary? properties = null) - : this(workspace.Services, new DiagnosticReporter(workspace), projectFileLoaderRegistry: null, properties) + : this(workspace.Services.SolutionServices, new DiagnosticReporter(workspace), projectFileLoaderRegistry: null, properties) { } @@ -200,7 +200,7 @@ public async Task LoadSolutionInfoAsync( var buildManager = new ProjectBuildManager(Properties, msbuildLogger); var worker = new Worker( - _workspaceServices, + _solutionServices, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, @@ -259,7 +259,7 @@ public async Task> LoadProjectInfoAsync( var buildManager = new ProjectBuildManager(Properties, msbuildLogger); var worker = new Worker( - _workspaceServices, + _solutionServices, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index c480a89edb51d..b3a60c92e978a 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -41,8 +41,8 @@ private MSBuildWorkspace( : base(hostServices, WorkspaceKind.MSBuild) { _reporter = new DiagnosticReporter(this); - _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(Services, _reporter); - _loader = new MSBuildProjectLoader(Services, _reporter, _projectFileLoaderRegistry, properties); + _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(Services.SolutionServices, _reporter); + _loader = new MSBuildProjectLoader(Services.SolutionServices, _reporter, _projectFileLoaderRegistry, properties); } /// diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs index d6a0a6fe61c90..f12fe97a64d09 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs @@ -34,9 +34,9 @@ public async Task LoadProjectFileAsync(string path, ProjectBuildMa return this.CreateProjectFile(project, buildManager, log); } - public static IProjectFileLoader GetLoaderForProjectFileExtension(HostWorkspaceServices workspaceServices, string extension) + public static IProjectFileLoader GetLoaderForProjectFileExtension(SolutionServices solutionServices, string extension) { - return workspaceServices.FindLanguageServices( + return solutionServices.FindLanguageServices( d => d.GetEnumerableMetadata("ProjectFileExtension").Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase))) .FirstOrDefault(); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs index 96e552c66a6d9..90d1c51fc64fd 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs @@ -13,14 +13,14 @@ namespace Microsoft.CodeAnalysis.MSBuild { internal class ProjectFileLoaderRegistry { - private readonly HostWorkspaceServices _workspaceServices; + private readonly SolutionServices _solutionServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly Dictionary _extensionToLanguageMap; private readonly NonReentrantLock _dataGuard; - public ProjectFileLoaderRegistry(HostWorkspaceServices workspaceServices, DiagnosticReporter diagnosticReporter) + public ProjectFileLoaderRegistry(SolutionServices solutionServices, DiagnosticReporter diagnosticReporter) { - _workspaceServices = workspaceServices; + _solutionServices = solutionServices; _diagnosticReporter = diagnosticReporter; _extensionToLanguageMap = new Dictionary(StringComparer.OrdinalIgnoreCase); _dataGuard = new NonReentrantLock(); @@ -61,9 +61,9 @@ public bool TryGetLoaderFromProjectPath(string? projectFilePath, DiagnosticRepor if (_extensionToLanguageMap.TryGetValue(extension, out var language)) { - if (_workspaceServices.SupportedLanguages.Contains(language)) + if (_solutionServices.SupportedLanguages.Contains(language)) { - loader = _workspaceServices.GetLanguageServices(language).GetService(); + loader = _solutionServices.GetLanguageServices(language).GetService(); } else { @@ -74,7 +74,7 @@ public bool TryGetLoaderFromProjectPath(string? projectFilePath, DiagnosticRepor } else { - loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_workspaceServices, extension); + loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_solutionServices, extension); if (loader == null) { @@ -89,7 +89,7 @@ public bool TryGetLoaderFromProjectPath(string? projectFilePath, DiagnosticRepor language = loader.Language; // check for command line parser existing... if not then error. - var commandLineParser = _workspaceServices + var commandLineParser = _solutionServices .GetLanguageServices(language) .GetService(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/SolutionServices.cs b/src/Workspaces/Core/Portable/Workspace/Host/SolutionServices.cs index cc1c92c0c383c..a49f51dc7fdc1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/SolutionServices.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/SolutionServices.cs @@ -53,5 +53,8 @@ public LanguageServices GetLanguageServices(string languageName) public TLanguageService GetRequiredLanguageService(string language) where TLanguageService : ILanguageService => this.GetLanguageServices(language).GetRequiredService(); + + internal IEnumerable FindLanguageServices(HostWorkspaceServices.MetadataFilter filter) + => _services.FindLanguageServices(filter); } } From 2454898e21d136b4a52e929917e9435725573ca0 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 12 Dec 2022 16:17:01 -0800 Subject: [PATCH 076/274] Add more InternalVisibleTos --- .../Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj | 1 + .../Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index fb91dd96d6348..2a98ba3ab4967 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj index 7f25e5b44f187..23ee18bb69462 100644 --- a/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj +++ b/src/Workspaces/Remote/Core/Microsoft.CodeAnalysis.Remote.Workspaces.csproj @@ -45,6 +45,7 @@ + From ea1d6777436280a23140c4cfa7d9e321b8c18e31 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:21:50 -0800 Subject: [PATCH 077/274] Quick bail out --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 3900f731432e3..3981e4529ab60 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -446,6 +446,10 @@ public bool ContainsDiagnostics /// public bool ContainsDirective(int rawKind) { + // Easy bail out without doing any work. + if (!this.ContainsDirectives) + return false; + var stack = PooledObjects.ArrayBuilder.GetInstance(); stack.Push(this.Green); From 131f8ea3afde53cc3b71b65e8255e4b5b2c5621d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:26:11 -0800 Subject: [PATCH 078/274] Simplify --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 3981e4529ab60..02b9ece3f3672 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -458,11 +458,9 @@ public bool ContainsDirective(int rawKind) while (stack.Count > 0) { var current = stack.Pop(); - if (current is null) - continue; // Don't bother looking further down this portion of the tree if it clearly doesn't contain directives. - if (!current.ContainsDirectives) + if (current is not { ContainsDirectives: true }) continue; if (current.IsToken) @@ -479,7 +477,7 @@ public bool ContainsDirective(int rawKind) for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) { var child = leadingTriviaNode.GetSlot(i); - if (child != null && child.IsDirective && child.RawKind == rawKind) + if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) return true; } } From fadce8aa510b8b29dda4c3d5c3fef498bca26d02 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:26:49 -0800 Subject: [PATCH 079/274] VB idioms --- src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb index 502b016a80669..f650e9b00560f 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb @@ -66,7 +66,7 @@ Namespace Microsoft.CodeAnalysis ''' Public Function ContainsDirective(node As SyntaxNode, kind As SyntaxKind) As Boolean - Return node.ContainsDirective(CType(kind, Integer)) + Return node.ContainsDirective(CInt(kind)) End Function ''' From 51e44f47e3e846c2005711f6a1474a7fcf8a7b15 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:30:04 -0800 Subject: [PATCH 080/274] Add asserts --- .../Core/Portable/Syntax/SyntaxNode.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 02b9ece3f3672..ff76e348a3640 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -468,24 +468,15 @@ public bool ContainsDirective(int rawKind) // no need to look within if this token doesn't even have leading trivia. if (current.HasLeadingTrivia) { - var leadingTriviaNode = current.GetLeadingTriviaCore(); - Debug.Assert(leadingTriviaNode != null); - - // Will either have one or many trivia nodes. - if (leadingTriviaNode.IsList) - { - for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) - { - var child = leadingTriviaNode.GetSlot(i); - if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) - return true; - } - } - else if (leadingTriviaNode.IsDirective && leadingTriviaNode.RawKind == rawKind) - { + if (triviaContainsMatch(current.GetLeadingTriviaCore(), rawKind)) return true; - } } + else + { + Debug.Assert(!triviaContainsMatch(current.GetLeadingTriviaCore(), rawKind), "Should not have a match if the token doesn't even have leading trivia"); + } + + Debug.Assert(!triviaContainsMatch(current.GetTrailingTriviaCore(), rawKind), "Should never have a match in trailing trivia"); } else { @@ -501,6 +492,29 @@ public bool ContainsDirective(int rawKind) { stack.Free(); } + + static bool triviaContainsMatch(GreenNode? triviaNode, int rawKind) + { + if (triviaNode is not null) + { + // Will either have one or many trivia nodes. + if (triviaNode.IsList) + { + for (int i = 0, n = triviaNode.SlotCount; i < n; i++) + { + var child = triviaNode.GetSlot(i); + if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) + return true; + } + } + else if (triviaNode.IsDirective && triviaNode.RawKind == rawKind) + { + return true; + } + } + + return false; + } } /// From 2b9364a52b1cf2f2ac3812e56d6b0747a5d022e8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:33:37 -0800 Subject: [PATCH 081/274] PR feedback --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index b15f553f6a646..2ecbeb30d342e 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -354,60 +354,60 @@ public void TestContainsDirective() for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); - TestContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); - TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#else", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia); - TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#endif", SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia); - TestContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#region\r\n#endregion", SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia); - TestContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); - TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); - TestContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); - TestContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); - TestContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); - TestContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); + testContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); + testContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#if false\r\n#elif false", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#if true\r\n#else", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia); + testContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#if true\r\n#endif", SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia); + testContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#region\r\n#endregion", SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia); + testContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); + testContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); + testContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); + testContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); + testContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); + testContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); // !# is special and is only recognized at start of a script file and nowhere else. - TestContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); - TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); - TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); + testContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); + testContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); + testContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); return; - void TestContainsHelper1(string directive, params SyntaxKind[] directiveKinds) + void testContainsHelper1(string directive, params SyntaxKind[] directiveKinds) { Assert.True(directiveKinds.Length > 0); // directive on its own. - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)); + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)); // Two of the same directive back to back. - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" {{directive}} {{directive}} """)); // Two of the same directive back to back with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" {{directive}} {{directive}} """)); // Directive inside a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { -{{directive}} + {{directive}} } """)); // Multiple Directive inside a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -416,7 +416,7 @@ namespace N """)); // Multiple Directive inside a namespace with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -425,7 +425,7 @@ namespace N """)); // Directives on different elements in a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -440,7 +440,7 @@ class D """)); // Directives on different elements in a namespace with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -455,7 +455,7 @@ class D """)); } - void TestContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) + void testContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) { Assert.True(compilationUnit.ContainsDirectives); foreach (var directiveKind in directiveKinds) From b5115339a09051ee848a01f2ce0ea225bb670be3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:39:59 -0800 Subject: [PATCH 082/274] Add trailing trivia tsts --- src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs | 4 ++++ src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 2ecbeb30d342e..56a9368348944 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -354,6 +354,10 @@ public void TestContainsDirective() for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); + // directive in trailing trivia is not a thing + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { } #if false").ContainsDirective(kind)); + testContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); testContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); testContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index ab881ea5d8273..ba6425d73b0e2 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2107,6 +2107,11 @@ End Module Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace").ContainsDirective(currentKind)) Next + ' directive in trailing trivia Is Not a thing + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace #if false").ContainsDirective(currentKind)) + Next + Dim TestContainsHelper2 = Sub(directiveKinds As SyntaxKind(), compilationUnit As CompilationUnitSyntax) Assert.True(compilationUnit.ContainsDirectives) For Each currentKind In directiveKinds From 578b8d5a5cca459e78c367d1c2f46dd2dd9b78bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:21:50 -0800 Subject: [PATCH 083/274] Quick bail out --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 3900f731432e3..3981e4529ab60 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -446,6 +446,10 @@ public bool ContainsDiagnostics /// public bool ContainsDirective(int rawKind) { + // Easy bail out without doing any work. + if (!this.ContainsDirectives) + return false; + var stack = PooledObjects.ArrayBuilder.GetInstance(); stack.Push(this.Green); From 0721e88fad3cefdec791505f312f987fe1843b9d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:26:11 -0800 Subject: [PATCH 084/274] Simplify --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 3981e4529ab60..02b9ece3f3672 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -458,11 +458,9 @@ public bool ContainsDirective(int rawKind) while (stack.Count > 0) { var current = stack.Pop(); - if (current is null) - continue; // Don't bother looking further down this portion of the tree if it clearly doesn't contain directives. - if (!current.ContainsDirectives) + if (current is not { ContainsDirectives: true }) continue; if (current.IsToken) @@ -479,7 +477,7 @@ public bool ContainsDirective(int rawKind) for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) { var child = leadingTriviaNode.GetSlot(i); - if (child != null && child.IsDirective && child.RawKind == rawKind) + if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) return true; } } From 1a765eb0ee9f201c14c3bfe8ef3ac94ab16348bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:26:49 -0800 Subject: [PATCH 085/274] VB idioms --- src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb index 502b016a80669..f650e9b00560f 100644 --- a/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb +++ b/src/Compilers/VisualBasic/Portable/VisualBasicExtensions.vb @@ -66,7 +66,7 @@ Namespace Microsoft.CodeAnalysis ''' Public Function ContainsDirective(node As SyntaxNode, kind As SyntaxKind) As Boolean - Return node.ContainsDirective(CType(kind, Integer)) + Return node.ContainsDirective(CInt(kind)) End Function ''' From c9e7c68a2f5d39c67d30a673d9cdae4b83aecdb7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:30:04 -0800 Subject: [PATCH 086/274] Add asserts --- .../Core/Portable/Syntax/SyntaxNode.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 02b9ece3f3672..ff76e348a3640 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -468,24 +468,15 @@ public bool ContainsDirective(int rawKind) // no need to look within if this token doesn't even have leading trivia. if (current.HasLeadingTrivia) { - var leadingTriviaNode = current.GetLeadingTriviaCore(); - Debug.Assert(leadingTriviaNode != null); - - // Will either have one or many trivia nodes. - if (leadingTriviaNode.IsList) - { - for (int i = 0, n = leadingTriviaNode.SlotCount; i < n; i++) - { - var child = leadingTriviaNode.GetSlot(i); - if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) - return true; - } - } - else if (leadingTriviaNode.IsDirective && leadingTriviaNode.RawKind == rawKind) - { + if (triviaContainsMatch(current.GetLeadingTriviaCore(), rawKind)) return true; - } } + else + { + Debug.Assert(!triviaContainsMatch(current.GetLeadingTriviaCore(), rawKind), "Should not have a match if the token doesn't even have leading trivia"); + } + + Debug.Assert(!triviaContainsMatch(current.GetTrailingTriviaCore(), rawKind), "Should never have a match in trailing trivia"); } else { @@ -501,6 +492,29 @@ public bool ContainsDirective(int rawKind) { stack.Free(); } + + static bool triviaContainsMatch(GreenNode? triviaNode, int rawKind) + { + if (triviaNode is not null) + { + // Will either have one or many trivia nodes. + if (triviaNode.IsList) + { + for (int i = 0, n = triviaNode.SlotCount; i < n; i++) + { + var child = triviaNode.GetSlot(i); + if (child is { IsDirective: true, RawKind: var childKind } && childKind == rawKind) + return true; + } + } + else if (triviaNode.IsDirective && triviaNode.RawKind == rawKind) + { + return true; + } + } + + return false; + } } /// From a22ea71fceb7c41c8c189c26cd96d98c587efc19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:33:37 -0800 Subject: [PATCH 087/274] PR feedback --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index b15f553f6a646..2ecbeb30d342e 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -354,60 +354,60 @@ public void TestContainsDirective() for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); - TestContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); - TestContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#if false\r\n#elif false", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); - TestContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#else", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia); - TestContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#if true\r\n#endif", SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia); - TestContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#region\r\n#endregion", SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia); - TestContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); - TestContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); - TestContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); - TestContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); - TestContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); - TestContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); - TestContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); + testContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); + testContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#if false\r\n#elif false", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); + testContainsHelper1("#elif true", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#if true\r\n#else", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia); + testContainsHelper1("#else", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#if true\r\n#endif", SyntaxKind.IfDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia); + testContainsHelper1("#endif", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#region\r\n#endregion", SyntaxKind.RegionDirectiveTrivia, SyntaxKind.EndRegionDirectiveTrivia); + testContainsHelper1("#endregion", SyntaxKind.BadDirectiveTrivia); + testContainsHelper1("#error", SyntaxKind.ErrorDirectiveTrivia); + testContainsHelper1("#if true", SyntaxKind.IfDirectiveTrivia); + testContainsHelper1("#nullable enable", SyntaxKind.NullableDirectiveTrivia); + testContainsHelper1("#region enable", SyntaxKind.RegionDirectiveTrivia); + testContainsHelper1("#undef x", SyntaxKind.UndefDirectiveTrivia); + testContainsHelper1("#warning", SyntaxKind.WarningDirectiveTrivia); // !# is special and is only recognized at start of a script file and nowhere else. - TestContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); - TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); - TestContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); + testContainsHelper2(new[] { SyntaxKind.ShebangDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Script)); + testContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit(" #!command", options: TestOptions.Script)); + testContainsHelper2(new[] { SyntaxKind.BadDirectiveTrivia }, SyntaxFactory.ParseCompilationUnit("#!command", options: TestOptions.Regular)); return; - void TestContainsHelper1(string directive, params SyntaxKind[] directiveKinds) + void testContainsHelper1(string directive, params SyntaxKind[] directiveKinds) { Assert.True(directiveKinds.Length > 0); // directive on its own. - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)); + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit(directive)); // Two of the same directive back to back. - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" {{directive}} {{directive}} """)); // Two of the same directive back to back with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" {{directive}} {{directive}} """)); // Directive inside a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { -{{directive}} + {{directive}} } """)); // Multiple Directive inside a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -416,7 +416,7 @@ namespace N """)); // Multiple Directive inside a namespace with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -425,7 +425,7 @@ namespace N """)); // Directives on different elements in a namespace - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -440,7 +440,7 @@ class D """)); // Directives on different elements in a namespace with additional trivia - TestContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" + testContainsHelper2(directiveKinds, SyntaxFactory.ParseCompilationUnit($$""" namespace N { {{directive}} @@ -455,7 +455,7 @@ class D """)); } - void TestContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) + void testContainsHelper2(SyntaxKind[] directiveKinds, CompilationUnitSyntax compilationUnit) { Assert.True(compilationUnit.ContainsDirectives); foreach (var directiveKind in directiveKinds) From 55b2cd960cd54471442a11064861ae9197ec1e3b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:39:59 -0800 Subject: [PATCH 088/274] Add trailing trivia tsts --- src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs | 4 ++++ src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 2ecbeb30d342e..56a9368348944 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -354,6 +354,10 @@ public void TestContainsDirective() for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { }").ContainsDirective(kind)); + // directive in trailing trivia is not a thing + for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { } #if false").ContainsDirective(kind)); + testContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); testContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); testContainsHelper1("#if false\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index ab881ea5d8273..ba6425d73b0e2 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2107,6 +2107,11 @@ End Module Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace").ContainsDirective(currentKind)) Next + ' directive in trailing trivia Is Not a thing + For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia + Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace #if false").ContainsDirective(currentKind)) + Next + Dim TestContainsHelper2 = Sub(directiveKinds As SyntaxKind(), compilationUnit As CompilationUnitSyntax) Assert.True(compilationUnit.ContainsDirectives) For Each currentKind In directiveKinds From f229690d7908826b394c53017ac867f2e60340fa Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 12 Dec 2022 16:44:26 -0800 Subject: [PATCH 089/274] Add methods to IWorkspaceProject for adding/removing dynamic files --- .../BrokeredService/WorkspaceProject.cs | 16 ++++++++++++++++ .../Core/ProjectSystem/IWorkspaceProject.cs | 3 +++ 2 files changed, 19 insertions(+) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs index 01d0a6ee86e2f..651bdba84e5a5 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs @@ -118,6 +118,22 @@ public async Task RemoveSourceFilesAsync(IReadOnlyList sourceFiles, Canc _project.RemoveSourceFile(sourceFile); } + public async Task AddDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) + { + await using var batch = _project.CreateBatchScope().ConfigureAwait(false); + + foreach (var dynamicFilePath in dynamicFilePaths) + _project.AddDynamicFile(dynamicFilePath); + } + + public async Task RemoveDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) + { + await using var batch = _project.CreateBatchScope().ConfigureAwait(false); + + foreach (var dynamicFilePath in dynamicFilePaths) + _project.RemoveDynamicFile(dynamicFilePath); + } + public async Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary properties, CancellationToken cancellationToken) { await using var batch = _project.CreateBatchScope().ConfigureAwait(false); diff --git a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs index 9b0b5e5ceec09..46d04df81872a 100644 --- a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs +++ b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs @@ -33,5 +33,8 @@ internal interface IWorkspaceProject : IAsyncDisposable Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken); Task RemoveAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken); + Task AddDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken); + Task RemoveDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken); + Task StartBatchAsync(CancellationToken cancellationToken); } From 3ef4f134268b7f38e9b72047810bfed86560324d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Dec 2022 16:47:08 -0800 Subject: [PATCH 090/274] Add test --- .../CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs | 12 +++++++++++- .../VisualBasic/Test/Syntax/TestSyntaxNodes.vb | 5 ++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 56a9368348944..724684fea475a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -356,7 +356,17 @@ public void TestContainsDirective() // directive in trailing trivia is not a thing for (var kind = SyntaxKind.TildeToken; kind < SyntaxKind.ScopedKeyword; kind++) - Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N { } #if false").ContainsDirective(kind)); + { + var compilationUnit = SyntaxFactory.ParseCompilationUnit("namespace N { } #if false"); + compilationUnit.GetDiagnostics().Verify( + // (1,17): error CS1040: Preprocessor directives must appear as the first non-whitespace character on a line + // namespace N { } #if false + TestBase.Diagnostic(ErrorCode.ERR_BadDirectivePlacement, "#").WithLocation(1, 17), + // (1,26): error CS1027: #endif directive expected + // namespace N { } #if false + TestBase.Diagnostic(ErrorCode.ERR_EndifDirectiveExpected, "").WithLocation(1, 26)); + Assert.False(compilationUnit.ContainsDirective(kind)); + } testContainsHelper1("#define x", SyntaxKind.DefineDirectiveTrivia); testContainsHelper1("#if true\r\n#elif true", SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElifDirectiveTrivia); diff --git a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb index ba6425d73b0e2..374ed12618a89 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/TestSyntaxNodes.vb @@ -2109,7 +2109,10 @@ End Module ' directive in trailing trivia Is Not a thing For currentKind = SyntaxKind.EmptyStatement To SyntaxKind.ConflictMarkerTrivia - Assert.False(SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace #if false").ContainsDirective(currentKind)) + Dim compilationUnit = SyntaxFactory.ParseCompilationUnit("namespace N" & vbCrLf & "end namespace #if false") + compilationUnit.GetDiagnostics().Verify( + Diagnostic(ERRID.ERR_ExpectedEOS, "#").WithLocation(2, 15)) + Assert.False(compilationUnit.ContainsDirective(currentKind)) Next Dim TestContainsHelper2 = Sub(directiveKinds As SyntaxKind(), compilationUnit As CompilationUnitSyntax) From e276b4df7c2692c661132826df139391374206d2 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Mon, 12 Dec 2022 16:57:33 -0800 Subject: [PATCH 091/274] Add a way to set whether we have full information or not --- .../Def/ProjectSystem/BrokeredService/WorkspaceProject.cs | 6 ++++++ .../Remote/Core/ProjectSystem/IWorkspaceProject.cs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs index 651bdba84e5a5..e3ea9d9c06dbd 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/BrokeredService/WorkspaceProject.cs @@ -154,6 +154,12 @@ public Task SetDisplayNameAsync(string displayName, CancellationToken cancellati return Task.CompletedTask; } + public Task SetProjectHasAllInformationAsync(bool hasAllInformation, CancellationToken cancellationToken) + { + _project.LastDesignTimeBuildSucceeded = hasAllInformation; + return Task.CompletedTask; + } + public Task StartBatchAsync(CancellationToken cancellationToken) { return Task.FromResult(_project.CreateBatchScope()); diff --git a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs index 46d04df81872a..7075210fe2bce 100644 --- a/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs +++ b/src/Workspaces/Remote/Core/ProjectSystem/IWorkspaceProject.cs @@ -36,5 +36,7 @@ internal interface IWorkspaceProject : IAsyncDisposable Task AddDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken); Task RemoveDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken); + Task SetProjectHasAllInformationAsync(bool hasAllInformation, CancellationToken cancellationToken); + Task StartBatchAsync(CancellationToken cancellationToken); } From c75c87363b8d330f274089244671ddf725805cbb Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 16 Sep 2022 17:43:05 +0530 Subject: [PATCH 092/274] Deprecate public constructors of various diagnostic analysis context types Fixes #63440 --- .../DiagnosticAnalysisContext.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs index 3e388bebf0909..11d2f2ff2446d 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs @@ -542,6 +542,7 @@ public readonly struct CompilationAnalysisContext /// public CancellationToken CancellationToken { get { return _cancellationToken; } } + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public CompilationAnalysisContext(Compilation compilation, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(compilation, options, reportDiagnostic, isSupportedDiagnostic, null, cancellationToken) { @@ -658,7 +659,7 @@ public readonly struct SemanticModelAnalysisContext /// public bool IsGeneratedCode { get; } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SemanticModelAnalysisContext(SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(semanticModel, options, reportDiagnostic, isSupportedDiagnostic, filterSpan: null, isGeneratedCode: false, cancellationToken) { @@ -736,7 +737,7 @@ public readonly struct SymbolAnalysisContext /// public bool IsGeneratedCode { get; } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SymbolAnalysisContext(ISymbol symbol, Compilation compilation, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(symbol, compilation, options, reportDiagnostic, isSupportedDiagnostic, isGeneratedCode: false, cancellationToken) { @@ -805,7 +806,7 @@ public abstract class SymbolStartAnalysisContext /// public CancellationToken CancellationToken { get; } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SymbolStartAnalysisContext(ISymbol symbol, Compilation compilation, AnalyzerOptions options, CancellationToken cancellationToken) : this(symbol, compilation, options, isGeneratedCode: false, cancellationToken) { @@ -954,7 +955,7 @@ public abstract class CodeBlockStartAnalysisContext where TLa /// public CancellationToken CancellationToken { get { return _cancellationToken; } } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] protected CodeBlockStartAnalysisContext(SyntaxNode codeBlock, ISymbol owningSymbol, SemanticModel semanticModel, AnalyzerOptions options, CancellationToken cancellationToken) : this(codeBlock, owningSymbol, semanticModel, options, isGeneratedCode: false, cancellationToken) { @@ -1049,7 +1050,7 @@ public readonly struct CodeBlockAnalysisContext /// public CancellationToken CancellationToken { get { return _cancellationToken; } } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public CodeBlockAnalysisContext(SyntaxNode codeBlock, ISymbol owningSymbol, SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(codeBlock, owningSymbol, semanticModel, options, reportDiagnostic, isSupportedDiagnostic, isGeneratedCode: false, cancellationToken) { @@ -1144,7 +1145,7 @@ public abstract class OperationBlockStartAnalysisContext /// public CancellationToken CancellationToken => _cancellationToken; - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] protected OperationBlockStartAnalysisContext( ImmutableArray operationBlocks, ISymbol owningSymbol, @@ -1269,7 +1270,7 @@ public readonly struct OperationBlockAnalysisContext /// public CancellationToken CancellationToken => _cancellationToken; - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public OperationBlockAnalysisContext( ImmutableArray operationBlocks, ISymbol owningSymbol, @@ -1372,7 +1373,7 @@ public readonly struct SyntaxTreeAnalysisContext internal Compilation? Compilation => _compilationOpt; - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SyntaxTreeAnalysisContext(SyntaxTree tree, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(tree, options, reportDiagnostic, isSupportedDiagnostic, compilation: null, isGeneratedCode: false, cancellationToken) { @@ -1519,13 +1520,13 @@ public readonly struct SyntaxNodeAnalysisContext /// public CancellationToken CancellationToken => _cancellationToken; - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SyntaxNodeAnalysisContext(SyntaxNode node, ISymbol? containingSymbol, SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(node, containingSymbol, semanticModel, options, reportDiagnostic, isSupportedDiagnostic, isGeneratedCode: false, cancellationToken) { } - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public SyntaxNodeAnalysisContext(SyntaxNode node, SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) : this(node, null, semanticModel, options, reportDiagnostic, isSupportedDiagnostic, isGeneratedCode: false, cancellationToken) { @@ -1610,7 +1611,7 @@ public readonly struct OperationAnalysisContext /// public CancellationToken CancellationToken => _cancellationToken; - // TODO: Mark obsolete, tracked with https://github.com/dotnet/roslyn/issues/63440 + [Obsolete("Use CompilationWithAnalyzers instead. See https://github.com/dotnet/roslyn/issues/63440 for more details.")] public OperationAnalysisContext( IOperation operation, ISymbol containingSymbol, From f2872e90126176f6777d459e48353aae4a0d0d01 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Thu, 8 Dec 2022 16:23:17 -0800 Subject: [PATCH 093/274] Remove a bunch of compatibility shims for TypeScript They moved off of this awhile ago, and we forgot to clean this up. --- .../FSharp/InternalAPI.Unshipped.txt | 8 +- .../FSharpVisualStudioWorkspaceExtensions.cs | 59 ++--- .../VSTypeScriptContainedDocumentWrapper.cs | 8 - .../Core/Def/ProjectSystem/AbstractProject.cs | 249 ------------------ .../Def/ProjectSystem/DocumentProvider.cs | 40 --- .../IVisualStudioHostDocument.cs | 25 -- .../ProjectSystem/IVisualStudioHostProject.cs | 17 -- .../VisualStudioProjectTracker.cs | 174 ------------ .../VisualStudioWorkspaceImpl.cs | 37 --- .../Def/Venus/AbstractContainedLanguage.cs | 24 -- .../Core/Def/Venus/ContainedDocument.cs | 22 +- 11 files changed, 25 insertions(+), 638 deletions(-) delete mode 100644 src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs delete mode 100644 src/VisualStudio/Core/Def/ProjectSystem/DocumentProvider.cs delete mode 100644 src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostDocument.cs delete mode 100644 src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostProject.cs delete mode 100644 src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectTracker.cs delete mode 100644 src/VisualStudio/Core/Def/Venus/AbstractContainedLanguage.cs diff --git a/src/Tools/ExternalAccess/FSharp/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/FSharp/InternalAPI.Unshipped.txt index 07acdb121915e..83c4fddec950b 100644 --- a/src/Tools/ExternalAccess/FSharp/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/FSharp/InternalAPI.Unshipped.txt @@ -382,6 +382,10 @@ static Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debugg static Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debugging.FSharpBreakpointResolutionResult.CreateSpanResult(Microsoft.CodeAnalysis.Document! document, Microsoft.CodeAnalysis.Text.TextSpan textSpan, string? locationNameOpt = null) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debugging.FSharpBreakpointResolutionResult! static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan.operator !=(Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan d1, Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan d2) -> bool static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan.operator ==(Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan d1, Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan d2) -> bool +static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace! workspace, string! fullPath, System.DateTime snapshotTimestamp) -> Microsoft.CodeAnalysis.Metadata! +static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetOrCreateProjectIdForPath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace! workspace, string! filePath, string! projectDisplayName) -> Microsoft.CodeAnalysis.ProjectId! +static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetProjectFilePath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace! workspace, Microsoft.CodeAnalysis.ProjectId! projectId) -> string? +static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.TryGetProjectIdByBinPath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace! workspace, string! filePath, out Microsoft.CodeAnalysis.ProjectId? projectId) -> bool ~abstract Microsoft.CodeAnalysis.ExternalAccess.FSharp.Completion.FSharpCommonCompletionProviderBase.GetTextChangeAsync(System.Func> baseGetTextChangeAsync, Microsoft.CodeAnalysis.Completion.CompletionItem selectedItem, char? ch, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task ~abstract Microsoft.CodeAnalysis.ExternalAccess.FSharp.Completion.FSharpCommonCompletionProviderBase.IsInsertionTrigger(Microsoft.CodeAnalysis.Text.SourceText text, int insertedCharacterPosition) -> bool ~abstract Microsoft.CodeAnalysis.ExternalAccess.FSharp.Completion.FSharpCommonCompletionProviderBase.ProvideCompletionsAsync(Microsoft.CodeAnalysis.Completion.CompletionContext context) -> System.Threading.Tasks.Task @@ -549,10 +553,6 @@ static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpDocumentSpan.operator ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FindUsages.FSharpDefinitionItem.CreateNonNavigableItem(System.Collections.Immutable.ImmutableArray tags, System.Collections.Immutable.ImmutableArray displayParts, System.Collections.Immutable.ImmutableArray originationParts) -> Microsoft.CodeAnalysis.ExternalAccess.FSharp.FindUsages.FSharpDefinitionItem ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpEditorFeaturesResources.You_cannot_rename_this_element.get -> string ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyphTags.GetTags(Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph glyph) -> System.Collections.Immutable.ImmutableArray -~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace workspace, string fullPath, System.DateTime snapshotTimestamp) -> Microsoft.CodeAnalysis.Metadata -~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetOrCreateProjectIdForPath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace workspace, string filePath, string projectDisplayName) -> Microsoft.CodeAnalysis.ProjectId -~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetProjectFilePath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace workspace, Microsoft.CodeAnalysis.ProjectId projectId) -> string -~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.TryGetProjectIdByBinPath(this Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace workspace, string filePath, out Microsoft.CodeAnalysis.ProjectId projectId) -> bool ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToItemKind.Class.get -> string ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToItemKind.Constant.get -> string ~static Microsoft.CodeAnalysis.ExternalAccess.FSharp.NavigateTo.FSharpNavigateToItemKind.Delegate.get -> string diff --git a/src/Tools/ExternalAccess/FSharp/LanguageServices/FSharpVisualStudioWorkspaceExtensions.cs b/src/Tools/ExternalAccess/FSharp/LanguageServices/FSharpVisualStudioWorkspaceExtensions.cs index 31328f45cb636..00bb1e2a144e5 100644 --- a/src/Tools/ExternalAccess/FSharp/LanguageServices/FSharpVisualStudioWorkspaceExtensions.cs +++ b/src/Tools/ExternalAccess/FSharp/LanguageServices/FSharpVisualStudioWorkspaceExtensions.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.VisualStudio.LanguageServices; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; @@ -14,59 +14,40 @@ internal static class FSharpVisualStudioWorkspaceExtensions { public static Metadata GetMetadata(this VisualStudioWorkspace workspace, string fullPath, DateTime snapshotTimestamp) { - var metadataReferenceProvider = workspace.Services.GetService(); + var metadataReferenceProvider = workspace.Services.GetRequiredService(); return metadataReferenceProvider.GetMetadata(fullPath, snapshotTimestamp); } [Obsolete("When Roslyn/ProjectSystem integration is finished, don't use this.")] - public static bool TryGetProjectIdByBinPath(this VisualStudioWorkspace workspace, string filePath, out ProjectId projectId) + public static bool TryGetProjectIdByBinPath(this VisualStudioWorkspace workspace, string filePath, [NotNullWhen(true)] out ProjectId? projectId) { - if (workspace is VisualStudioWorkspaceImpl) + var projects = workspace.CurrentSolution.Projects.Where(p => string.Equals(p.OutputFilePath, filePath, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (projects.Count == 1) + { + projectId = projects[0].Id; + return true; + } + else { - var impl = workspace as VisualStudioWorkspaceImpl; - if (impl.ProjectTracker.TryGetProjectByBinPath(filePath, out var project)) - { - projectId = project.Id; - return true; - } - else - { - projectId = null; - return false; - } + projectId = null; + return false; } - projectId = null; - return false; } [Obsolete("When Roslyn/ProjectSystem integration is finished, don't use this.")] public static ProjectId GetOrCreateProjectIdForPath(this VisualStudioWorkspace workspace, string filePath, string projectDisplayName) { - if (workspace is VisualStudioWorkspaceImpl) - { - var impl = workspace as VisualStudioWorkspaceImpl; - return impl.ProjectTracker.GetOrCreateProjectIdForPath(filePath, projectDisplayName); - } - return null; + // HACK: to keep F# working, we will ensure we return the ProjectId if there is a project that matches this path. Otherwise, we'll just return + // a random ProjectId, which is sufficient for their needs. They'll simply observe there is no project with that ID, and then go and create a + // new project. Then they call this function again, and fetch the real ID. + return workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == filePath)?.Id ?? ProjectId.CreateNewId("ProjectNotFound"); } [Obsolete("When Roslyn/ProjectSystem integration is finished, don't use this.")] - public static string GetProjectFilePath(this VisualStudioWorkspace workspace, ProjectId projectId) + public static string? GetProjectFilePath(this VisualStudioWorkspace workspace, ProjectId projectId) { - if (workspace is VisualStudioWorkspaceImpl) - { - var impl = workspace as VisualStudioWorkspaceImpl; - var project = impl.ProjectTracker.GetProject(projectId); - if (project != null) - { - return project.ProjectFilePath; - } - else - { - return null; - } - } - return null; + return workspace.CurrentSolution.GetProject(projectId)?.FilePath; } } } diff --git a/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptContainedDocumentWrapper.cs b/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptContainedDocumentWrapper.cs index 3a8b707fbe3ff..72b895369a4ad 100644 --- a/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptContainedDocumentWrapper.cs +++ b/src/VisualStudio/Core/Def/ExternalAccess/VSTypeScript/Api/VSTypeScriptContainedDocumentWrapper.cs @@ -45,13 +45,5 @@ public ITextBuffer SubjectBuffer public IVsContainedLanguageHost Host => _underlyingObject.ContainedLanguageHost; - - [Obsolete("Remove once TypeScript has stopped using this.")] - internal AbstractProject Project - => _underlyingObject.Project; - - [Obsolete("Remove once TypeScript has stopped using this.")] - internal IVisualStudioHostDocument HostDocument - => _underlyingObject; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs deleted file mode 100644 index 21b93d26e51be..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/AbstractProject.cs +++ /dev/null @@ -1,249 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; -using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; -using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - using Workspace = Microsoft.CodeAnalysis.Workspace; - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal abstract partial class AbstractProject : ForegroundThreadAffinitizedObject, IVisualStudioHostProject - { - internal const string ProjectGuidPropertyName = "ProjectGuid"; - - private string _displayName; - private readonly VisualStudioWorkspace _visualStudioWorkspace; - - public AbstractProject( - VisualStudioProjectTracker projectTracker, - Func reportExternalErrorCreatorOpt, - string projectSystemName, - string projectFilePath, - IVsHierarchy hierarchy, - string language, - Guid projectGuid, -#pragma warning disable IDE0060 // Remove unused parameter - not used, but left for compat with TypeScript - IServiceProvider serviceProviderNotUsed, -#pragma warning restore IDE0060 // Remove unused parameter - VisualStudioWorkspaceImpl workspace, - HostDiagnosticUpdateSource hostDiagnosticUpdateSourceOpt, -#pragma warning disable IDE0060 // Remove unused parameter - not used, but left for compat - ICommandLineParserService commandLineParserServiceOpt = null) -#pragma warning restore IDE0060 // Remove unused parameter - : base(projectTracker.ThreadingContext) - { - Hierarchy = hierarchy; - Guid = projectGuid; - Language = language; - ProjectTracker = projectTracker; - _visualStudioWorkspace = workspace; - - this.DisplayName = hierarchy != null && hierarchy.TryGetName(out var name) ? name : projectSystemName; - - ProjectSystemName = projectSystemName; - HostDiagnosticUpdateSource = hostDiagnosticUpdateSourceOpt; - - // Set the default value for last design time build result to be true, until the project system lets us know that it failed. - LastDesignTimeBuildSucceeded = true; - - if (projectFilePath != null && File.Exists(projectFilePath)) - { - ProjectFilePath = projectFilePath; - } - - if (ProjectFilePath != null) - { - Version = VersionStamp.Create(File.GetLastWriteTimeUtc(ProjectFilePath)); - } - else - { - Version = VersionStamp.Create(); - } - - if (reportExternalErrorCreatorOpt != null) - { - ExternalErrorReporter = reportExternalErrorCreatorOpt(Id); - } - } - - /// - /// A full path to the project bin output binary, or null if the project doesn't have an bin output binary. - /// - // FYI: this can't be made virtual because there are calls to this where a 'call' instead of 'callvirt' is being used to call - // the method. - internal string BinOutputPath => GetOutputFilePath(); - - protected virtual string GetOutputFilePath() - => VisualStudioProject.OutputFilePath; - - protected IVsReportExternalErrors ExternalErrorReporter { get; } - - internal HostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; } - - public virtual ProjectId Id => VisualStudioProject?.Id ?? ExplicitId; - - internal ProjectId ExplicitId { get; set; } - - public string Language { get; } - public VisualStudioProjectTracker ProjectTracker { get; } - - /// - /// The for this project. NOTE: May be null in Deferred Project Load cases. - /// - public IVsHierarchy Hierarchy { get; } - - /// - /// Guid of the project - /// - /// it is not readonly since it can be changed while loading project - /// - public Guid Guid { get; protected set; } - - public Workspace Workspace { get; } - - public VersionStamp Version { get; } - - public IProjectCodeModel ProjectCodeModel { get; protected set; } - - /// - /// The containing directory of the project. Null if none exists (consider Venus.) - /// - protected string ContainingDirectoryPathOpt - { - get - { - var projectFilePath = this.ProjectFilePath; - if (projectFilePath != null) - { - return Path.GetDirectoryName(projectFilePath); - } - else - { - return null; - } - } - } - - /// - /// The full path of the project file. Null if none exists (consider Venus.) - /// Note that the project file path might change with project file rename. - /// If you need the folder of the project, just use which doesn't change for a project. - /// - public string ProjectFilePath { get; private set; } - - /// - /// The public display name of the project. This name is not unique and may be shared - /// between multiple projects, especially in cases like Venus where the intellisense - /// projects will match the name of their logical parent project. - /// - public string DisplayName - { - get => _displayName; - set - { - _displayName = value; - - UpdateVisualStudioProjectProperties(); - } - } - - internal string AssemblyName { get; private set; } - - /// - /// The name of the project according to the project system. In "regular" projects this is - /// equivalent to , but in Venus cases these will differ. The - /// ProjectSystemName is the 2_Default.aspx project name, whereas the regular display name - /// matches the display name of the project the user actually sees in the solution explorer. - /// These can be assumed to be unique within the Visual Studio workspace. - /// - public string ProjectSystemName { get; } - - /// - /// Flag indicating if the latest design time build has succeeded for current project state. - /// - /// Default value is true. - protected bool LastDesignTimeBuildSucceeded { get; private set; } - -#nullable enable - - public ProjectSystemProject? VisualStudioProject { get; internal set; } - -#nullable disable - - internal void UpdateVisualStudioProjectProperties() - { - if (VisualStudioProject != null) - { - VisualStudioProject.DisplayName = this.DisplayName; - } - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - protected void UpdateProjectDisplayName(string displayName) - => this.DisplayName = displayName; - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal void AddDocument(IVisualStudioHostDocument document, bool isCurrentContext, bool hookupHandlers) - { - var shimDocument = (DocumentProvider.ShimDocument)document; - - VisualStudioProject.AddSourceFile(shimDocument.FilePath, shimDocument.SourceCodeKind); - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal void RemoveDocument(IVisualStudioHostDocument document) - { - var containedDocument = ContainedDocument.TryGetContainedDocument(document.Id); - if (containedDocument != null) - { - VisualStudioProject.RemoveSourceTextContainer(containedDocument.SubjectBuffer.AsTextContainer()); - containedDocument.Dispose(); - } - else - { - var shimDocument = (DocumentProvider.ShimDocument)document; - VisualStudioProject.RemoveSourceFile(shimDocument.FilePath); - } - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal IVisualStudioHostDocument GetCurrentDocumentFromPath(string filePath) - { - var id = _visualStudioWorkspace.CurrentSolution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(d => d.ProjectId == Id); - - if (id != null) - { - return new DocumentProvider.ShimDocument(this, id, filePath); - } - else - { - return null; - } - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal ImmutableArray GetCurrentDocuments() - { - return _visualStudioWorkspace.CurrentSolution.GetProject(Id).Documents.SelectAsArray( - d => (IVisualStudioHostDocument)new DocumentProvider.ShimDocument(this, d.Id, d.FilePath, d.SourceCodeKind)); - } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/DocumentProvider.cs b/src/VisualStudio/Core/Def/ProjectSystem/DocumentProvider.cs deleted file mode 100644 index 52fd01f102639..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/DocumentProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal sealed class DocumentProvider - { - internal class ShimDocument : IVisualStudioHostDocument - { - public ShimDocument(AbstractProject hostProject, DocumentId id, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) - { - Project = hostProject; - Id = id ?? DocumentId.CreateNewId(hostProject.Id, filePath); - FilePath = filePath; - SourceCodeKind = sourceCodeKind; - } - - public AbstractProject Project { get; } - - public DocumentId Id { get; } - - public string FilePath { get; } - - public SourceCodeKind SourceCodeKind { get; } - } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostDocument.cs b/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostDocument.cs deleted file mode 100644 index a6c7b6e802092..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostDocument.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Operations; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal interface IVisualStudioHostDocument - { - /// - /// The workspace document Id for this document. - /// - DocumentId Id { get; } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostProject.cs deleted file mode 100644 index 6746811f9f735..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/IVisualStudioHostProject.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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. - -#nullable disable - -using System; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal interface IVisualStudioHostProject - { - ProjectId Id { get; } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectTracker.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectTracker.cs deleted file mode 100644 index eaeaa45573cbb..0000000000000 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectTracker.cs +++ /dev/null @@ -1,174 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -{ -#pragma warning disable IDE0060 // Remove unused parameter - compatibility shim for TypeScript - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal sealed partial class VisualStudioProjectTracker - { - private readonly Workspace _workspace; - private readonly VisualStudioProjectFactory _projectFactory; - internal IThreadingContext ThreadingContext { get; } - - internal HostWorkspaceServices WorkspaceServices => _workspace.Services; - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - private readonly Dictionary _projects = new(); - - [Obsolete("This is a compatibility shim; please do not use it.")] - public VisualStudioProjectTracker(Workspace workspace, VisualStudioProjectFactory projectFactory, IThreadingContext threadingContext) - { - _workspace = workspace; - _projectFactory = projectFactory; - ThreadingContext = threadingContext; - DocumentProvider = new DocumentProvider(); - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - public DocumentProvider DocumentProvider { get; set; } - - public Workspace Workspace => _workspace; - - /* - - private void FinishLoad() - { - // Check that the set of analyzers is complete and consistent. - GetAnalyzerDependencyCheckingService()?.ReanalyzeSolutionForConflicts(); - } - - private AnalyzerDependencyCheckingService GetAnalyzerDependencyCheckingService() - { - var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); - - return componentModel.GetService(); - } - - */ - - public ProjectId GetOrCreateProjectIdForPath(string filePath, string projectDisplayName) - { - // HACK: to keep F# working, we will ensure we return the ProjectId if there is a project that matches this path. Otherwise, we'll just return - // a random ProjectId, which is sufficient for their needs. They'll simply observe there is no project with that ID, and then go and create a - // new project. Then they call this function again, and fetch the real ID. - return _workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == filePath)?.Id ?? ProjectId.CreateNewId("ProjectNotFound"); - } - - [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] - public AbstractProject GetProject(ProjectId projectId) - { - // HACK: if we have a TypeScript project, they expect to return the real thing deriving from AbstractProject - if (_projects.TryGetValue(projectId, out var typeScriptProject)) - { - return typeScriptProject; - } - - // HACK: to keep F# working, we will ensure that if there is a project with that ID, we will return a non-null value, otherwise we'll return null. - // It doesn't actually matter *what* the project is, so we'll just return something silly - var project = _workspace.CurrentSolution.GetProject(projectId); - - if (project != null) - { - return new StubProject(this, project); - } - else - { - return null; - } - } - - [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] - internal bool TryGetProjectByBinPath(string filePath, out AbstractProject project) - { - var projectsWithBinPath = _workspace.CurrentSolution.Projects.Where(p => string.Equals(p.OutputFilePath, filePath, StringComparison.OrdinalIgnoreCase)).ToList(); - - if (projectsWithBinPath.Count == 1) - { - project = new StubProject(this, projectsWithBinPath[0]); - return true; - } - else - { - project = null; - return false; - } - } - - [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] - private sealed class StubProject : AbstractProject - { - private readonly ProjectId _id; - - public StubProject(VisualStudioProjectTracker projectTracker, Project project) - : base(projectTracker, null, project.Name + "_Stub", project.FilePath, null, project.Language, Guid.Empty, null, null, null, null) - { - _id = project.Id; - } - - public override ProjectId Id => _id; - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - public void AddProject(AbstractProject project) - { - if (_projectFactory != null) - { - var creationInfo = new VisualStudioProjectCreationInfo - { - AssemblyName = project.AssemblyName, - FilePath = project.ProjectFilePath, - Hierarchy = project.Hierarchy, - ProjectGuid = project.Guid, - }; - project.VisualStudioProject = this.ThreadingContext.JoinableTaskFactory.Run(() => _projectFactory.CreateAndAddToWorkspaceAsync( - project.ProjectSystemName, project.Language, creationInfo, CancellationToken.None)); - project.UpdateVisualStudioProjectProperties(); - } - else - { - // We don't have an ID, so make something up - project.ExplicitId = ProjectId.CreateNewId(project.ProjectSystemName); - Workspace.OnProjectAdded(ProjectInfo.Create(project.ExplicitId, VersionStamp.Create(), project.ProjectSystemName, project.ProjectSystemName, project.Language)); - } - - _projects[project.Id] = project; - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - public bool ContainsProject(AbstractProject project) - { - // This will be set as long as the project has been added and not since removed - return _projects.Values.Contains(project); - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - public void RemoveProject(AbstractProject project) - { - _projects.Remove(project.Id); - - if (project.ExplicitId != null) - { - Workspace.OnProjectRemoved(project.ExplicitId); - } - else - { - project.VisualStudioProject.RemoveFromWorkspace(); - project.VisualStudioProject = null; - } - } - } -} diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs index 702a9640be895..62c0e687b27bd 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -68,9 +68,6 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly IProjectionBufferFactoryService _projectionBufferFactoryService; private readonly IGlobalOptionService _globalOptions; - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - private readonly Lazy _projectFactory; - private readonly ITextBufferCloneService _textBufferCloneService; /// @@ -101,9 +98,6 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac /// private readonly Dictionary _languageToProjectExistsUIContext = new(); - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal VisualStudioProjectTracker? _projectTracker; - private VirtualMemoryNotificationListener? _memoryListener; private OpenFileTracker? _openFileTracker; @@ -127,12 +121,6 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro _projectionBufferFactoryService = exportProvider.GetExportedValue(); _projectCodeModelFactory = exportProvider.GetExport(); - // We fetch this lazily because VisualStudioProjectFactory depends on VisualStudioWorkspaceImpl -- we have a circularity. Since this - // exists right now as a compat shim, we'll just do this. -#pragma warning disable CS0618 // Type or member is obsolete - _projectFactory = exportProvider.GetExport(); -#pragma warning restore CS0618 // Type or member is obsolete - _foregroundObject = new ForegroundThreadAffinitizedObject(_threadingContext); _textBufferFactoryService.TextBufferCreated += AddTextBufferCloneServiceToBuffer; @@ -260,23 +248,6 @@ internal void AddProjectRuleSetFileToInternalMaps(ProjectSystemProject project, Contract.ThrowIfFalse(ImmutableInterlocked.TryAdd(ref _projectToRuleSetFilePath, project.Id, ruleSetFilePathFunc)); } - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal VisualStudioProjectTracker GetProjectTrackerAndInitializeIfNecessary() - { - _projectTracker ??= new VisualStudioProjectTracker(this, _projectFactory.Value, _threadingContext); - - return _projectTracker; - } - - [Obsolete("This is a compatibility shim for TypeScript and F#; please do not use it.")] - internal VisualStudioProjectTracker ProjectTracker - { - get - { - return GetProjectTrackerAndInitializeIfNecessary(); - } - } - internal ProjectSystemProject? GetProjectWithHierarchyAndName(IVsHierarchy hierarchy, string projectName) { using (_gate.DisposableWait()) @@ -319,14 +290,6 @@ internal VisualStudioProjectTracker ProjectTracker } } - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal IVisualStudioHostDocument? GetHostDocument(DocumentId documentId) - { - // TypeScript only calls this to immediately check if the document is a ContainedDocument. Because of that we can just check for - // ContainedDocuments - return ContainedDocument.TryGetContainedDocument(documentId); - } - public override EnvDTE.FileCodeModel GetFileCodeModel(DocumentId documentId) { if (documentId == null) diff --git a/src/VisualStudio/Core/Def/Venus/AbstractContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/AbstractContainedLanguage.cs deleted file mode 100644 index 626bee6fad80a..0000000000000 --- a/src/VisualStudio/Core/Def/Venus/AbstractContainedLanguage.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TextManager.Interop; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus -{ - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal sealed class AbstractContainedLanguage - { - public AbstractContainedLanguage(IVsContainedLanguageHost host) - => ContainedLanguageHost = host; - - public IVsContainedLanguageHost ContainedLanguageHost { get; } - } -} diff --git a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs index bcf1b4e81286e..24e07a5b5eb1d 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedDocument.cs @@ -38,9 +38,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus { -#pragma warning disable CS0618 // Type or member is obsolete - internal sealed partial class ContainedDocument : ForegroundThreadAffinitizedObject, IVisualStudioHostDocument, IContainedDocument -#pragma warning restore CS0618 // Type or member is obsolete + internal sealed partial class ContainedDocument : ForegroundThreadAffinitizedObject, IContainedDocument { private const string ReturnReplacementString = @"{|r|}"; private const string NewLineReplacementString = @"{|n|}"; @@ -129,24 +127,6 @@ public ContainedDocument( s_containedDocuments.TryAdd(documentId, this); } - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal AbstractProject Project - { - get - { - return _componentModel.GetService().GetProjectTrackerAndInitializeIfNecessary().GetProject(_project.Id); - } - } - - [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] - internal AbstractContainedLanguage ContainedLanguage - { - get - { - return new AbstractContainedLanguage(ContainedLanguageHost); - } - } - private HostType GetHostType() { if (DataBuffer is IProjectionBuffer projectionBuffer) From 9d5b8d51bde5229faa2a6d13ff0f5084f8714d02 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 11:42:04 -0800 Subject: [PATCH 094/274] Optional ervice --- src/Workspaces/Core/Portable/Workspace/Workspace.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 28ad70c19c9a9..fd97c315eaa85 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -234,8 +234,8 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( var newSolution = data.transformation(oldSolution); // Attempt to unify the syntax trees in the new solution (unless the option is set disabling that). - var service = oldSolution.Services.GetRequiredService(); - if (service.Options.DisableSharedSyntaxTrees) + var options = oldSolution.Services.GetService()?.Options ?? WorkspaceConfigurationOptions.Default; + if (options.DisableSharedSyntaxTrees) return newSolution; return UnifyLinkedDocumentContents(oldSolution, newSolution); @@ -1051,8 +1051,8 @@ private void OnAnyDocumentTextChanged( // instance data that the initial document points at. This way things like tree data can be // shared across docs. - var service = oldSolution.Services.GetRequiredService(); - var shareSyntaxTrees = !service.Options.DisableSharedSyntaxTrees; + var options = oldSolution.Services.GetService()?.Options ?? WorkspaceConfigurationOptions.Default; + var shareSyntaxTrees = !options.DisableSharedSyntaxTrees; var newDocument = newSolution.GetRequiredDocument(data.documentId); foreach (var linkedDocumentId in linkedDocumentIds) From f9d14a82160faecd986e63f1419291842a383028 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 11:48:15 -0800 Subject: [PATCH 095/274] Add default service impl --- .../Options/WorkspaceConfigurationService.cs | 2 +- .../Workspace/IWorkspaceConfigurationService.cs | 15 +++++++++++++++ .../Core/Portable/Workspace/Workspace.cs | 4 ++-- .../RemoteWorkspaceConfigurationService.cs | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationService.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationService.cs index e42b269ae1d35..3b6ffa92508a1 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Host; -[ExportWorkspaceService(typeof(IWorkspaceConfigurationService)), Shared] +[ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Host), Shared] internal sealed class WorkspaceConfigurationService : IWorkspaceConfigurationService { private readonly IGlobalOptionService _globalOptions; diff --git a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs index 01176ec8aed89..a906c7bc86b97 100644 --- a/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs +++ b/src/Workspaces/Core/Portable/Workspace/IWorkspaceConfigurationService.cs @@ -2,7 +2,10 @@ // 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.Runtime.Serialization; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Storage; namespace Microsoft.CodeAnalysis.Host @@ -12,6 +15,18 @@ internal interface IWorkspaceConfigurationService : IWorkspaceService WorkspaceConfigurationOptions Options { get; } } + [ExportWorkspaceService(typeof(IWorkspaceConfigurationService)), Shared] + internal sealed class DefaultWorkspaceConfigurationService : IWorkspaceConfigurationService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DefaultWorkspaceConfigurationService() + { + } + + public WorkspaceConfigurationOptions Options => WorkspaceConfigurationOptions.Default; + } + /// /// Options that affect behavior of workspace core APIs (, , , , etc.) to which it would be impractical to flow these options diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index fd97c315eaa85..b4e932d378dea 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -234,7 +234,7 @@ internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( var newSolution = data.transformation(oldSolution); // Attempt to unify the syntax trees in the new solution (unless the option is set disabling that). - var options = oldSolution.Services.GetService()?.Options ?? WorkspaceConfigurationOptions.Default; + var options = oldSolution.Services.GetRequiredService().Options; if (options.DisableSharedSyntaxTrees) return newSolution; @@ -1051,7 +1051,7 @@ private void OnAnyDocumentTextChanged( // instance data that the initial document points at. This way things like tree data can be // shared across docs. - var options = oldSolution.Services.GetService()?.Options ?? WorkspaceConfigurationOptions.Default; + var options = oldSolution.Services.GetRequiredService().Options; var shareSyntaxTrees = !options.DisableSharedSyntaxTrees; var newDocument = newSolution.GetRequiredDocument(data.documentId); diff --git a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteWorkspaceConfigurationService.cs b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteWorkspaceConfigurationService.cs index 61824f13f6e54..5acccbc75aacb 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteWorkspaceConfigurationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ProcessTelemetry/RemoteWorkspaceConfigurationService.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.Host { - [ExportWorkspaceService(typeof(IWorkspaceConfigurationService)), Shared] + [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Host), Shared] internal sealed class RemoteWorkspaceConfigurationService : IWorkspaceConfigurationService { private WorkspaceConfigurationOptions? _options; From dbcfa1ea21ac4d24a2ec4e52c5abe3698a56eb1c Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2022 16:26:36 -0800 Subject: [PATCH 096/274] Update src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs Co-authored-by: Jason Malinowski --- src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 7d33a904f49b9..d3d1ea5648ded 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -70,7 +70,7 @@ private static Workspace CreateWorkspaceWithProjectAndLinkedDocuments( var workspace = CreateWorkspace(); - // note: despite teh additional-doc and analyzer-config doc being at the same path in multiple projects, + // note: despite the additional-doc and analyzer-config doc being at the same path in multiple projects, // they will still be treated as unique as the workspace only has the concept of linked docs for normal // docs. Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution From b8ffd0252313f2e744b3d072dca5b85ac492eb56 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 16:27:48 -0800 Subject: [PATCH 097/274] Use interlocked increment --- .../Solution/DocumentState_LinkedFileReuse.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index cb9deca32f4e1..eafaa6f03937d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -113,7 +113,7 @@ static bool TryReuseSiblingRoot( bool CanReuseSiblingRoot() { - s_tryReuseSyntaxTree++; + Interlocked.Increment(ref s_tryReuseSyntaxTree); var siblingParseOptions = siblingTree.Options; var ppSymbolsNames1 = parseOptions.PreprocessorSymbolNames; @@ -123,14 +123,14 @@ bool CanReuseSiblingRoot() // same trees. So we can trivially reuse the tree from one for the other. if (ppSymbolsNames1.SetEquals(ppSymbolsNames2)) { - s_couldReuseBecauseOfEqualPPNames++; + Interlocked.Increment(ref s_couldReuseBecauseOfEqualPPNames); return true; } // If the tree contains no `#` directives whatsoever, then you'll parse out the same tree and can reuse it. if (!siblingRoot.ContainsDirectives) { - s_couldReuseBecauseOfNoDirectives++; + Interlocked.Increment(ref s_couldReuseBecauseOfNoDirectives); return true; } @@ -139,7 +139,7 @@ bool CanReuseSiblingRoot() var syntaxKinds = languageServices.GetRequiredService(); if (!siblingRoot.ContainsDirective(syntaxKinds.IfDirectiveTrivia)) { - s_couldReuseBecauseOfNoPPDirectives++; + Interlocked.Increment(ref s_couldReuseBecauseOfNoPPDirectives); return true; } @@ -149,7 +149,7 @@ bool CanReuseSiblingRoot() // TODO(cyrusn): We could potentially be smarter here as well. We can check what pp-symbols the file // actually uses. (e.g. 'DEBUG'/'NETCORE'/etc.) and see if the project parse options actually differ // for these values. If not, we could reuse the trees even then. - s_couldNotReuse++; + Interlocked.Increment(ref s_couldNotReuse); return false; } } From 1c0fe77f4e3fe812eb262a26a35dccb749c2b838 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 16:33:35 -0800 Subject: [PATCH 098/274] add comment --- .../Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index eafaa6f03937d..40d17bc3238ee 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -199,6 +199,7 @@ static TreeAndVersion TryReuseSiblingTree( } } + // Values just kept around for benchmark tests. public static int s_tryReuseSyntaxTree; public static int s_couldNotReuse; public static int s_couldReuseBecauseOfEqualPPNames; From 0757308574e544f4a1c5af986f44c860b711616b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 16:41:09 -0800 Subject: [PATCH 099/274] Add test accessor --- .../IdeCoreBenchmarks/NavigateToBenchmarks.cs | 10 +++++----- .../Solution/DocumentState_LinkedFileReuse.cs | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs index aeb77e6a880e5..e3fb6376ad783 100644 --- a/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs +++ b/src/Tools/IdeCoreBenchmarks/NavigateToBenchmarks.cs @@ -129,11 +129,11 @@ public async Task RunSerialParsing() } Console.WriteLine("Serial: " + (DateTime.Now - start)); - Console.WriteLine($"{nameof(DocumentState.s_tryReuseSyntaxTree)} - {DocumentState.s_tryReuseSyntaxTree}"); - Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfEqualPPNames)} - {DocumentState.s_couldReuseBecauseOfEqualPPNames}"); - Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfNoDirectives)} - {DocumentState.s_couldReuseBecauseOfNoDirectives}"); - Console.WriteLine($"{nameof(DocumentState.s_couldReuseBecauseOfNoPPDirectives)} - {DocumentState.s_couldReuseBecauseOfNoPPDirectives}"); - Console.WriteLine($"{nameof(DocumentState.s_couldNotReuse)} - {DocumentState.s_couldNotReuse}"); + Console.WriteLine($"{nameof(DocumentState.TestAccessor.TryReuseSyntaxTree)} - {DocumentState.TestAccessor.TryReuseSyntaxTree}"); + Console.WriteLine($"{nameof(DocumentState.TestAccessor.CouldReuseBecauseOfEqualPPNames)} - {DocumentState.TestAccessor.CouldReuseBecauseOfEqualPPNames}"); + Console.WriteLine($"{nameof(DocumentState.TestAccessor.CouldReuseBecauseOfNoDirectives)} - {DocumentState.TestAccessor.CouldReuseBecauseOfNoDirectives}"); + Console.WriteLine($"{nameof(DocumentState.TestAccessor.CouldReuseBecauseOfNoPPDirectives)} - {DocumentState.TestAccessor.CouldReuseBecauseOfNoPPDirectives}"); + Console.WriteLine($"{nameof(DocumentState.TestAccessor.CouldNotReuse)} - {DocumentState.TestAccessor.CouldNotReuse}"); for (var i = 0; i < 10; i++) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 40d17bc3238ee..76b8a19b5c9c5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -200,10 +200,19 @@ static TreeAndVersion TryReuseSiblingTree( } // Values just kept around for benchmark tests. - public static int s_tryReuseSyntaxTree; - public static int s_couldNotReuse; - public static int s_couldReuseBecauseOfEqualPPNames; - public static int s_couldReuseBecauseOfNoDirectives; - public static int s_couldReuseBecauseOfNoPPDirectives; + private static int s_tryReuseSyntaxTree; + private static int s_couldNotReuse; + private static int s_couldReuseBecauseOfEqualPPNames; + private static int s_couldReuseBecauseOfNoDirectives; + private static int s_couldReuseBecauseOfNoPPDirectives; + + public struct TestAccessor + { + public static int TryReuseSyntaxTree => s_tryReuseSyntaxTree; + public static int CouldNotReuse => s_couldNotReuse; + public static int CouldReuseBecauseOfEqualPPNames => s_couldReuseBecauseOfEqualPPNames; + public static int CouldReuseBecauseOfNoDirectives => s_couldReuseBecauseOfNoDirectives; + public static int CouldReuseBecauseOfNoPPDirectives => s_couldReuseBecauseOfNoPPDirectives; + } } } From 5dbfd2026b46eb76594cf9fee052a94a913c3c1c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 16:59:40 -0800 Subject: [PATCH 100/274] Add docs --- .../Solution/DocumentState_LinkedFileReuse.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 76b8a19b5c9c5..ebc0d956e02cb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -111,6 +111,22 @@ static bool TryReuseSiblingRoot( newTreeAndVersion = new TreeAndVersion(newTree, siblingVersion); return true; + // Determines if the root of a tree from a different project can be used in this project. The general + // intuition (explained below) is that files without `#if` directives in them can be reused as the parse + // trees will be the same. + // + // This is *technically* not completely accurate as language-version can affect the parse tree as well. + // For example, `record X() { }` is a method prior to the addition of records to the language. However, + // in practice this should not be an issue. Specifically, either user code does not have a construct + // like this, in which case they are not affected by sharing. *Or*, they do have such a construct, but + // are being deliberately pathological. In other words, there are no realistic programs that depend on + // having one interpretation in one version, and another interpretation in another version. So we are + // ok saying we don't care about having that not work in the IDE (it will still work fine in the + // compiler). + // + // Note: we deliberately do not look at language version because it often is different across project + // flavors. So we would often get no benefit to sharing if we restricted to only when the lang version + // is the same. bool CanReuseSiblingRoot() { Interlocked.Increment(ref s_tryReuseSyntaxTree); From f07eed18d8d786143f10256179b259c7c4f4bd18 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 17:02:54 -0800 Subject: [PATCH 101/274] Rename method back --- .../Completion/CompletionServiceTests.cs | 2 +- ...emoveUnnecessaryPragmaSuppressionsTests.cs | 2 +- .../Core/Interactive/InteractiveSession.cs | 2 +- ...ractiveWorkspace.SolutionAnalyzerSetter.cs | 2 +- .../Core/Interactive/InteractiveWorkspace.cs | 2 +- .../CompileTimeSolutionProviderTests.cs | 4 +- .../EditAndContinueWorkspaceServiceTests.cs | 2 +- .../Ordering/RequestOrderingTests.cs | 2 +- ...spaceImpl.SolutionAnalyzerSetterService.cs | 2 +- .../Services/SolutionAssetCacheTests.cs | 2 +- .../Services/SolutionServiceTests.cs | 2 +- .../ProjectSystemProjectFactory.cs | 6 +- .../Core/Portable/Workspace/Workspace.cs | 70 +++++++++---------- .../SolutionWithSourceGeneratorTests.cs | 12 ++-- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs index 29b75ed20ad31..29432880c9997 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs @@ -171,7 +171,7 @@ public class C1 .AddAnalyzerReference(analyzerReference) .AddDocument("Document1.cs", sourceMarkup, filePath: "Document1.cs").Project; - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); var completionService = document.GetLanguageService(); diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs index 0cfa1fe3660a6..227ac0e5036f3 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/Suppression/RemoveUnnecessaryPragmaSuppressionsTests.cs @@ -1064,7 +1064,7 @@ void M() var compilationOptions = TestOptions.DebugDll.WithSpecificDiagnosticOptions( ImmutableDictionary.Empty .Add(IDEDiagnosticIds.RemoveUnnecessarySuppressionDiagnosticId, ReportDiagnostic.Suppress)); - workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithProjectCompilationOptions(projectId, compilationOptions), WorkspaceChangeKind.ProjectChanged, projectId); + workspace.SetCurrentSolution(s => s.WithProjectCompilationOptions(projectId, compilationOptions), WorkspaceChangeKind.ProjectChanged, projectId); var (actions, _) = await GetCodeActionsAsync(workspace, parameters); Assert.True(actions.Length == 0, "An action was offered when none was expected"); diff --git a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs index 0c272da207d4c..f13654f4f4e5f 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveSession.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveSession.cs @@ -224,7 +224,7 @@ private void AddSubmissionProjectNoLock(ITextBuffer submissionBuffer, string lan textDocument.Rename(newSubmissionFilePath); // Chain projects to the the last submission that successfully executed. - _workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => + _workspace.SetCurrentSolution(solution => { if (initializationScriptProjectId != null) { diff --git a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs index 801a1035e03db..24eab269c2826 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.SolutionAnalyzerSetter.cs @@ -34,7 +34,7 @@ public SolutionAnalyzerSetter(InteractiveWorkspace workspace) => _workspace = workspace; public void SetAnalyzerReferences(ImmutableArray references) - => _workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged); + => _workspace.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged); } } } diff --git a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs index 26d736c2ee9b7..2348187d602f1 100644 --- a/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs +++ b/src/EditorFeatures/Core/Interactive/InteractiveWorkspace.cs @@ -81,7 +81,7 @@ public void ResetSolution() ClearOpenDocuments(); var emptySolution = CreateSolution(SolutionId.CreateNewId("InteractiveSolution")); - SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => emptySolution.WithAnalyzerReferences(solution.AnalyzerReferences), WorkspaceChangeKind.SolutionCleared); + SetCurrentSolution(solution => emptySolution.WithAnalyzerReferences(solution.AnalyzerReferences), WorkspaceChangeKind.SolutionCleared); } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs b/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs index 3f2cb1814daac..f5cace812b627 100644 --- a/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/CompileTimeSolutionProviderTests.cs @@ -116,7 +116,7 @@ public async Task GeneratorOutputCachedBetweenAcrossCompileTimeSolutions() var analyzerConfigText = "is_global = true\r\nbuild_property.SuppressRazorSourceGenerator = true"; - workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s. + workspace.SetCurrentSolution(s => s. AddProject(ProjectInfo.Create(projectId, VersionStamp.Default, "proj", "proj", LanguageNames.CSharp)). AddAnalyzerReference(projectId, new TestGeneratorReference(generator)). AddAdditionalDocument(additionalDocumentId, "additional", SourceText.From(""), filePath: "additional.razor"). @@ -137,7 +137,7 @@ public async Task GeneratorOutputCachedBetweenAcrossCompileTimeSolutions() // Now do something that shouldn't force the generator to rerun; we must change this through the workspace since the // service itself uses versions that won't change otherwise var documentId = DocumentId.CreateNewId(projectId); - workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents( + workspace.SetCurrentSolution( s => s.AddDocument(documentId, "Test.cs", "// source file"), WorkspaceChangeKind.DocumentAdded, projectId, diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index d7e73457d34ec..3106342dbe8e8 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -4699,7 +4699,7 @@ public async Task DefaultPdbMatchingSourceTextProvider() loader: new WorkspaceFileTextLoader(workspace.Services.SolutionServices, sourceFile.Path, Encoding.UTF8), filePath: sourceFile.Path)); - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => solution, WorkspaceChangeKind.SolutionAdded)); + Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionAdded)); solution = workspace.CurrentSolution; var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs index 65bd974682834..4db0bb46b2269 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs @@ -209,7 +209,7 @@ public async Task NonMutatingRequestsOperateOnTheSameSolutionAfterMutation() Assert.Equal(expectedSolution, solution); // Apply some random change to the workspace that the LSP server doesn't "see" - testLspServer.TestWorkspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithProjectName(s.Projects.First().Id, "NewName"), WorkspaceChangeKind.ProjectChanged); + testLspServer.TestWorkspace.SetCurrentSolution(s => s.WithProjectName(s.Projects.First().Id, "NewName"), WorkspaceChangeKind.ProjectChanged); expectedSolution = testLspServer.GetCurrentSolution(); diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs index 69c00961665eb..88327564f95a9 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioWorkspaceImpl.SolutionAnalyzerSetterService.cs @@ -35,7 +35,7 @@ public SolutionAnalyzerSetter(VisualStudioWorkspaceImpl workspace) => _workspace = workspace; public void SetAnalyzerReferences(ImmutableArray references) - => _workspace.ProjectSystemProjectFactory.ApplyChangeToWorkspace(w => w.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); + => _workspace.ProjectSystemProjectFactory.ApplyChangeToWorkspace(w => w.SetCurrentSolution(s => s.WithAnalyzerReferences(references), WorkspaceChangeKind.SolutionChanged)); } } } diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs index 9b1b7a0128188..06ae77da3dfeb 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs @@ -100,7 +100,7 @@ public async Task TestSolutionKeepsAssetPinned() Assert.False(gotChecksum2); // Now, add a project. At this point, the original pinned object should go away. - workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(solution => solution.AddProject("Project", "Assembly", LanguageNames.CSharp).Solution, WorkspaceChangeKind.ProjectAdded); + workspace.SetCurrentSolution(solution => solution.AddProject("Project", "Assembly", LanguageNames.CSharp).Solution, WorkspaceChangeKind.ProjectAdded); for (var i = 0; i < 10; i++) { diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 77e998cc6c07e..c26a06654968a 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -192,7 +192,7 @@ static void ValidateProperties(Solution solution, int version) Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); await VerifySolutionUpdate(workspace, newSolutionGetter: s => SetProjectProperties(s, version: 1), diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs index 81085464b6494..efc95bd15920d 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.cs @@ -201,7 +201,7 @@ public void ApplyChangeToWorkspace(ProjectId projectId, Func solutionChanges.Solution, solutionChanges.WorkspaceChangeKind, solutionChanges.WorkspaceChangeProjectId, @@ -309,7 +309,7 @@ internal void RemoveSolution_NoLock() // to look up by that location; we also keep the existing analyzer references around since those are host-level analyzers that were loaded asynchronously. Workspace.ClearOpenDocuments(); - Workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents( + Workspace.SetCurrentSolution( solution => Workspace.CreateSolution( SolutionInfo.Create( SolutionId.CreateNewId(), diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index b4e932d378dea..459e9815a6e26 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -187,7 +187,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio } /// - internal bool SetCurrentSolutionAndUnifyLinkedDocumentContents( + internal bool SetCurrentSolution( Func transformation, WorkspaceChangeKind changeKind, ProjectId? projectId = null, @@ -410,7 +410,7 @@ public OptionSet Options internal void UpdateCurrentSolutionOnOptionsChanged() { - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => oldSolution.WithOptions(new SolutionOptionSet(_legacyOptions)), WorkspaceChangeKind.SolutionChanged); } @@ -548,7 +548,7 @@ private static Solution CheckAndAddProject(Solution newSolution, ProjectInfo pro /// protected internal void OnSolutionAdded(SolutionInfo solutionInfo) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckSolutionIsEmpty(oldSolution); @@ -567,7 +567,7 @@ protected internal void OnSolutionAdded(SolutionInfo solutionInfo) /// protected internal void OnSolutionReloaded(SolutionInfo reloadedSolutionInfo) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { var newSolution = this.CreateSolution(reloadedSolutionInfo); @@ -588,7 +588,7 @@ protected internal void OnSolutionReloaded(SolutionInfo reloadedSolutionInfo) /// protected internal void OnSolutionRemoved() { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( _ => this.CreateSolution(SolutionId.CreateNewId()), WorkspaceChangeKind.SolutionRemoved, onBeforeUpdate: (_, _) => this.ClearSolutionData()); @@ -599,7 +599,7 @@ protected internal void OnSolutionRemoved() /// protected internal void OnProjectAdded(ProjectInfo projectInfo) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => CheckAndAddProject(oldSolution, projectInfo), WorkspaceChangeKind.ProjectAdded, projectId: projectInfo.Id); } @@ -610,7 +610,7 @@ protected internal void OnProjectAdded(ProjectInfo projectInfo) protected internal virtual void OnProjectReloaded(ProjectInfo reloadedProjectInfo) { var projectId = reloadedProjectInfo.Id; - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckProjectIsInSolution(oldSolution, projectId); @@ -626,7 +626,7 @@ protected internal virtual void OnProjectReloaded(ProjectInfo reloadedProjectInf /// protected internal virtual void OnProjectRemoved(ProjectId projectId) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckProjectIsInSolution(oldSolution, projectId); @@ -655,19 +655,19 @@ protected virtual void CheckProjectCanBeRemoved(ProjectId projectId) /// Call this method when a project's assembly name is changed in the host environment. /// protected internal void OnAssemblyNameChanged(ProjectId projectId, string assemblyName) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectAssemblyName(projectId, assemblyName), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectAssemblyName(projectId, assemblyName), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's output file path is changed in the host environment. /// protected internal void OnOutputFilePathChanged(ProjectId projectId, string? outputFilePath) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectOutputFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectOutputFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's output ref file path is changed in the host environment. /// protected internal void OnOutputRefFilePathChanged(ProjectId projectId, string? outputFilePath) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectOutputRefFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectOutputRefFilePath(projectId, outputFilePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's name is changed in the host environment. @@ -677,32 +677,32 @@ protected internal void OnOutputRefFilePathChanged(ProjectId projectId, string? // I'm leaving this marked as "non-null" so as not to say we actually support that behavior. The underlying // requirement is ProjectInfo.ProjectAttributes holds a non-null name, so you can't get a null into this even if you tried. protected internal void OnProjectNameChanged(ProjectId projectId, string name, string? filePath) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectName(projectId, name).WithProjectFilePath(projectId, filePath), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectName(projectId, name).WithProjectFilePath(projectId, filePath), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's default namespace is changed in the host environment. /// internal void OnDefaultNamespaceChanged(ProjectId projectId, string? defaultNamespace) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectDefaultNamespace(projectId, defaultNamespace), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectDefaultNamespace(projectId, defaultNamespace), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's compilation options are changed in the host environment. /// protected internal void OnCompilationOptionsChanged(ProjectId projectId, CompilationOptions options) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectCompilationOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectCompilationOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's parse options are changed in the host environment. /// protected internal void OnParseOptionsChanged(ProjectId projectId, ParseOptions options) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithProjectParseOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithProjectParseOptions(projectId, options), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project reference is added to a project in the host environment. /// protected internal void OnProjectReferenceAdded(ProjectId projectId, ProjectReference projectReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectIsInCurrentSolution(projectReference.ProjectId); CheckProjectDoesNotHaveProjectReference(projectId, projectReference); @@ -719,7 +719,7 @@ protected internal void OnProjectReferenceAdded(ProjectId projectId, ProjectRefe /// protected internal void OnProjectReferenceRemoved(ProjectId projectId, ProjectReference projectReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectIsInCurrentSolution(projectReference.ProjectId); CheckProjectHasProjectReference(projectId, projectReference); @@ -733,7 +733,7 @@ protected internal void OnProjectReferenceRemoved(ProjectId projectId, ProjectRe /// protected internal void OnMetadataReferenceAdded(ProjectId projectId, MetadataReference metadataReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectDoesNotHaveMetadataReference(projectId, metadataReference); return oldSolution.AddMetadataReference(projectId, metadataReference); @@ -745,7 +745,7 @@ protected internal void OnMetadataReferenceAdded(ProjectId projectId, MetadataRe /// protected internal void OnMetadataReferenceRemoved(ProjectId projectId, MetadataReference metadataReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectHasMetadataReference(projectId, metadataReference); return oldSolution.RemoveMetadataReference(projectId, metadataReference); @@ -757,7 +757,7 @@ protected internal void OnMetadataReferenceRemoved(ProjectId projectId, Metadata /// protected internal void OnAnalyzerReferenceAdded(ProjectId projectId, AnalyzerReference analyzerReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectDoesNotHaveAnalyzerReference(projectId, analyzerReference); return oldSolution.AddAnalyzerReference(projectId, analyzerReference); @@ -769,7 +769,7 @@ protected internal void OnAnalyzerReferenceAdded(ProjectId projectId, AnalyzerRe /// protected internal void OnAnalyzerReferenceRemoved(ProjectId projectId, AnalyzerReference analyzerReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckProjectHasAnalyzerReference(projectId, analyzerReference); return oldSolution.RemoveAnalyzerReference(projectId, analyzerReference); @@ -781,7 +781,7 @@ protected internal void OnAnalyzerReferenceRemoved(ProjectId projectId, Analyzer /// internal void OnSolutionAnalyzerReferenceAdded(AnalyzerReference analyzerReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckSolutionDoesNotHaveAnalyzerReference(oldSolution, analyzerReference); return oldSolution.AddAnalyzerReference(analyzerReference); @@ -793,7 +793,7 @@ internal void OnSolutionAnalyzerReferenceAdded(AnalyzerReference analyzerReferen /// internal void OnSolutionAnalyzerReferenceRemoved(AnalyzerReference analyzerReference) { - SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => + SetCurrentSolution(oldSolution => { CheckSolutionHasAnalyzerReference(oldSolution, analyzerReference); return oldSolution.RemoveAnalyzerReference(analyzerReference); @@ -806,20 +806,20 @@ internal void OnSolutionAnalyzerReferenceRemoved(AnalyzerReference analyzerRefer /// // TODO: make it public internal void OnHasAllInformationChanged(ProjectId projectId, bool hasAllInformation) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithHasAllInformation(projectId, hasAllInformation), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithHasAllInformation(projectId, hasAllInformation), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a project's RunAnalyzers property is changed in the host environment. /// internal void OnRunAnalyzersChanged(ProjectId projectId, bool runAnalyzers) - => SetCurrentSolutionAndUnifyLinkedDocumentContents(oldSolution => oldSolution.WithRunAnalyzers(projectId, runAnalyzers), WorkspaceChangeKind.ProjectChanged, projectId); + => SetCurrentSolution(oldSolution => oldSolution.WithRunAnalyzers(projectId, runAnalyzers), WorkspaceChangeKind.ProjectChanged, projectId); /// /// Call this method when a document is added to a project in the host environment. /// protected internal void OnDocumentAdded(DocumentInfo documentInfo) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => oldSolution.AddDocument(documentInfo), WorkspaceChangeKind.DocumentAdded, documentId: documentInfo.Id); } @@ -847,7 +847,7 @@ protected internal void OnDocumentsAdded(ImmutableArray documentIn protected internal void OnDocumentReloaded(DocumentInfo newDocumentInfo) { var documentId = newDocumentInfo.Id; - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => oldSolution.RemoveDocument(documentId).AddDocument(newDocumentInfo), WorkspaceChangeKind.DocumentReloaded, documentId: documentId); } @@ -857,7 +857,7 @@ protected internal void OnDocumentReloaded(DocumentInfo newDocumentInfo) /// protected internal void OnDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -883,7 +883,7 @@ protected virtual void CheckDocumentCanBeRemoved(DocumentId documentId) /// protected internal void OnDocumentInfoChanged(DocumentId documentId, DocumentInfo newInfo) { - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -1099,7 +1099,7 @@ private void OnAnyDocumentTextChanged( /// protected internal void OnDocumentSourceCodeKindChanged(DocumentId documentId, SourceCodeKind sourceCodeKind) { - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => { CheckDocumentIsInSolution(oldSolution, documentId); @@ -1115,7 +1115,7 @@ protected internal void OnDocumentSourceCodeKindChanged(DocumentId documentId, S protected internal void OnAdditionalDocumentAdded(DocumentInfo documentInfo) { var documentId = documentInfo.Id; - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => { CheckProjectIsInSolution(oldSolution, documentId.ProjectId); @@ -1130,7 +1130,7 @@ protected internal void OnAdditionalDocumentAdded(DocumentInfo documentInfo) /// protected internal void OnAdditionalDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckAdditionalDocumentIsInSolution(oldSolution, documentId); @@ -1153,7 +1153,7 @@ protected internal void OnAdditionalDocumentRemoved(DocumentId documentId) protected internal void OnAnalyzerConfigDocumentAdded(DocumentInfo documentInfo) { var documentId = documentInfo.Id; - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => { CheckProjectIsInSolution(oldSolution, documentId.ProjectId); @@ -1169,7 +1169,7 @@ protected internal void OnAnalyzerConfigDocumentAdded(DocumentInfo documentInfo) /// protected internal void OnAnalyzerConfigDocumentRemoved(DocumentId documentId) { - this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + this.SetCurrentSolution( oldSolution => { CheckAnalyzerConfigDocumentIsInSolution(oldSolution, documentId); @@ -1190,7 +1190,7 @@ protected internal void OnAnalyzerConfigDocumentRemoved(DocumentId documentId) /// protected void UpdateReferencesAfterAdd() { - SetCurrentSolutionAndUnifyLinkedDocumentContents( + SetCurrentSolution( oldSolution => UpdateReferencesAfterAdd(oldSolution), WorkspaceChangeKind.SolutionChanged); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 0f74f8f93a1bc..cceca9efe2ed7 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -572,7 +572,7 @@ public async Task OpenSourceGeneratedUpdatedToBufferContentsWhenCallingGetOpenDo var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -596,7 +596,7 @@ public async Task OpenSourceGeneratedFileDoesNotCreateNewSnapshotIfContentsKnown var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// StaticContent", Encoding.UTF8).Container; @@ -616,7 +616,7 @@ public async Task OpenSourceGeneratedFileMatchesBufferContentsEvenIfGeneratedFil .AddAnalyzerReference(analyzerReference) .AddAdditionalDocument("Test.txt", SourceText.From("")); - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => originalAdditionalFile.Project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => originalAdditionalFile.Project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await originalAdditionalFile.Project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -647,7 +647,7 @@ public async Task OpenSourceGeneratedDocumentUpdatedAndVisibleInProjectReference solution = AddEmptyProject(solution).AddProjectReference( new ProjectReference(projectIdWithGenerator)).Solution; - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await workspace.CurrentSolution.GetRequiredProject(projectIdWithGenerator).GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -674,7 +674,7 @@ public async Task OpenSourceGeneratedDocumentsUpdateIsDocumentOpenAndCloseWorks( var project = AddEmptyProject(workspace.CurrentSolution) .AddAnalyzerReference(analyzerReference); - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var generatedDocument = Assert.Single(await project.GetSourceGeneratedDocumentsAsync()); var differentOpenTextContainer = SourceText.From("// Open Text").Container; @@ -703,7 +703,7 @@ public async Task FreezingSolutionEnsuresGeneratorsDoNotRun(bool forkBeforeFreez .AddAnalyzerReference(analyzerReference) .AddDocument("RegularDocument.cs", "// Source File", filePath: "RegularDocument.cs").Project; - Assert.True(workspace.SetCurrentSolutionAndUnifyLinkedDocumentContents(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); + Assert.True(workspace.SetCurrentSolution(_ => project.Solution, WorkspaceChangeKind.SolutionChanged)); var documentToFreeze = workspace.CurrentSolution.Projects.Single().Documents.Single(); From 9add813340df09910cbb88ff92d6008964c2a794 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 17:05:40 -0800 Subject: [PATCH 102/274] Rename method back --- src/Workspaces/Core/Portable/Workspace/Workspace.cs | 6 +++--- src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 459e9815a6e26..b28da4cf29ed8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -186,7 +186,7 @@ private protected (Solution oldSolution, Solution newSolution) SetCurrentSolutio } } - /// + /// internal bool SetCurrentSolution( Func transformation, WorkspaceChangeKind changeKind, @@ -195,7 +195,7 @@ internal bool SetCurrentSolution( Action? onBeforeUpdate = null, Action? onAfterUpdate = null) { - var (updated, _) = SetCurrentSolutionAndUnifyLinkedDocumentContents( + var (updated, _) = SetCurrentSolution( transformation, (_, _) => changeKind, projectId, @@ -220,7 +220,7 @@ internal bool SetCurrentSolution( /// the workspace change event. /// True if was set to the transformed solution, false if the /// transformation did not change the solution. - internal (bool updated, Solution newSolution) SetCurrentSolutionAndUnifyLinkedDocumentContents( + internal (bool updated, Solution newSolution) SetCurrentSolution( Func transformation, Func changeKind, ProjectId? projectId = null, diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index f2325a7414d60..ae74efc25a0d3 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -308,7 +308,7 @@ private async Task TryUpdateWorkspaceCurrentSolutionAsync( // Ensure we update newSolution with the result of SetCurrentSolution. It will be the one appropriately // 'attached' to this workspace. - (_, newSolution) = this.SetCurrentSolutionAndUnifyLinkedDocumentContents( + (_, newSolution) = this.SetCurrentSolution( _ => newSolution, changeKind: static (oldSolution, newSolution) => IsAddingSolution(oldSolution, newSolution) ? WorkspaceChangeKind.SolutionAdded From 52b33e5d4e767cf82a6314b9738342f88d31ef2c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 17:09:36 -0800 Subject: [PATCH 103/274] Lower accessibility --- src/Workspaces/Core/Portable/Workspace/Workspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index b28da4cf29ed8..e1f3670e80995 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -220,7 +220,7 @@ internal bool SetCurrentSolution( /// the workspace change event. /// True if was set to the transformed solution, false if the /// transformation did not change the solution. - internal (bool updated, Solution newSolution) SetCurrentSolution( + private protected (bool updated, Solution newSolution) SetCurrentSolution( Func transformation, Func changeKind, ProjectId? projectId = null, From d87f5de46c0e5b8118c8e3e990be07afd37077ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 17:23:54 -0800 Subject: [PATCH 104/274] Add comment --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index ebc0d956e02cb..adefbe6107513 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -101,6 +101,10 @@ static bool TryReuseSiblingRoot( var treeFactory = languageServices.GetRequiredService(); + // Note: passing along siblingTree.Encoding is a bit suspect. Ideally we would only populate this tree + // with our own data (*except* for the new root). However, we think it's safe as the encoding really is + // a property of the file, and that should stay the same even if linked into multiple projects. + var newTree = treeFactory.CreateSyntaxTree( filePath, parseOptions, From e61e0e9ac4b1e3de2a91c2881b7c529c4a101154 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 17:32:07 -0800 Subject: [PATCH 105/274] Add tests of parse options --- .../CoreTest/SolutionTests/SolutionTests.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index d3d1ea5648ded..3faa86c6c450c 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -562,10 +562,13 @@ public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_DifferentP PreservationMode mode, TextUpdateType updateType) { + var parseOptions1 = CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME"); + var parseOptions2 = CSharpParseOptions.Default; + using var workspace = CreateWorkspaceWithProjectAndLinkedDocuments(""" #if NETSTANDARD public class Goo { } - """, CSharpParseOptions.Default.WithPreprocessorSymbols("UNIQUE_NAME"), CSharpParseOptions.Default); + """, parseOptions1, parseOptions2); var solution = workspace.CurrentSolution; var documentId1 = solution.Projects.First().DocumentIds.Single(); @@ -588,6 +591,9 @@ public class Goo { } Assert.NotEqual(root1, root2); Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + Assert.Equal(parseOptions1, root1.SyntaxTree.Options); + Assert.Equal(parseOptions2, root2.SyntaxTree.Options); + // Because we removed pp directives, we'll be able to reuse after this. var text = SourceText.From("new text without pp directives", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); @@ -618,6 +624,9 @@ public class Goo { } Assert.NotEqual(root1, root2); Assert.False(root1.IsIncrementallyIdenticalTo(root2)); + Assert.Equal(parseOptions1, root1.SyntaxTree.Options); + Assert.Equal(parseOptions2, root2.SyntaxTree.Options); + // Now apply the change to the workspace. This should bring the linked document in sync with the one we changed. workspace.TryApplyChanges(solution); solution = workspace.CurrentSolution; @@ -638,6 +647,9 @@ public class Goo { } // We can reuse trees once they don't have conditional directives. Assert.NotEqual(root1, root2); Assert.True(root1.IsIncrementallyIdenticalTo(root2)); + + Assert.Equal(parseOptions1, root1.SyntaxTree.Options); + Assert.Equal(parseOptions2, root2.SyntaxTree.Options); } [Theory, CombinatorialData] From db82a9fb314308a73c6af41c8f9799512b2ff4d4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Dec 2022 18:00:28 -0800 Subject: [PATCH 106/274] Add helper --- .../CoreTest/SolutionTests/SolutionTests.cs | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 3faa86c6c450c..350b917d90898 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -426,13 +426,7 @@ public async Task WithDocumentText_LinkedFiles( var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - solution = updateType switch - { - TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), - TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), - TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), - _ => throw ExceptionUtilities.UnexpectedValue(updateType) - }; + solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion); // because we only forked one doc, the text/versions should be different in this interim solution. @@ -475,6 +469,18 @@ public async Task WithDocumentText_LinkedFiles( Assert.True(root1.IsIncrementallyIdenticalTo(root2)); } + private static Solution UpdateSolution(PreservationMode mode, TextUpdateType updateType, Solution solution, DocumentId documentId1, SourceText text, TextAndVersion textAndVersion) + { + solution = updateType switch + { + TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), + TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), + TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), + _ => throw ExceptionUtilities.UnexpectedValue(updateType) + }; + return solution; + } + [Theory, CombinatorialData] public async Task WithDocumentText_LinkedFiles_PPConditionalDirective_SameParseOptions( PreservationMode mode, @@ -508,13 +514,7 @@ public class Goo { } var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - solution = updateType switch - { - TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), - TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), - TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), - _ => throw ExceptionUtilities.UnexpectedValue(updateType) - }; + solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion); // because we only forked one doc, the text/versions should be different in this interim solution. @@ -597,13 +597,7 @@ public class Goo { } // Because we removed pp directives, we'll be able to reuse after this. var text = SourceText.From("new text without pp directives", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - solution = updateType switch - { - TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), - TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), - TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), - _ => throw ExceptionUtilities.UnexpectedValue(updateType) - }; + solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion); // because we only forked one doc, the text/versions should be different in this interim solution. @@ -686,13 +680,7 @@ public class Goo { } // Because we still have pp directives, we'll still not be able to reuse the file. var text = SourceText.From("#if true", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - solution = updateType switch - { - TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), - TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), - TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), - _ => throw ExceptionUtilities.UnexpectedValue(updateType) - }; + solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion); // because we only forked one doc, the text/versions should be different in this interim solution. @@ -768,13 +756,7 @@ public class Goo { } var text = SourceText.From("new text", encoding: null, SourceHashAlgorithm.Sha1); var textAndVersion = TextAndVersion.Create(text, VersionStamp.Create()); - solution = updateType switch - { - TextUpdateType.SourceText => solution.WithDocumentText(documentId1, text, mode), - TextUpdateType.TextAndVersion => solution.WithDocumentText(documentId1, textAndVersion, mode), - TextUpdateType.TextLoader => solution.WithDocumentTextLoader(documentId1, TextLoader.From(textAndVersion), mode), - _ => throw ExceptionUtilities.UnexpectedValue(updateType) - }; + solution = UpdateSolution(mode, updateType, solution, documentId1, text, textAndVersion); // because we only forked one doc, the text/versions should be different in this interim solution. From e277db284d63aa75d21f7de1772a6b3be715ecc6 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 14 Dec 2022 17:27:59 +0530 Subject: [PATCH 107/274] Fixes and improvements for Run Code Analysis command Fixes #26817 Fixes [AB#1698190](https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1698190) This PR fixes the regression where in diagnostics reported for a project/solution after executing `Run Code Analysis` command were getting cleared out after editing source files and/or switching active file tabs. While fixing the above, I also enhanced the `Run Code Analysis` to handle multi-tfm projects so that now we run analysis for all tfm flavors of a multi-tfm project instead of just the current tfm. --- ...gnosticIncrementalAnalyzer.AnalysisData.cs | 3 +++ .../DiagnosticIncrementalAnalyzer.Executor.cs | 23 +++++++++-------- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 25 ++++++++++--------- .../VisualStudioDiagnosticAnalyzerService.cs | 23 ++++++++++++++--- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs index d9c11caf65950..e72d4e5ade939 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs @@ -109,6 +109,9 @@ public ProjectAnalysisData( public DiagnosticAnalysisResult GetResult(DiagnosticAnalyzer analyzer) => GetResultOrEmpty(Result, analyzer, ProjectId, Version); + public bool TryGetResult(DiagnosticAnalyzer analyzer, out DiagnosticAnalysisResult result) + => Result.TryGetValue(analyzer, out result); + public static async Task CreateAsync(Project project, IEnumerable stateSets, bool avoidLoadingData, CancellationToken cancellationToken) { VersionStamp? version = null; diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 5ec74f649933c..553f6e54e12c3 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -24,8 +24,10 @@ internal partial class DiagnosticIncrementalAnalyzer { /// /// Return all cached local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer). - /// Also returns empty diagnostics for suppressed analyzer. - /// Returns null if the diagnostics need to be computed. + /// Otherwise, return null. + /// For the latter case, indicates if the analyzer is suppressed + /// for the given document/project. If suppressed, the caller does not need to compute the diagnostics for the given + /// analyzer. Otherwise, diagnostics need to be computed. /// private DocumentAnalysisData? TryGetCachedDocumentAnalysisData( TextDocument document, StateSet stateSet, @@ -34,10 +36,13 @@ internal partial class DiagnosticIncrementalAnalyzer CompilerDiagnosticsScope compilerDiagnosticsScope, bool isActiveDocument, bool isVisibleDocument, bool isOpenDocument, bool isGeneratedRazorDocument, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + out bool skipAnalysisForNonCachedDocument) { Debug.Assert(isActiveDocument || isOpenDocument || isGeneratedRazorDocument); + skipAnalysisForNonCachedDocument = false; + try { var state = stateSet.GetOrCreateActiveFileState(document.Id); @@ -48,14 +53,12 @@ internal partial class DiagnosticIncrementalAnalyzer return existingData; } - // Perf optimization: Check whether analyzer is suppressed for project or document and avoid getting diagnostics if suppressed. - if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || + // Check whether analyzer is suppressed for project or document and avoid getting diagnostics if suppressed. + // Note that we do not return empty DocumentAnalysisData for suppressed analyzer as that would clear the + // error list and remove the previously reported diagnostics from "Run Code Analysis" command. + skipAnalysisForNonCachedDocument = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || !IsAnalyzerEnabledForDocument(stateSet.Analyzer, existingData, analysisScope, compilerDiagnosticsScope, - isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument)) - { - return new DocumentAnalysisData(version, existingData.Items, ImmutableArray.Empty); - } - + isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument); return null; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 8406732d9f5a6..34d4cfa71d3dd 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -73,13 +73,12 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi { var data = TryGetCachedDocumentAnalysisData(document, stateSet, kind, version, backgroundAnalysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, - isOpenDocument, isGeneratedRazorDocument, cancellationToken); + isOpenDocument, isGeneratedRazorDocument, cancellationToken, out var skipAnalysisForNonCachedDocument); if (data.HasValue) { - // We need to persist and raise diagnostics for suppressed analyzer. PersistAndRaiseDiagnosticsIfNeeded(data.Value, stateSet); } - else + else if (!skipAnalysisForNonCachedDocument) { nonCachedStateSets.Add(stateSet); } @@ -156,23 +155,25 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C } var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, cancellationToken).ConfigureAwait(false); - if (result.OldResult == null) - { - RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.Result); - return; - } // no cancellation after this point. - // any analyzer that doesn't have result will be treated as returned empty set - // which means we will remove those from error list + using var _ = ArrayBuilder.GetInstance(out var analyzedStateSetsBuilder); foreach (var stateSet in stateSets) { var state = stateSet.GetOrCreateProjectState(project.Id); - await state.SaveToInMemoryStorageAsync(project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false); + if (result.TryGetResult(stateSet.Analyzer, out var analyzerResult)) + { + await state.SaveToInMemoryStorageAsync(project, analyzerResult).ConfigureAwait(false); + analyzedStateSetsBuilder.Add(stateSet); + } } - RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result); + if (analyzedStateSetsBuilder.Count > 0) + { + var oldResult = result.OldResult ?? ImmutableDictionary.Empty; + RaiseProjectDiagnosticsIfNeeded(project, analyzedStateSetsBuilder.ToImmutable(), oldResult, result.Result); + } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs index 65cfe498d5edd..4b76065d338df 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerService.cs @@ -307,6 +307,20 @@ public void RunAnalyzers(IVsHierarchy? hierarchy) var solution = _workspace.CurrentSolution; var projectOrSolutionName = project?.Name ?? PathUtilities.GetFileName(solution.FilePath); + // Handle multi-tfm projects - we want to run code analysis for all tfm flavors of the project. + ImmutableArray otherProjectsForMultiTfmProject; + if (project != null) + { + otherProjectsForMultiTfmProject = solution.Projects.Where( + p => p != project && p.FilePath == project.FilePath && p.State.NameAndFlavor.name == project.State.NameAndFlavor.name).ToImmutableArray(); + if (!otherProjectsForMultiTfmProject.IsEmpty) + projectOrSolutionName = project.State.NameAndFlavor.name; + } + else + { + otherProjectsForMultiTfmProject = ImmutableArray.Empty; + } + bool isAnalysisDisabled; if (project != null) { @@ -323,9 +337,9 @@ public void RunAnalyzers(IVsHierarchy? hierarchy) // Add a message to VS status bar that we are running code analysis. var statusBar = _serviceProvider?.GetService(typeof(SVsStatusbar)) as IVsStatusbar; - var totalProjectCount = project != null ? 1 : (uint)solution.ProjectIds.Count; + var totalProjectCount = project != null ? (1 + otherProjectsForMultiTfmProject.Length) : solution.ProjectIds.Count; var statusBarUpdater = statusBar != null - ? new StatusBarUpdater(statusBar, _threadingContext, projectOrSolutionName, totalProjectCount) + ? new StatusBarUpdater(statusBar, _threadingContext, projectOrSolutionName, (uint)totalProjectCount) : null; // Force complete analyzer execution in background. @@ -337,6 +351,9 @@ public void RunAnalyzers(IVsHierarchy? hierarchy) var onProjectAnalyzed = statusBarUpdater != null ? statusBarUpdater.OnProjectAnalyzed : (Action)((Project _) => { }); await _diagnosticService.ForceAnalyzeAsync(solution, onProjectAnalyzed, project?.Id, CancellationToken.None).ConfigureAwait(false); + foreach (var otherProject in otherProjectsForMultiTfmProject) + await _diagnosticService.ForceAnalyzeAsync(solution, onProjectAnalyzed, otherProject.Id, CancellationToken.None).ConfigureAwait(false); + // If user has disabled live analyzer execution for any project(s), i.e. set RunAnalyzersDuringLiveAnalysis = false, // then ForceAnalyzeAsync will not cause analyzers to execute. // We explicitly fetch diagnostics for such projects and report these as "Host" diagnostics. @@ -355,7 +372,7 @@ void HandleProjectsWithDisabledAnalysis() RoslynDebug.Assert(solution != null); // First clear all special host diagostics for all involved projects. - var projects = project != null ? SpecializedCollections.SingletonEnumerable(project) : solution.Projects; + var projects = project != null ? otherProjectsForMultiTfmProject.Add(project) : solution.Projects; foreach (var project in projects) { _hostDiagnosticUpdateSource.ClearDiagnosticsForProject(project.Id, key: this); From 42e629dc21c73513dfc521deb8933281f26acf7d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:05:18 -0800 Subject: [PATCH 108/274] Only examine a pereference once when walking projects --- .../FindReferences/DependentTypeFinder.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 2fe08d5457b4c..f34a8b49e0cd2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -112,6 +112,11 @@ private static async Task> DescendInheritanceTr // Same as above, but contains source types as well. using var _3 = GetSymbolSet(out var currentSourceAndMetadataTypes); + // The set of PEReferences we've examined. We only need to examine a reference once when we encounter it + // while walking projects. PEReferences cannot reference source symbols, so the results from them cannot + // change as we examine further projects. + using var _4 = PooledHashSet.GetInstance(out var seenPEReferences); + currentSourceAndMetadataTypes.Add(type); if (searchInMetadata) currentMetadataTypes.Add(type); @@ -131,7 +136,7 @@ private static async Task> DescendInheritanceTr { await DescendInheritanceTreeInProjectAsync( searchInMetadata, result, - currentMetadataTypes, currentSourceAndMetadataTypes, + currentMetadataTypes, currentSourceAndMetadataTypes, seenPEReferences, project, typeMatches, shouldContinueSearching, @@ -147,6 +152,7 @@ private static async Task DescendInheritanceTreeInProjectAsync( SymbolSet result, SymbolSet currentMetadataTypes, SymbolSet currentSourceAndMetadataTypes, + HashSet seenPEReferences, Project project, Func typeMatches, Func shouldContinueSearching, @@ -165,8 +171,9 @@ private static async Task DescendInheritanceTreeInProjectAsync( using var _ = GetSymbolSet(out var tempBuffer); await AddDescendantMetadataTypesInProjectAsync( - currentMetadataTypes, result: tempBuffer, + currentMetadataTypes, + seenPEReferences, project, typeMatches, shouldContinueSearching, @@ -355,8 +362,9 @@ private static ImmutableArray GetProjectsToExamineWorker( } private static async Task AddDescendantMetadataTypesInProjectAsync( - SymbolSet currentMetadataTypes, SymbolSet result, + SymbolSet currentMetadataTypes, + HashSet seenPEReferences, Project project, Func typeMatches, Func shouldContinueSearching, @@ -383,6 +391,10 @@ private static async Task AddDescendantMetadataTypesInProjectAsync( if (reference is not PortableExecutableReference peReference) continue; + // Don't look inside this reference if we already looked inside it in another project. + if (seenPEReferences.Contains(peReference)) + continue; + cancellationToken.ThrowIfCancellationRequested(); await AddMatchingMetadataTypesInMetadataReferenceAsync( @@ -394,6 +406,8 @@ await AddMatchingMetadataTypesInMetadataReferenceAsync( PropagateTemporaryResults( result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); } + + seenPEReferences.AddRange(compilation.References.OfType()); } private static async Task AddMatchingMetadataTypesInMetadataReferenceAsync( From cc6c11329d584c4fa3f5d6df11a54f23a9d9c51b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:06:20 -0800 Subject: [PATCH 109/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 130 ++++++++---------- 1 file changed, 56 insertions(+), 74 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index f34a8b49e0cd2..4e7028d50c45d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -133,91 +133,73 @@ private static async Task> DescendInheritanceTr cancellationToken.ThrowIfCancellationRequested(); if (project.SupportsCompilation) + await DescendInheritanceTreeInProjectAsync(project).ConfigureAwait(false); + } + + return result.ToImmutableArray(); + + async Task DescendInheritanceTreeInProjectAsync(Project project) + { + cancellationToken.ThrowIfCancellationRequested(); + + Debug.Assert(project.SupportsCompilation); + + // First see what derived metadata types we might find in this project. This is only necessary if we + // started with a metadata type (i.e. a source type could not have a descendant type found in metadata, + // but a metadata type could have descendant types in source and metadata). + if (searchInMetadata) { - await DescendInheritanceTreeInProjectAsync( - searchInMetadata, result, - currentMetadataTypes, currentSourceAndMetadataTypes, seenPEReferences, + using var _ = GetSymbolSet(out var tempBuffer); + + await AddDescendantMetadataTypesInProjectAsync( + result: tempBuffer, + currentMetadataTypes, + seenPEReferences, project, typeMatches, shouldContinueSearching, - transitive, cancellationToken).ConfigureAwait(false); + transitive, + cancellationToken).ConfigureAwait(false); + + // Add all the matches we found to the result set. + AssertContents(tempBuffer, assert: s_isInMetadata, "Found type was not from metadata"); + AddRange(tempBuffer, result); + + // Now, if we're doing a transitive search, add these found types to the 'current' sets we're + // searching for more results for. These will then be used when searching for more types in the next + // project (which our caller controls). + if (transitive) + { + AddRange(tempBuffer, currentMetadataTypes, shouldContinueSearching); + AddRange(tempBuffer, currentSourceAndMetadataTypes, shouldContinueSearching); + } } - } - return result.ToImmutableArray(); - } + { + using var _ = GetSymbolSet(out var tempBuffer); - private static async Task DescendInheritanceTreeInProjectAsync( - bool searchInMetadata, - SymbolSet result, - SymbolSet currentMetadataTypes, - SymbolSet currentSourceAndMetadataTypes, - HashSet seenPEReferences, - Project project, - Func typeMatches, - Func shouldContinueSearching, - bool transitive, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + // Now search the project and see what source types we can find. + await AddDescendantSourceTypesInProjectAsync( + currentSourceAndMetadataTypes, + result: tempBuffer, + project, + typeMatches, + shouldContinueSearching, + transitive, + cancellationToken).ConfigureAwait(false); - Debug.Assert(project.SupportsCompilation); + // Add all the matches we found to the result set. + AssertContents(tempBuffer, assert: s_isInSource, "Found type was not from source"); + AddRange(tempBuffer, result); - // First see what derived metadata types we might find in this project. This is only necessary if we - // started with a metadata type (i.e. a source type could not have a descendant type found in metadata, - // but a metadata type could have descendant types in source and metadata). - if (searchInMetadata) - { - using var _ = GetSymbolSet(out var tempBuffer); - - await AddDescendantMetadataTypesInProjectAsync( - result: tempBuffer, - currentMetadataTypes, - seenPEReferences, - project, - typeMatches, - shouldContinueSearching, - transitive, - cancellationToken).ConfigureAwait(false); - - // Add all the matches we found to the result set. - AssertContents(tempBuffer, assert: s_isInMetadata, "Found type was not from metadata"); - AddRange(tempBuffer, result); - - // Now, if we're doing a transitive search, add these found types to the 'current' sets we're - // searching for more results for. These will then be used when searching for more types in the next - // project (which our caller controls). - if (transitive) - { - AddRange(tempBuffer, currentMetadataTypes, shouldContinueSearching); - AddRange(tempBuffer, currentSourceAndMetadataTypes, shouldContinueSearching); + // Now, if we're doing a transitive search, add these types to the currentSourceAndMetadataTypes + // set. These will then be used when searching for more types in the next project (which our caller + // controls). We don't have to add this to currentMetadataTypes since these will all be + // source types. + if (transitive) + AddRange(tempBuffer, currentSourceAndMetadataTypes, shouldContinueSearching); } } - - { - using var _ = GetSymbolSet(out var tempBuffer); - - // Now search the project and see what source types we can find. - await AddDescendantSourceTypesInProjectAsync( - currentSourceAndMetadataTypes, - result: tempBuffer, - project, - typeMatches, - shouldContinueSearching, - transitive, - cancellationToken).ConfigureAwait(false); - - // Add all the matches we found to the result set. - AssertContents(tempBuffer, assert: s_isInSource, "Found type was not from source"); - AddRange(tempBuffer, result); - - // Now, if we're doing a transitive search, add these types to the currentSourceAndMetadataTypes - // set. These will then be used when searching for more types in the next project (which our caller - // controls). We don't have to add this to currentMetadataTypes since these will all be - // source types. - if (transitive) - AddRange(tempBuffer, currentSourceAndMetadataTypes, shouldContinueSearching); - } } [Conditional("DEBUG")] From 3592b0fda18f34e798463407685f3559bf38e12e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:08:21 -0800 Subject: [PATCH 110/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 100 ++++++++---------- 1 file changed, 42 insertions(+), 58 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 4e7028d50c45d..67f7b7277cc34 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -151,15 +151,7 @@ async Task DescendInheritanceTreeInProjectAsync(Project project) { using var _ = GetSymbolSet(out var tempBuffer); - await AddDescendantMetadataTypesInProjectAsync( - result: tempBuffer, - currentMetadataTypes, - seenPEReferences, - project, - typeMatches, - shouldContinueSearching, - transitive, - cancellationToken).ConfigureAwait(false); + await AddDescendantMetadataTypesInProjectAsync(tempBuffer, project).ConfigureAwait(false); // Add all the matches we found to the result set. AssertContents(tempBuffer, assert: s_isInMetadata, "Found type was not from metadata"); @@ -200,6 +192,47 @@ await AddDescendantSourceTypesInProjectAsync( AddRange(tempBuffer, currentSourceAndMetadataTypes, shouldContinueSearching); } } + + async Task AddDescendantMetadataTypesInProjectAsync(SymbolSet result, Project project) + { + Debug.Assert(project.SupportsCompilation); + + if (currentMetadataTypes.Count == 0) + return; + + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + + using var _1 = GetSymbolSet(out var typesToSearchFor); + using var _2 = GetSymbolSet(out var tempBuffer); + + typesToSearchFor.AddAll(currentMetadataTypes); + + // As long as there are new types to search for, keep looping. + while (typesToSearchFor.Count > 0) + { + foreach (var reference in compilation.References) + { + if (reference is not PortableExecutableReference peReference) + continue; + + // Don't look inside this reference if we already looked inside it in another project. + if (seenPEReferences.Contains(peReference)) + continue; + + cancellationToken.ThrowIfCancellationRequested(); + + await AddMatchingMetadataTypesInMetadataReferenceAsync( + typesToSearchFor, project, typeMatches, + compilation, peReference, tempBuffer, + cancellationToken).ConfigureAwait(false); + } + + PropagateTemporaryResults( + result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); + } + + seenPEReferences.AddRange(compilation.References.OfType()); + } } [Conditional("DEBUG")] @@ -343,55 +376,6 @@ private static ImmutableArray GetProjectsToExamineWorker( .ToImmutableArray(); } - private static async Task AddDescendantMetadataTypesInProjectAsync( - SymbolSet result, - SymbolSet currentMetadataTypes, - HashSet seenPEReferences, - Project project, - Func typeMatches, - Func shouldContinueSearching, - bool transitive, - CancellationToken cancellationToken) - { - Debug.Assert(project.SupportsCompilation); - - if (currentMetadataTypes.Count == 0) - return; - - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - - using var _1 = GetSymbolSet(out var typesToSearchFor); - using var _2 = GetSymbolSet(out var tempBuffer); - - typesToSearchFor.AddAll(currentMetadataTypes); - - // As long as there are new types to search for, keep looping. - while (typesToSearchFor.Count > 0) - { - foreach (var reference in compilation.References) - { - if (reference is not PortableExecutableReference peReference) - continue; - - // Don't look inside this reference if we already looked inside it in another project. - if (seenPEReferences.Contains(peReference)) - continue; - - cancellationToken.ThrowIfCancellationRequested(); - - await AddMatchingMetadataTypesInMetadataReferenceAsync( - typesToSearchFor, project, typeMatches, - compilation, peReference, tempBuffer, - cancellationToken).ConfigureAwait(false); - } - - PropagateTemporaryResults( - result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); - } - - seenPEReferences.AddRange(compilation.References.OfType()); - } - private static async Task AddMatchingMetadataTypesInMetadataReferenceAsync( SymbolSet metadataTypes, Project project, From df8568e0685ef5df8ace04f381201156ffd04bdc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:10:43 -0800 Subject: [PATCH 111/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 67f7b7277cc34..424ada81a5b76 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -222,9 +222,7 @@ async Task AddDescendantMetadataTypesInProjectAsync(SymbolSet result, Project pr cancellationToken.ThrowIfCancellationRequested(); await AddMatchingMetadataTypesInMetadataReferenceAsync( - typesToSearchFor, project, typeMatches, - compilation, peReference, tempBuffer, - cancellationToken).ConfigureAwait(false); + typesToSearchFor, project, compilation, peReference, tempBuffer).ConfigureAwait(false); } PropagateTemporaryResults( @@ -233,6 +231,47 @@ await AddMatchingMetadataTypesInMetadataReferenceAsync( seenPEReferences.AddRange(compilation.References.OfType()); } + + async Task AddMatchingMetadataTypesInMetadataReferenceAsync( + SymbolSet metadataTypes, + Project project, + Compilation compilation, + PortableExecutableReference reference, + SymbolSet result) + { + cancellationToken.ThrowIfCancellationRequested(); + + // We store an index in SymbolTreeInfo of the *simple* metadata type name to the names of the all the types + // that either immediately derive or implement that type. Because the mapping is from the simple name we + // might get false positives. But that's fine as we still use 'tpeMatches' to make sure the match is + // correct. + var symbolTreeInfo = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( + project.Solution, reference, checksum: null, cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfNull(symbolTreeInfo); + + // For each type we care about, see if we can find any derived types + // in this index. + foreach (var metadataType in metadataTypes) + { + cancellationToken.ThrowIfCancellationRequested(); + + var baseTypeName = metadataType.Name; + + // For each derived type we find, see if we can map that back + // to an actual symbol. Then check if that symbol actually fits + // our criteria. + foreach (var derivedType in symbolTreeInfo.GetDerivedMetadataTypes(baseTypeName, compilation, cancellationToken)) + { + if (derivedType != null && + derivedType.Locations.Any(s_isInMetadata) && + typeMatches(derivedType, metadataTypes)) + { + result.Add(derivedType); + } + } + } + } } [Conditional("DEBUG")] @@ -376,49 +415,6 @@ private static ImmutableArray GetProjectsToExamineWorker( .ToImmutableArray(); } - private static async Task AddMatchingMetadataTypesInMetadataReferenceAsync( - SymbolSet metadataTypes, - Project project, - Func typeMatches, - Compilation compilation, - PortableExecutableReference reference, - SymbolSet result, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // We store an index in SymbolTreeInfo of the *simple* metadata type name to the names of the all the types - // that either immediately derive or implement that type. Because the mapping is from the simple name we - // might get false positives. But that's fine as we still use 'tpeMatches' to make sure the match is - // correct. - var symbolTreeInfo = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, reference, checksum: null, cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfNull(symbolTreeInfo); - - // For each type we care about, see if we can find any derived types - // in this index. - foreach (var metadataType in metadataTypes) - { - cancellationToken.ThrowIfCancellationRequested(); - - var baseTypeName = metadataType.Name; - - // For each derived type we find, see if we can map that back - // to an actual symbol. Then check if that symbol actually fits - // our criteria. - foreach (var derivedType in symbolTreeInfo.GetDerivedMetadataTypes(baseTypeName, compilation, cancellationToken)) - { - if (derivedType != null && - derivedType.Locations.Any(s_isInMetadata) && - typeMatches(derivedType, metadataTypes)) - { - result.Add(derivedType); - } - } - } - } - private static bool TypeHasBaseTypeInSet(INamedTypeSymbol type, SymbolSet set) { var baseType = type.BaseType?.OriginalDefinition; From 8f04e1a740edf51674ab9b77fc54c98fa661c3bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:12:08 -0800 Subject: [PATCH 112/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 166 ++++++++---------- 1 file changed, 76 insertions(+), 90 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 424ada81a5b76..e7b7e1aee9815 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -171,14 +171,7 @@ async Task DescendInheritanceTreeInProjectAsync(Project project) using var _ = GetSymbolSet(out var tempBuffer); // Now search the project and see what source types we can find. - await AddDescendantSourceTypesInProjectAsync( - currentSourceAndMetadataTypes, - result: tempBuffer, - project, - typeMatches, - shouldContinueSearching, - transitive, - cancellationToken).ConfigureAwait(false); + await AddDescendantSourceTypesInProjectAsync(tempBuffer, project).ConfigureAwait(false); // Add all the matches we found to the result set. AssertContents(tempBuffer, assert: s_isInSource, "Found type was not from source"); @@ -193,6 +186,81 @@ await AddDescendantSourceTypesInProjectAsync( } } + async Task AddDescendantSourceTypesInProjectAsync(SymbolSet result, Project project) + { + cancellationToken.ThrowIfCancellationRequested(); + + // We're going to be sweeping over this project over and over until we reach a + // fixed point. In order to limit GC and excess work, we cache all the semantic + // models and DeclaredSymbolInfo for the documents we look at. + // Because we're only processing a project at a time, this is not an issue. + var cachedModels = new ConcurrentSet(); + + using var _1 = GetSymbolSet(out var typesToSearchFor); + using var _2 = GetSymbolSet(out var tempBuffer); + + typesToSearchFor.AddAll(currentSourceAndMetadataTypes); + + var projectIndex = await ProjectIndex.GetIndexAsync(project, cancellationToken).ConfigureAwait(false); + + // As long as there are new types to search for, keep looping. + while (typesToSearchFor.Count > 0) + { + foreach (var type in typesToSearchFor) + { + cancellationToken.ThrowIfCancellationRequested(); + + switch (type.SpecialType) + { + case SpecialType.System_Object: + await AddMatchingTypesAsync( + cachedModels, + projectIndex.ClassesAndRecordsThatMayDeriveFromSystemObject, + result: tempBuffer, + predicateOpt: n => n.BaseType?.SpecialType == SpecialType.System_Object, + cancellationToken).ConfigureAwait(false); + break; + case SpecialType.System_ValueType: + await AddMatchingTypesAsync( + cachedModels, + projectIndex.ValueTypes, + result: tempBuffer, + predicateOpt: null, + cancellationToken).ConfigureAwait(false); + break; + case SpecialType.System_Enum: + await AddMatchingTypesAsync( + cachedModels, + projectIndex.Enums, + result: tempBuffer, + predicateOpt: null, + cancellationToken).ConfigureAwait(false); + break; + case SpecialType.System_MulticastDelegate: + await AddMatchingTypesAsync( + cachedModels, + projectIndex.Delegates, + result: tempBuffer, + predicateOpt: null, + cancellationToken).ConfigureAwait(false); + break; + } + + await AddSourceTypesThatDeriveFromNameAsync( + typeMatches, + cachedModels, + typesToSearchFor, + projectIndex, + result: tempBuffer, + type.Name, + cancellationToken).ConfigureAwait(false); + } + + PropagateTemporaryResults( + result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); + } + } + async Task AddDescendantMetadataTypesInProjectAsync(SymbolSet result, Project project) { Debug.Assert(project.SupportsCompilation); @@ -432,88 +500,6 @@ private static bool TypeHasInterfaceInSet(INamedTypeSymbol type, SymbolSet set) return false; } - private static async Task AddDescendantSourceTypesInProjectAsync( - SymbolSet currentSourceAndMetadataTypes, - SymbolSet result, - Project project, - Func typeMatches, - Func shouldContinueSearching, - bool transitive, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // We're going to be sweeping over this project over and over until we reach a - // fixed point. In order to limit GC and excess work, we cache all the semantic - // models and DeclaredSymbolInfo for the documents we look at. - // Because we're only processing a project at a time, this is not an issue. - var cachedModels = new ConcurrentSet(); - - using var _1 = GetSymbolSet(out var typesToSearchFor); - using var _2 = GetSymbolSet(out var tempBuffer); - - typesToSearchFor.AddAll(currentSourceAndMetadataTypes); - - var projectIndex = await ProjectIndex.GetIndexAsync(project, cancellationToken).ConfigureAwait(false); - - // As long as there are new types to search for, keep looping. - while (typesToSearchFor.Count > 0) - { - foreach (var type in typesToSearchFor) - { - cancellationToken.ThrowIfCancellationRequested(); - - switch (type.SpecialType) - { - case SpecialType.System_Object: - await AddMatchingTypesAsync( - cachedModels, - projectIndex.ClassesAndRecordsThatMayDeriveFromSystemObject, - result: tempBuffer, - predicateOpt: n => n.BaseType?.SpecialType == SpecialType.System_Object, - cancellationToken).ConfigureAwait(false); - break; - case SpecialType.System_ValueType: - await AddMatchingTypesAsync( - cachedModels, - projectIndex.ValueTypes, - result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); - break; - case SpecialType.System_Enum: - await AddMatchingTypesAsync( - cachedModels, - projectIndex.Enums, - result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); - break; - case SpecialType.System_MulticastDelegate: - await AddMatchingTypesAsync( - cachedModels, - projectIndex.Delegates, - result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); - break; - } - - await AddSourceTypesThatDeriveFromNameAsync( - typeMatches, - cachedModels, - typesToSearchFor, - projectIndex, - result: tempBuffer, - type.Name, - cancellationToken).ConfigureAwait(false); - } - - PropagateTemporaryResults( - result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); - } - } - /// /// Moves all the types in to . If these are types we /// haven't seen before, and the caller says we on them, then add From 6881b51e71f77552279a710846bbe3a16816f055 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:16:06 -0800 Subject: [PATCH 113/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index e7b7e1aee9815..2798718707271 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -214,35 +214,27 @@ async Task AddDescendantSourceTypesInProjectAsync(SymbolSet result, Project proj { case SpecialType.System_Object: await AddMatchingTypesAsync( - cachedModels, projectIndex.ClassesAndRecordsThatMayDeriveFromSystemObject, result: tempBuffer, - predicateOpt: n => n.BaseType?.SpecialType == SpecialType.System_Object, - cancellationToken).ConfigureAwait(false); + predicateOpt: n => n.BaseType?.SpecialType == SpecialType.System_Object).ConfigureAwait(false); break; case SpecialType.System_ValueType: await AddMatchingTypesAsync( - cachedModels, projectIndex.ValueTypes, result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); + predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_Enum: await AddMatchingTypesAsync( - cachedModels, projectIndex.Enums, result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); + predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_MulticastDelegate: await AddMatchingTypesAsync( - cachedModels, projectIndex.Delegates, result: tempBuffer, - predicateOpt: null, - cancellationToken).ConfigureAwait(false); + predicateOpt: null).ConfigureAwait(false); break; } @@ -259,6 +251,36 @@ await AddSourceTypesThatDeriveFromNameAsync( PropagateTemporaryResults( result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); } + + async Task AddMatchingTypesAsync( + MultiDictionary documentToInfos, + SymbolSet result, + Func? predicateOpt) + { + foreach (var (document, infos) in documentToInfos) + { + cancellationToken.ThrowIfCancellationRequested(); + + Debug.Assert(infos.Count > 0); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + cachedModels.Add(semanticModel); + + foreach (var info in infos) + { + cancellationToken.ThrowIfCancellationRequested(); + + var resolvedSymbol = info.TryResolve(semanticModel, cancellationToken); + if (resolvedSymbol is INamedTypeSymbol namedType) + { + if (predicateOpt == null || + predicateOpt(namedType)) + { + result.Add(namedType); + } + } + } + } + } } async Task AddDescendantMetadataTypesInProjectAsync(SymbolSet result, Project project) @@ -556,38 +578,6 @@ private static async Task AddSourceTypesThatDeriveFromNameAsync( } } - private static async Task AddMatchingTypesAsync( - ConcurrentSet cachedModels, - MultiDictionary documentToInfos, - SymbolSet result, - Func? predicateOpt, - CancellationToken cancellationToken) - { - foreach (var (document, infos) in documentToInfos) - { - cancellationToken.ThrowIfCancellationRequested(); - - Debug.Assert(infos.Count > 0); - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - cachedModels.Add(semanticModel); - - foreach (var info in infos) - { - cancellationToken.ThrowIfCancellationRequested(); - - var resolvedSymbol = info.TryResolve(semanticModel, cancellationToken); - if (resolvedSymbol is INamedTypeSymbol namedType) - { - if (predicateOpt == null || - predicateOpt(namedType)) - { - result.Add(namedType); - } - } - } - } - } - public static PooledDisposer> GetSymbolSet(out SymbolSet instance) { var pooledInstance = s_symbolSetPool.Allocate(); From 94c2b7ff9a97f7fb905b45d5e5968e07dcd20d62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:19:02 -0800 Subject: [PATCH 114/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 60 +++++++------------ 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 2798718707271..d30f01658bf8b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -215,37 +215,30 @@ async Task AddDescendantSourceTypesInProjectAsync(SymbolSet result, Project proj case SpecialType.System_Object: await AddMatchingTypesAsync( projectIndex.ClassesAndRecordsThatMayDeriveFromSystemObject, - result: tempBuffer, + tempBuffer, predicateOpt: n => n.BaseType?.SpecialType == SpecialType.System_Object).ConfigureAwait(false); break; case SpecialType.System_ValueType: await AddMatchingTypesAsync( projectIndex.ValueTypes, - result: tempBuffer, + tempBuffer, predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_Enum: await AddMatchingTypesAsync( projectIndex.Enums, - result: tempBuffer, + tempBuffer, predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_MulticastDelegate: await AddMatchingTypesAsync( projectIndex.Delegates, - result: tempBuffer, + tempBuffer, predicateOpt: null).ConfigureAwait(false); break; } - await AddSourceTypesThatDeriveFromNameAsync( - typeMatches, - cachedModels, - typesToSearchFor, - projectIndex, - result: tempBuffer, - type.Name, - cancellationToken).ConfigureAwait(false); + await AddSourceTypesThatDeriveFromNameAsync(tempBuffer, type.Name).ConfigureAwait(false); } PropagateTemporaryResults( @@ -281,6 +274,24 @@ async Task AddMatchingTypesAsync( } } } + + async Task AddSourceTypesThatDeriveFromNameAsync(SymbolSet result, string name) + { + foreach (var (document, info) in projectIndex.NamedTypes[name]) + { + cancellationToken.ThrowIfCancellationRequested(); + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + cachedModels.Add(semanticModel); + + var resolvedType = info.TryResolve(semanticModel, cancellationToken); + if (resolvedType is INamedTypeSymbol namedType && + typeMatches(namedType, typesToSearchFor)) + { + result.Add(namedType); + } + } + } } async Task AddDescendantMetadataTypesInProjectAsync(SymbolSet result, Project project) @@ -553,31 +564,6 @@ private static void PropagateTemporaryResults( tempBuffer.Clear(); } - private static async Task AddSourceTypesThatDeriveFromNameAsync( - Func typeMatches, - ConcurrentSet cachedModels, - SymbolSet typesToSearchFor, - ProjectIndex index, - SymbolSet result, - string name, - CancellationToken cancellationToken) - { - foreach (var (document, info) in index.NamedTypes[name]) - { - cancellationToken.ThrowIfCancellationRequested(); - - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - cachedModels.Add(semanticModel); - - var resolvedType = info.TryResolve(semanticModel, cancellationToken); - if (resolvedType is INamedTypeSymbol namedType && - typeMatches(namedType, typesToSearchFor)) - { - result.Add(namedType); - } - } - } - public static PooledDisposer> GetSymbolSet(out SymbolSet instance) { var pooledInstance = s_symbolSetPool.Allocate(); From 817af570090c958b347bc30784fc48a81ee97843 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:21:15 -0800 Subject: [PATCH 115/274] Simplify code --- .../FindReferences/DependentTypeFinder.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index d30f01658bf8b..f653b13f22432 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -220,21 +220,15 @@ await AddMatchingTypesAsync( break; case SpecialType.System_ValueType: await AddMatchingTypesAsync( - projectIndex.ValueTypes, - tempBuffer, - predicateOpt: null).ConfigureAwait(false); + projectIndex.ValueTypes, tempBuffer, predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_Enum: await AddMatchingTypesAsync( - projectIndex.Enums, - tempBuffer, - predicateOpt: null).ConfigureAwait(false); + projectIndex.Enums, tempBuffer, predicateOpt: null).ConfigureAwait(false); break; case SpecialType.System_MulticastDelegate: await AddMatchingTypesAsync( - projectIndex.Delegates, - tempBuffer, - predicateOpt: null).ConfigureAwait(false); + projectIndex.Delegates, tempBuffer, predicateOpt: null).ConfigureAwait(false); break; } From 905d8ae20cc92b1baa815b33693729e8266ec080 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:22:19 -0800 Subject: [PATCH 116/274] Add comment --- .../Portable/FindSymbols/FindReferences/DependentTypeFinder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index f653b13f22432..2962bdd22318c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -324,6 +324,7 @@ await AddMatchingMetadataTypesInMetadataReferenceAsync( result, typesToSearchFor, tempBuffer, transitive, shouldContinueSearching); } + // Mark all these references as having been seen. We don't need to examine it in future projects. seenPEReferences.AddRange(compilation.References.OfType()); } From 9985f9c53974fd4d0a88280a9d64e3dc14141922 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 16 Dec 2022 15:38:45 -0800 Subject: [PATCH 117/274] Simplify code --- .../FindSymbols/FindReferences/DependentTypeFinder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 2962bdd22318c..f299b9b02e46c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -42,9 +42,9 @@ internal static partial class DependentTypeFinder private static readonly Func s_isInMetadata = static loc => loc.IsInMetadata; private static readonly Func s_isInSource = static loc => loc.IsInSource; - private static readonly Func s_isInterface = t => t?.TypeKind == TypeKind.Interface; - private static readonly Func s_isNonSealedClass = t => t?.TypeKind == TypeKind.Class && !t.IsSealed; - private static readonly Func s_isInterfaceOrNonSealedClass = t => s_isInterface(t) || s_isNonSealedClass(t); + private static readonly Func s_isInterface = static t => t is { TypeKind: TypeKind.Interface }; + private static readonly Func s_isNonSealedClass = static t => t is { TypeKind: TypeKind.Class, IsSealed: false }; + private static readonly Func s_isInterfaceOrNonSealedClass = static t => s_isInterface(t) || s_isNonSealedClass(t); private static readonly ObjectPool> s_symbolSetPool = PooledHashSet.CreatePool(SymbolEquivalenceComparer.Instance); From f11341d2a25b525151dedba25e40efd65da450f1 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 19:18:39 +0300 Subject: [PATCH 118/274] Keep the block of else-if statement in "Use null propogation" codefix --- ...CSharpUseNullPropagationCodeFixProvider.cs | 6 ++ .../UseNullPropagationTests.cs | 63 +++++++++++++++++++ ...stractUseNullPropagationCodeFixProvider.cs | 23 ++++++- ...lBasicUseNullPropagationCodeFixProvider.vb | 8 +++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index a002ac15201af..0b5cf58c492f1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -32,6 +32,12 @@ public CSharpUseNullPropagationCodeFixProvider() { } + protected override bool IsBlockSyntax(SyntaxNode? statement) + => statement is BlockSyntax; + + protected override StatementSyntax Block(StatementSyntax innerStatement) + => SyntaxFactory.Block(innerStatement); + protected override ElementBindingExpressionSyntax ElementBindingExpression(BracketedArgumentListSyntax argumentList) => SyntaxFactory.ElementBindingExpression(argumentList); } diff --git a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs index 9a7a2a84426fe..73f0161feaf9c 100644 --- a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs +++ b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs @@ -2069,5 +2069,68 @@ class C Action M(List p) => p is null ? null : p.Add; }"); } + + [Fact, WorkItem(66036, "https://github.com/dotnet/roslyn/issues/66036")] + public async Task TestElseIfStatement1() + { + await TestInRegularAndScript1Async(""" + class C + { + void M(string s) + { + if (true) + { + } + else [|if|] (s != null) + { + s.ToString(); + } + } + } + """, """ + class C + { + void M(string s) + { + if (true) + { + } + else + { + s?.ToString(); + } + } + } + """); + } + + [Fact, WorkItem(66036, "https://github.com/dotnet/roslyn/issues/66036")] + public async Task TestElseIfStatement2() + { + await TestInRegularAndScript1Async(""" + class C + { + void M(string s) + { + if (true) + { + } + else [|if|] (s != null) + s.ToString(); + } + } + """, """ + class C + { + void M(string s) + { + if (true) + { + } + else s?.ToString(); + } + } + """); + } } } diff --git a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs index 922b904539cf0..b8e05f017b0fc 100644 --- a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs @@ -47,6 +47,8 @@ internal abstract class AbstractUseNullPropagationCodeFixProvider< where TExpressionStatementSyntax : TStatementSyntax where TElementBindingArgumentListSyntax : SyntaxNode { + protected abstract bool IsBlockSyntax(SyntaxNode? node); + protected abstract TStatementSyntax Block(TStatementSyntax innerStatement); protected abstract TElementBindingExpressionSyntax ElementBindingExpression(TElementBindingArgumentListSyntax argumentList); public override ImmutableArray FixableDiagnosticIds @@ -142,7 +144,8 @@ private void FixIfStatement( var syntaxFacts = document.GetRequiredLanguageService(); var generator = document.GetRequiredLanguageService(); - var whenTrueStatement = (TExpressionStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); + var originalIfStatement = (TStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); + var whenTrueStatement = (TStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); var match = (TExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan, getInnermostNodeForTie: true); var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable); @@ -153,6 +156,24 @@ private void FixIfStatement( syntaxFacts, generator, whenPartIsNullable, whenTrueStatement, match); Contract.ThrowIfNull(newWhenTrueStatement); + // If we have code like: + // ... + // else if (v != null) + // { + // v.M(); + // } + // then we want to keep the result statement in a block: + // else + // { + // v?.M(); + // } + // Applies only to C# since VB doesn't have a general-purpose block syntax + if (syntaxFacts.IsElseClause(originalIfStatement.Parent) && + IsBlockSyntax(whenTrueStatement.Parent)) + { + newWhenTrueStatement = Block(newWhenTrueStatement).WithAdditionalAnnotations(Formatter.Annotation); + } + // If there's leading trivia on the original inner statement, then combine that with the leading // trivia on the if-statement. We'll need to add a formatting annotation so that the leading comments // are put in the right location. diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb index 6e3a31f9aa536..5af18e4897c52 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb @@ -31,6 +31,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Public Sub New() End Sub + Protected Overrides Function IsBlockSyntax(node As SyntaxNode) As Boolean + Return False + End Function + + Protected Overrides Function Block(innerStatement As ExecutableStatementSyntax) As ExecutableStatementSyntax + Throw ExceptionUtilities.Unreachable() + End Function + Protected Overrides Function ElementBindingExpression(argumentList As ArgumentListSyntax) As InvocationExpressionSyntax Return SyntaxFactory.InvocationExpression(Nothing, argumentList) End Function From 2ced569d5f1d3e66f57cfb6db72c65c4017d7a92 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 20:38:48 +0300 Subject: [PATCH 119/274] Reuse existing block --- ...CSharpUseNullPropagationCodeFixProvider.cs | 20 +++++++++++++++---- ...stractUseNullPropagationCodeFixProvider.cs | 9 +++++---- ...lBasicUseNullPropagationCodeFixProvider.vb | 4 ++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index 0b5cf58c492f1..ea3e11508488d 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -32,11 +32,23 @@ public CSharpUseNullPropagationCodeFixProvider() { } - protected override bool IsBlockSyntax(SyntaxNode? statement) - => statement is BlockSyntax; + protected override bool TryGetBlock(SyntaxNode? statement, [NotNullWhen(true)] out StatementSyntax? block) + { + if (statement is BlockSyntax stetementBlock) + { + block = stetementBlock; + return true; + } + + block = null; + return false; + } - protected override StatementSyntax Block(StatementSyntax innerStatement) - => SyntaxFactory.Block(innerStatement); + protected override StatementSyntax ReplaceBlockStatements(StatementSyntax block, StatementSyntax newInnerStatement) + { + var newStatementList = SyntaxFactory.List(new[] { newInnerStatement }); + return ((BlockSyntax)block).WithStatements(newStatementList); + } protected override ElementBindingExpressionSyntax ElementBindingExpression(BracketedArgumentListSyntax argumentList) => SyntaxFactory.ElementBindingExpression(argumentList); diff --git a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs index b8e05f017b0fc..a1a465ff6d7cf 100644 --- a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -47,8 +48,8 @@ internal abstract class AbstractUseNullPropagationCodeFixProvider< where TExpressionStatementSyntax : TStatementSyntax where TElementBindingArgumentListSyntax : SyntaxNode { - protected abstract bool IsBlockSyntax(SyntaxNode? node); - protected abstract TStatementSyntax Block(TStatementSyntax innerStatement); + protected abstract bool TryGetBlock(SyntaxNode? node, [NotNullWhen(true)] out TStatementSyntax? block); + protected abstract TStatementSyntax ReplaceBlockStatements(TStatementSyntax block, TStatementSyntax newInnerStatement); protected abstract TElementBindingExpressionSyntax ElementBindingExpression(TElementBindingArgumentListSyntax argumentList); public override ImmutableArray FixableDiagnosticIds @@ -169,9 +170,9 @@ private void FixIfStatement( // } // Applies only to C# since VB doesn't have a general-purpose block syntax if (syntaxFacts.IsElseClause(originalIfStatement.Parent) && - IsBlockSyntax(whenTrueStatement.Parent)) + TryGetBlock(whenTrueStatement.Parent, out var block)) { - newWhenTrueStatement = Block(newWhenTrueStatement).WithAdditionalAnnotations(Formatter.Annotation); + newWhenTrueStatement = ReplaceBlockStatements(block, newWhenTrueStatement).WithAdditionalAnnotations(Formatter.Annotation); } // If there's leading trivia on the original inner statement, then combine that with the leading diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb index 5af18e4897c52..a0ecd4cf45422 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb @@ -31,11 +31,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Public Sub New() End Sub - Protected Overrides Function IsBlockSyntax(node As SyntaxNode) As Boolean + Protected Overrides Function TryGetBlock(node As SyntaxNode, ByRef block As ExecutableStatementSyntax) As Boolean Return False End Function - Protected Overrides Function Block(innerStatement As ExecutableStatementSyntax) As ExecutableStatementSyntax + Protected Overrides Function ReplaceBlockStatements(block As ExecutableStatementSyntax, newInnerStatement As ExecutableStatementSyntax) As ExecutableStatementSyntax Throw ExceptionUtilities.Unreachable() End Function From 324651e85db0d2811304a82bd45164c5ad3f5890 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 20:51:27 +0300 Subject: [PATCH 120/274] Add VB test --- .../UseNullPropagation/UseNullPropagationTests.vb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb b/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb index bfd122f941919..872d69736c509 100644 --- a/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/UseNullPropagation/UseNullPropagationTests.vb @@ -1104,5 +1104,20 @@ public class C end sub end class") End Function + + + Public Async Function TestElseIf() As Task + ' Subject to improve + Await TestMissingInRegularAndScriptAsync( +" +Class C + Sub M(s as String) + If True Then + ElseIf s [||]IsNot Nothing + s.ToString() + End If + End Sub +End Class") + End Function End Class End Namespace From 12a3a35fa98f566e5434d104fb280c2d2811994c Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 22:39:58 +0300 Subject: [PATCH 121/274] Trivial work (pun intended) --- ...CSharpUseNullPropagationCodeFixProvider.cs | 9 +++++ .../UseNullPropagationTests.cs | 39 ++++++++++++++++++- ...stractUseNullPropagationCodeFixProvider.cs | 23 ++++++++--- ...lBasicUseNullPropagationCodeFixProvider.vb | 5 +++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index ea3e11508488d..a70a0a60f0f8d 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseNullPropagation; namespace Microsoft.CodeAnalysis.CSharp.UseNullPropagation @@ -50,6 +51,14 @@ protected override StatementSyntax ReplaceBlockStatements(StatementSyntax block, return ((BlockSyntax)block).WithStatements(newStatementList); } + protected override SyntaxNode PostProcessElseIf(IfStatementSyntax ifStatement, StatementSyntax newWhenTrueStatement) + { + var elseClauseSyntax = (ElseClauseSyntax)ifStatement.Parent!; + return elseClauseSyntax + .WithElseKeyword(elseClauseSyntax.ElseKeyword.WithTrailingTrivia()) + .WithStatement(newWhenTrueStatement.WithPrependedLeadingTrivia(ifStatement.CloseParenToken.TrailingTrivia)); + } + protected override ElementBindingExpressionSyntax ElementBindingExpression(BracketedArgumentListSyntax argumentList) => SyntaxFactory.ElementBindingExpression(argumentList); } diff --git a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs index 73f0161feaf9c..1ad88b9d9d6c0 100644 --- a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs +++ b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs @@ -2127,7 +2127,44 @@ void M(string s) if (true) { } - else s?.ToString(); + else + s?.ToString(); + } + } + """); + } + + [Fact, WorkItem(66036, "https://github.com/dotnet/roslyn/issues/66036")] + public async Task TestElseIfStatement_Trivia() + { + await TestInRegularAndScript1Async(""" + class C + { + void M(string s) + { + if (true) + { + } + else [|if|] (s != null) + { + // comment + s.ToString(); + } + } + } + """, """ + class C + { + void M(string s) + { + if (true) + { + } + else + { + // comment + s?.ToString(); + } } } """); diff --git a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs index a1a465ff6d7cf..90f8cfd8b3159 100644 --- a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs @@ -50,6 +50,7 @@ internal abstract class AbstractUseNullPropagationCodeFixProvider< { protected abstract bool TryGetBlock(SyntaxNode? node, [NotNullWhen(true)] out TStatementSyntax? block); protected abstract TStatementSyntax ReplaceBlockStatements(TStatementSyntax block, TStatementSyntax newInnerStatement); + protected abstract SyntaxNode PostProcessElseIf(TIfStatementSyntax ifStatement, TStatementSyntax newWhenTrueStatement); protected abstract TElementBindingExpressionSyntax ElementBindingExpression(TElementBindingArgumentListSyntax argumentList); public override ImmutableArray FixableDiagnosticIds @@ -145,18 +146,22 @@ private void FixIfStatement( var syntaxFacts = document.GetRequiredLanguageService(); var generator = document.GetRequiredLanguageService(); - var originalIfStatement = (TStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true); var whenTrueStatement = (TStatementSyntax)root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan); var match = (TExpressionSyntax)root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan, getInnermostNodeForTie: true); var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable); + SyntaxNode nodeToBeReplaced = ifStatement; + SyntaxNode? replacementNode = null; + // we have `if (x != null) x.Y();`. Update `x.Y()` to be `x?.Y()`, then replace the entire // if-statement with that expression statement. var newWhenTrueStatement = CreateConditionalAccessExpression( syntaxFacts, generator, whenPartIsNullable, whenTrueStatement, match); Contract.ThrowIfNull(newWhenTrueStatement); + var isElseIf = syntaxFacts.IsElseClause(ifStatement.Parent); + // If we have code like: // ... // else if (v != null) @@ -169,7 +174,7 @@ private void FixIfStatement( // v?.M(); // } // Applies only to C# since VB doesn't have a general-purpose block syntax - if (syntaxFacts.IsElseClause(originalIfStatement.Parent) && + if (isElseIf && TryGetBlock(whenTrueStatement.Parent, out var block)) { newWhenTrueStatement = ReplaceBlockStatements(block, newWhenTrueStatement).WithAdditionalAnnotations(Formatter.Annotation); @@ -186,15 +191,23 @@ private void FixIfStatement( } else { - newWhenTrueStatement = newWhenTrueStatement.WithLeadingTrivia(ifStatement.GetLeadingTrivia()); + if (isElseIf) + { + nodeToBeReplaced = ifStatement.Parent!; + replacementNode = PostProcessElseIf(ifStatement, newWhenTrueStatement); + } + else + { + newWhenTrueStatement = newWhenTrueStatement.WithLeadingTrivia(ifStatement.GetLeadingTrivia()); + } } // If there's trailing comments on the original inner statement, then preserve that. Otherwise, // replace it with the trailing trivia of hte original if-statement. - if (!newWhenTrueStatement.GetTrailingTrivia().Any(syntaxFacts.IsRegularComment)) + if (!newWhenTrueStatement.GetTrailingTrivia().Any(syntaxFacts.IsRegularComment) && !isElseIf) newWhenTrueStatement = newWhenTrueStatement.WithTrailingTrivia(ifStatement.GetTrailingTrivia()); - editor.ReplaceNode(ifStatement, newWhenTrueStatement); + editor.ReplaceNode(nodeToBeReplaced, replacementNode ?? newWhenTrueStatement); } private TContainer? CreateConditionalAccessExpression( diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb index a0ecd4cf45422..311f4c9f21bcc 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb @@ -39,6 +39,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation Throw ExceptionUtilities.Unreachable() End Function + Protected Overrides Function PostProcessElseIf(ifStatement As MultiLineIfBlockSyntax, newWhenTrueStatement As ExecutableStatementSyntax) As SyntaxNode + Debug.Fail("Should never get here") + Return newWhenTrueStatement + End Function + Protected Overrides Function ElementBindingExpression(argumentList As ArgumentListSyntax) As InvocationExpressionSyntax Return SyntaxFactory.InvocationExpression(Nothing, argumentList) End Function From 573b3418f00ef735d283e1a4f5b8223c7f0c0bf7 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 22:57:56 +0300 Subject: [PATCH 122/274] SingletonList --- .../CSharpUseNullPropagationCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index a70a0a60f0f8d..df6fb1261079c 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -47,7 +47,7 @@ protected override bool TryGetBlock(SyntaxNode? statement, [NotNullWhen(true)] o protected override StatementSyntax ReplaceBlockStatements(StatementSyntax block, StatementSyntax newInnerStatement) { - var newStatementList = SyntaxFactory.List(new[] { newInnerStatement }); + var newStatementList = SyntaxFactory.SingletonList(newInnerStatement); return ((BlockSyntax)block).WithStatements(newStatementList); } From c54d6bf6adba4ae4ac3f74c83b760fd3369bcff2 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 17 Dec 2022 23:03:16 +0300 Subject: [PATCH 123/274] Keep braces --- .../UseNullPropagationTests.cs | 32 +++++++++++++++++++ ...stractUseNullPropagationCodeFixProvider.cs | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs index 1ad88b9d9d6c0..657f652db904f 100644 --- a/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs +++ b/src/Analyzers/CSharp/Tests/UseNullPropagation/UseNullPropagationTests.cs @@ -2169,5 +2169,37 @@ void M(string s) } """); } + + [Fact, WorkItem(66036, "https://github.com/dotnet/roslyn/issues/66036")] + public async Task TestElseIfStatement_KeepBracePlacementStyle() + { + await TestInRegularAndScript1Async(""" + class C + { + void M(string s) + { + if (true) + { + } + else [|if|] (s != null) { + s.ToString(); + } + } + } + """, """ + class C + { + void M(string s) + { + if (true) + { + } + else { + s?.ToString(); + } + } + } + """); + } } } diff --git a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs index 90f8cfd8b3159..4bb65d0e6fc7e 100644 --- a/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseNullPropagation/AbstractUseNullPropagationCodeFixProvider.cs @@ -177,7 +177,7 @@ private void FixIfStatement( if (isElseIf && TryGetBlock(whenTrueStatement.Parent, out var block)) { - newWhenTrueStatement = ReplaceBlockStatements(block, newWhenTrueStatement).WithAdditionalAnnotations(Formatter.Annotation); + newWhenTrueStatement = ReplaceBlockStatements(block, newWhenTrueStatement); } // If there's leading trivia on the original inner statement, then combine that with the leading From b65c0055ec7857886bb567f03f34c648345e3cc8 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:51:58 +0000 Subject: [PATCH 124/274] Update dependencies from https://github.com/dotnet/arcade build 20221216.1 (#66058) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index f5d7cc7181436..0f7df75e5d499 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,18 +13,18 @@ - + https://github.com/dotnet/arcade - 5da4fd65d650129f1771e2fb55e296161b654b85 + 57ba56de330e50f9012493b8ba24818e24ec7817 https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 5da4fd65d650129f1771e2fb55e296161b654b85 + 57ba56de330e50f9012493b8ba24818e24ec7817 https://github.com/dotnet/roslyn-analyzers diff --git a/global.json b/global.json index affb3fb79f549..f48af8715e1c3 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.4.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.22615.3", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.22615.3" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.22616.1", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.22616.1" } } From e1557adf496114cf933001a2bea3eadfd6b1d2aa Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 19 Dec 2022 22:09:00 +0300 Subject: [PATCH 125/274] Typo --- .../CSharpUseNullPropagationCodeFixProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index df6fb1261079c..739fdc2cea4ff 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -35,9 +35,9 @@ public CSharpUseNullPropagationCodeFixProvider() protected override bool TryGetBlock(SyntaxNode? statement, [NotNullWhen(true)] out StatementSyntax? block) { - if (statement is BlockSyntax stetementBlock) + if (statement is BlockSyntax statementBlock) { - block = stetementBlock; + block = statementBlock; return true; } From 082927987980aa9c315c92a52d17067402a948fd Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 19 Dec 2022 22:09:29 +0300 Subject: [PATCH 126/274] Dedent --- .../CSharpUseNullPropagationCodeFixProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs index 739fdc2cea4ff..934510b97a7f4 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseNullPropagation/CSharpUseNullPropagationCodeFixProvider.cs @@ -55,8 +55,8 @@ protected override SyntaxNode PostProcessElseIf(IfStatementSyntax ifStatement, S { var elseClauseSyntax = (ElseClauseSyntax)ifStatement.Parent!; return elseClauseSyntax - .WithElseKeyword(elseClauseSyntax.ElseKeyword.WithTrailingTrivia()) - .WithStatement(newWhenTrueStatement.WithPrependedLeadingTrivia(ifStatement.CloseParenToken.TrailingTrivia)); + .WithElseKeyword(elseClauseSyntax.ElseKeyword.WithTrailingTrivia()) + .WithStatement(newWhenTrueStatement.WithPrependedLeadingTrivia(ifStatement.CloseParenToken.TrailingTrivia)); } protected override ElementBindingExpressionSyntax ElementBindingExpression(BracketedArgumentListSyntax argumentList) From 1185525a0f95e24ea0688088e1acac0dd0b78342 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 19 Dec 2022 22:10:51 +0300 Subject: [PATCH 127/274] Unreachable --- .../VisualBasicUseNullPropagationCodeFixProvider.vb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb index 311f4c9f21bcc..d2844caf72300 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseNullPropagation/VisualBasicUseNullPropagationCodeFixProvider.vb @@ -40,8 +40,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation End Function Protected Overrides Function PostProcessElseIf(ifStatement As MultiLineIfBlockSyntax, newWhenTrueStatement As ExecutableStatementSyntax) As SyntaxNode - Debug.Fail("Should never get here") - Return newWhenTrueStatement + Throw ExceptionUtilities.Unreachable() End Function Protected Overrides Function ElementBindingExpression(argumentList As ArgumentListSyntax) As InvocationExpressionSyntax From 5553f9d0d07357e18adea987c76684601ad9bfac Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 19 Dec 2022 15:35:50 -0800 Subject: [PATCH 128/274] Add tests for CheckInvocationArgMixing (#65916) --- .../Test/Semantic/Semantics/RefFieldTests.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index e6b74402b2fc1..0c21cadaf8b73 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -27082,5 +27082,119 @@ ref struct RS // return new RS(ref i4); // 4 Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "i4").WithArguments("i4").WithLocation(14, 31)); } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void ConstructorInitializer_01(LanguageVersion languageVersion) + { + var source = """ + using System; + class A + { + public A(ref Span x, Span y) { } + A(ref Span s) : this(ref s, new Span()) { } + A(ref Span s, int i) : this(ref s, stackalloc int[1]) { } // 1 + } + class B : A + { + B(ref Span s) : base(ref s, new Span()) { } + B(ref Span s, int i) : base(ref s, stackalloc int[2]) { } // 2 + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (6,31): error CS8350: This combination of arguments to 'A.A(ref Span, Span)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope + // A(ref Span s, int i) : this(ref s, stackalloc int[1]) { } // 1 + Diagnostic(ErrorCode.ERR_CallArgMixing, ": this(ref s, stackalloc int[1])").WithArguments("A.A(ref System.Span, System.Span)", "y").WithLocation(6, 31), + // (6,45): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // A(ref Span s, int i) : this(ref s, stackalloc int[1]) { } // 1 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(6, 45), + // (11,31): error CS8350: This combination of arguments to 'A.A(ref Span, Span)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope + // B(ref Span s, int i) : base(ref s, stackalloc int[2]) { } // 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, ": base(ref s, stackalloc int[2])").WithArguments("A.A(ref System.Span, System.Span)", "y").WithLocation(11, 31), + // (11,45): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // B(ref Span s, int i) : base(ref s, stackalloc int[2]) { } // 2 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[2]").WithArguments("System.Span").WithLocation(11, 45)); + } + + [Fact] + public void ConstructorInitializer_02() + { + var source = """ + ref struct R + { + R(in int i) { } + R(int x, int y) : this(x) { } // 1 + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,21): error CS8350: This combination of arguments to 'R.R(in int)' is disallowed because it may expose variables referenced by parameter 'i' outside of their declaration scope + // R(int x, int y) : this(x) { } // 1 + Diagnostic(ErrorCode.ERR_CallArgMixing, ": this(x)").WithArguments("R.R(in int)", "i").WithLocation(4, 21), + // (4,28): error CS8166: Cannot return a parameter by reference 'x' because it is not a ref parameter + // R(int x, int y) : this(x) { } // 1 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(4, 28)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void FunctionPointerInvocation_01(LanguageVersion languageVersion) + { + var source = """ + using System; + class Program + { + static void F0(ref Span x, Span y) + { + } + static unsafe void F1(ref Span s) + { + delegate*, Span, void> f = &F0; + f(ref s, new Span()); + f(ref s, stackalloc int[1]); // 1 + } + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (11,18): warning CS9081: A result of a stackalloc expression of type 'Span' in this context may be exposed outside of the containing method + // f(ref s, stackalloc int[1]); // 1 + Diagnostic(ErrorCode.WRN_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(11, 18)); + } + + [Fact] + public void FunctionPointerInvocation_02() + { + var source = """ + ref struct R + { + } + class Program + { + static void F0(out R r, in int i) + { + r = default; + } + static unsafe void F1(out R r1, in int i1) + { + delegate* f = &F0; + f(out r1, i1); + } + static unsafe void F2(out R r1, int i2) + { + delegate* f = &F0; + f(out r1, i2); // 1 + } + } + """; + var comp = CreateCompilationWithSpan(source, options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (18,19): warning CS9087: This returns a parameter by reference 'i2' but it is not a ref parameter + // f(out r1, i2); // 1 + Diagnostic(ErrorCode.WRN_RefReturnParameter, "i2").WithArguments("i2").WithLocation(18, 19)); + } } } From 34180c3974c46fe354740d7f5fddd17244474612 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 19 Dec 2022 16:09:36 -0800 Subject: [PATCH 129/274] Avoid reporting ref safety errors for discard assignments (#65693) --- .../Portable/Binder/Binder.ValueChecks.cs | 20 +- .../Portable/Binder/Binder_Deconstruct.cs | 7 +- .../Portable/Binder/Binder_Expressions.cs | 6 +- .../Portable/Binder/Binder_Statements.cs | 5 +- .../Portable/Binder/ForEachLoopBinder.cs | 4 +- .../BoundTree/BoundDiscardExpression.cs | 4 +- .../BoundTree/BoundExpressionExtensions.cs | 11 + .../CSharp/Portable/BoundTree/BoundNodes.xml | 3 +- .../OutDeconstructVarPendingInference.cs | 2 +- .../Generated/BoundNodes.xml.Generated.cs | 58 +- .../Semantic/Semantics/RefEscapingTests.cs | 597 +++++++++++++++++- .../Test/Semantic/Semantics/RefFieldTests.cs | 162 ++++- 12 files changed, 792 insertions(+), 87 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 6ebc085102644..04667f6e498b4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1996,8 +1996,18 @@ parameter is not null && parameter.Type.IsRefLikeType && parameter.RefKind.IsWritableReference(); - static bool isMixableArgument(BoundExpression argument) => - argument is not (BoundDeconstructValuePlaceholder { VariableSymbol: not null } or BoundLocal { DeclarationKind: not BoundLocalDeclarationKind.None }); + static bool isMixableArgument(BoundExpression argument) + { + if (argument is BoundDeconstructValuePlaceholder { VariableSymbol: not null } or BoundLocal { DeclarationKind: not BoundLocalDeclarationKind.None }) + { + return false; + } + if (argument.IsDiscardExpression()) + { + return false; + } + return true; + } static EscapeArgument getReceiver(MethodSymbol? method, BoundExpression receiver) { @@ -2308,7 +2318,9 @@ private bool CheckInvocationArgMixing( continue; } - if (refKind.IsWritableReference() && argument.Type?.IsRefLikeType == true) + if (refKind.IsWritableReference() + && !argument.IsDiscardExpression() + && argument.Type?.IsRefLikeType == true) { escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression)); } @@ -3345,7 +3357,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres return Binder.CallingMethodScope; case BoundKind.DiscardExpression: - return ((BoundDiscardExpression)expr).ValEscape; + return Binder.CallingMethodScope; case BoundKind.DeconstructValuePlaceholder: return ((BoundDeconstructValuePlaceholder)expr).ValEscape; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 68dadfd4275ea..f4e4895e47e8f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -276,7 +276,7 @@ private bool MakeDeconstructionConversion( return false; } - var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, variableSymbol: null, rightValEscape, type); + var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, variableSymbol: null, rightValEscape, isDiscardExpression: false, type); BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders, out _, variables); @@ -655,7 +655,7 @@ private BoundExpression MakeDeconstructInvocationExpression( BoundLocal { DeclarationKind: BoundLocalDeclarationKind.WithExplicitType or BoundLocalDeclarationKind.WithInferredType, LocalSymbol: var symbol } => symbol, _ => null, }; - var variable = new OutDeconstructVarPendingInference(receiverSyntax, variableSymbol: variableSymbol, valEscape); + var variable = new OutDeconstructVarPendingInference(receiverSyntax, variableSymbol: variableSymbol, valEscape, isDiscardExpression: variableOpt is BoundDiscardExpression); analyzedArguments.Arguments.Add(variable); analyzedArguments.RefKinds.Add(RefKind.Out); outVars.Add(variable); @@ -865,8 +865,7 @@ private BoundDiscardExpression BindDiscardExpression( SyntaxNode syntax, TypeWithAnnotations declTypeWithAnnotations) { - // Cannot escape out of the current expression, as it's a compiler-synthesized location. - return new BoundDiscardExpression(syntax, LocalScopeDepth, declTypeWithAnnotations.Type); + return new BoundDiscardExpression(syntax, declTypeWithAnnotations.Type); } /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 4ea97abf0b693..88414d78dbf8e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1589,8 +1589,7 @@ private BoundExpression BindIdentifier( } else if (FallBackOnDiscard(identifier, diagnostics)) { - // Cannot escape out of the current expression, as it's a compiler-synthesized location. - expression = new BoundDiscardExpression(node, LocalScopeDepth, type: null); + expression = new BoundDiscardExpression(node, type: null); } } @@ -2826,8 +2825,7 @@ private BoundExpression BindOutDeclarationArgument(DeclarationExpressionSyntax d var declType = BindVariableTypeWithAnnotations(designation, diagnostics, typeSyntax, ref isConst, out isVar, out alias); Debug.Assert(isVar != declType.HasType); - // ValEscape is the same as for an uninitialized local - return new BoundDiscardExpression(declarationExpression, Binder.CallingMethodScope, declType.Type); + return new BoundDiscardExpression(declarationExpression, declType.Type); } case SyntaxKind.SingleVariableDesignation: return BindOutVariableDeclarationArgument(declarationExpression, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index c5bf8b814631a..20ed3c50a8a57 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1466,13 +1466,14 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD var rhsKind = isRef ? GetRequiredRHSValueKindForRefAssignment(op1) : BindValueKind.RValue; var op2 = BindValue(rhsExpr, diagnostics, rhsKind); - if (op1.Kind == BoundKind.DiscardExpression) + bool discardAssignment = op1.Kind == BoundKind.DiscardExpression; + if (discardAssignment) { op2 = BindToNaturalType(op2, diagnostics); op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, verifyEscapeSafety: true, diagnostics); + return BindAssignment(node, op1, op2, isRef, verifyEscapeSafety: !discardAssignment, diagnostics); } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 0f9caddc63720..0e730ce919f50 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -182,7 +182,7 @@ internal override BoundStatement BindForEachDeconstruction(BindingDiagnosticBag ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable; // Tracking narrowest safe-to-escape scope by default, the proper val escape will be set when doing full binding of the foreach statement - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, this.LocalScopeDepth, inferredType.Type ?? CreateErrorType("var")); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, this.LocalScopeDepth, isDiscardExpression: false, inferredType.Type ?? CreateErrorType("var")); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; @@ -355,7 +355,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno var variables = node.Variable; if (variables.IsDeconstructionLeft()) { - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, collectionEscape, iterationVariableType.Type).MakeCompilerGenerated(); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, collectionEscape, isDiscardExpression: false, iterationVariableType.Type).MakeCompilerGenerated(); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardExpression.cs index 787b7621fe4e8..10fd67b7c23ef 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDiscardExpression.cs @@ -12,7 +12,7 @@ internal partial class BoundDiscardExpression public BoundExpression SetInferredTypeWithAnnotations(TypeWithAnnotations type) { Debug.Assert(Type is null && type.HasType); - return this.Update(ValEscape, type.Type); + return this.Update(type.Type); } public BoundDiscardExpression FailInference(Binder binder, BindingDiagnosticBag? diagnosticsOpt) @@ -21,7 +21,7 @@ public BoundDiscardExpression FailInference(Binder binder, BindingDiagnosticBag? { Binder.Error(diagnosticsOpt, ErrorCode.ERR_DiscardTypeInferenceFailed, this.Syntax); } - return this.Update(ValEscape, binder.CreateErrorType("var")); + return this.Update(binder.CreateErrorType("var")); } public override Symbol ExpressionSymbol diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs index e5c6a8c3d8991..ddac2d0eea06f 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs @@ -268,5 +268,16 @@ internal static bool IsExpressionOfComImportType([NotNullWhen(true)] this BoundE TypeSymbol? receiverType = expressionOpt.Type; return receiverType is NamedTypeSymbol { Kind: SymbolKind.NamedType, IsComImport: true }; } + + internal static bool IsDiscardExpression(this BoundExpression expr) + { + return expr switch + { + BoundDiscardExpression => true, + OutDeconstructVarPendingInference { IsDiscardExpression: true } => true, + BoundDeconstructValuePlaceholder { IsDiscardExpression: true } => true, + _ => false + }; + } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 8df37bd600688..1ee4b8bd5558a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -115,6 +115,7 @@ + - @@ -2391,6 +2391,7 @@ + diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs index 3144b9e6d385b..718e6488c17db 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -15,7 +15,7 @@ public BoundDeconstructValuePlaceholder SetInferredTypeWithAnnotations(TypeWithA { Debug.Assert(Placeholder is null); - Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol: VariableSymbol, ValEscape, type.Type, hasErrors: this.HasErrors || !success); + Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol: VariableSymbol, ValEscape, isDiscardExpression: IsDiscardExpression, type.Type, hasErrors: this.HasErrors || !success); return Placeholder; } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 4551d29aba12c..58a33122f23d6 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -510,7 +510,7 @@ public BoundCapturedReceiverPlaceholder Update(BoundExpression receiver, uint lo internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase { - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, TypeSymbol type, bool hasErrors) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type, bool hasErrors) : base(BoundKind.DeconstructValuePlaceholder, syntax, type, hasErrors) { @@ -518,9 +518,10 @@ public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbo this.VariableSymbol = variableSymbol; this.ValEscape = valEscape; + this.IsDiscardExpression = isDiscardExpression; } - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, TypeSymbol type) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type) : base(BoundKind.DeconstructValuePlaceholder, syntax, type) { @@ -528,20 +529,22 @@ public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbo this.VariableSymbol = variableSymbol; this.ValEscape = valEscape; + this.IsDiscardExpression = isDiscardExpression; } public new TypeSymbol Type => base.Type!; public Symbol? VariableSymbol { get; } public uint ValEscape { get; } + public bool IsDiscardExpression { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDeconstructValuePlaceholder(this); - public BoundDeconstructValuePlaceholder Update(Symbol? variableSymbol, uint valEscape, TypeSymbol type) + public BoundDeconstructValuePlaceholder Update(Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape || isDiscardExpression != this.IsDiscardExpression || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol, valEscape, type, this.HasErrors); + var result = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol, valEscape, isDiscardExpression, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -8054,29 +8057,26 @@ public BoundRelationalPattern Update(BinaryOperatorKind relation, BoundExpressio internal sealed partial class BoundDiscardExpression : BoundExpression { - public BoundDiscardExpression(SyntaxNode syntax, uint valEscape, TypeSymbol? type, bool hasErrors) + public BoundDiscardExpression(SyntaxNode syntax, TypeSymbol? type, bool hasErrors) : base(BoundKind.DiscardExpression, syntax, type, hasErrors) { - this.ValEscape = valEscape; } - public BoundDiscardExpression(SyntaxNode syntax, uint valEscape, TypeSymbol? type) + public BoundDiscardExpression(SyntaxNode syntax, TypeSymbol? type) : base(BoundKind.DiscardExpression, syntax, type) { - this.ValEscape = valEscape; } public new TypeSymbol? Type => base.Type; - public uint ValEscape { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDiscardExpression(this); - public BoundDiscardExpression Update(uint valEscape, TypeSymbol? type) + public BoundDiscardExpression Update(TypeSymbol? type) { - if (valEscape != this.ValEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundDiscardExpression(this.Syntax, valEscape, type, this.HasErrors); + var result = new BoundDiscardExpression(this.Syntax, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -8183,32 +8183,35 @@ public DeconstructionVariablePendingInference Update(Symbol variableSymbol, Boun internal sealed partial class OutDeconstructVarPendingInference : BoundExpression { - public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool hasErrors) + public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, bool hasErrors) : base(BoundKind.OutDeconstructVarPendingInference, syntax, null, hasErrors) { this.VariableSymbol = variableSymbol; this.ValEscape = valEscape; + this.IsDiscardExpression = isDiscardExpression; } - public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape) + public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression) : base(BoundKind.OutDeconstructVarPendingInference, syntax, null) { this.VariableSymbol = variableSymbol; this.ValEscape = valEscape; + this.IsDiscardExpression = isDiscardExpression; } public new TypeSymbol? Type => base.Type; public Symbol? VariableSymbol { get; } public uint ValEscape { get; } + public bool IsDiscardExpression { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitOutDeconstructVarPendingInference(this); - public OutDeconstructVarPendingInference Update(Symbol? variableSymbol, uint valEscape) + public OutDeconstructVarPendingInference Update(Symbol? variableSymbol, uint valEscape, bool isDiscardExpression) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape || isDiscardExpression != this.IsDiscardExpression) { - var result = new OutDeconstructVarPendingInference(this.Syntax, variableSymbol, valEscape, this.HasErrors); + var result = new OutDeconstructVarPendingInference(this.Syntax, variableSymbol, valEscape, isDiscardExpression, this.HasErrors); result.CopyAttributes(this); return result; } @@ -10270,7 +10273,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.VariableSymbol, node.ValEscape, type); + return node.Update(node.VariableSymbol, node.ValEscape, node.IsDiscardExpression, type); } public override BoundNode? VisitTupleOperandPlaceholder(BoundTupleOperandPlaceholder node) { @@ -11522,7 +11525,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitDiscardExpression(BoundDiscardExpression node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ValEscape, type); + return node.Update(type); } public override BoundNode? VisitThrowExpression(BoundThrowExpression node) { @@ -11545,7 +11548,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitOutDeconstructVarPendingInference(OutDeconstructVarPendingInference node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.VariableSymbol, node.ValEscape); + return node.Update(node.VariableSymbol, node.ValEscape, node.IsDiscardExpression); } public override BoundNode? VisitNonConstructorMethodBody(BoundNonConstructorMethodBody node) { @@ -11648,12 +11651,12 @@ public NullabilityRewriter(ImmutableDictionary new TreeDumperNode("discardExpression", null, new TreeDumperNode[] { - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) @@ -16167,6 +16170,7 @@ private BoundTreeDumperNodeProducer() { new TreeDumperNode("variableSymbol", node.VariableSymbol, null), new TreeDumperNode("valEscape", node.ValEscape, null), + new TreeDumperNode("isDiscardExpression", node.IsDiscardExpression, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 49fff34aa127f..4219cffac258e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -889,8 +889,8 @@ static ref int ReturnsRef(out int x) ); } - [Fact()] - public void DiscardExpressionSpan() + [Fact] + public void DiscardExpressionSpan_01() { var text = @" using System; @@ -914,7 +914,7 @@ static Span Test2() ref var s = ref ReturnsSpan(out var _); // error - s = stackalloc int[1]; + s = stackalloc int[1]; // 1 // ok return s; @@ -923,33 +923,33 @@ static Span Test2() static void Test3() { // error - ReturnsSpan(out var _ ) = stackalloc int[1]; + ReturnsSpan(out var _ ) = stackalloc int[1]; // 2 } static ref Span ReturnsSpan(out Span x) { x = default; - return ref x; + return ref x; // 3 } } "; CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular10).VerifyDiagnostics( // (23,13): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // s = stackalloc int[1]; + // s = stackalloc int[1]; // 1 Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(23, 13), // (32,35): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // ReturnsSpan(out var _ ) = stackalloc int[1]; + // ReturnsSpan(out var _ ) = stackalloc int[1]; // 2 Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(32, 35) ); CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( // (23,13): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // s = stackalloc int[1]; + // s = stackalloc int[1]; // 1 Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(23, 13), // (32,35): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // ReturnsSpan(out var _ ) = stackalloc int[1]; + // ReturnsSpan(out var _ ) = stackalloc int[1]; // 2 Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(32, 35), // (38,20): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method - // return ref x; + // return ref x; // 3 Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(38, 20) ); } @@ -1068,6 +1068,574 @@ .locals init (System.Span V_0, "); } + // As above with 'out _' instead of 'out var _'. + [WorkItem(65651, "https://github.com/dotnet/roslyn/issues/65651")] + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DiscardExpressionSpan_02(LanguageVersion languageVersion) + { + string source = """ + using System; + class Program + { + static Span Test2A() + { + ref var s2A = ref ReturnsSpan(out _); + s2A = stackalloc int[1]; // 1 + return s2A; + } + static Span Test2B() + { + Span _; + ref var s2B = ref ReturnsSpan(out _); + s2B = stackalloc int[1]; // 2 + return s2B; + } + static void Test3A() + { + ReturnsSpan(out _ ) = stackalloc int[1]; // 3 + } + static void Test3B() + { + Span _; + ReturnsSpan(out _ ) = stackalloc int[1]; // 4 + } + static ref Span ReturnsSpan(out Span x) + { + throw null; + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (7,15): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // s2A = stackalloc int[1]; // 1 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(7, 15), + // (14,15): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // s2B = stackalloc int[1]; // 2 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(14, 15), + // (19,31): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // ReturnsSpan(out _ ) = stackalloc int[1]; // 3 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(19, 31), + // (24,31): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // ReturnsSpan(out _ ) = stackalloc int[1]; // 4 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(24, 31)); + } + + // ReturnsSpan() returns ref Span, callers return Span by value. + [WorkItem(65651, "https://github.com/dotnet/roslyn/issues/65651")] + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DiscardExpressionSpan_03(LanguageVersion languageVersion) + { + string source = """ + using System; + class Program + { + static Span Test1() + { + var s1 = ReturnsSpan(out _); + return s1; + } + static Span Test2() + { + var s2 = ReturnsSpan(out var _); + return s2; + } + static Span Test3() + { + var s3 = ReturnsSpan(out Span _); + return s3; + } + static Span Test4() + { + var s4 = ReturnsSpan(out var unused); + return s4; + } + static Span Test5() + { + Span _; + var s5 = ReturnsSpan(out _); + return s5; + } + static Span Test6(out Span _) + { + var s6 = ReturnsSpan(out _); + return s6; + } + static ref Span ReturnsSpan(out Span x) + { + throw null; + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics(); + } + + // ReturnsSpan() and callers return Span by value. + [WorkItem(65651, "https://github.com/dotnet/roslyn/issues/65651")] + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DiscardExpressionSpan_04(LanguageVersion languageVersion) + { + string source = """ + using System; + class Program + { + static Span Test1() + { + var s1 = ReturnsSpan(out _); + return s1; + } + static Span Test2() + { + var s2 = ReturnsSpan(out var _); + return s2; + } + static Span Test3() + { + var s3 = ReturnsSpan(out Span _); + return s3; + } + static Span Test4() + { + var s4 = ReturnsSpan(out var unused); + return s4; + } + static Span Test5() + { + Span _; + var s5 = ReturnsSpan(out _); + return s5; + } + static Span Test6(out Span _) + { + var s6 = ReturnsSpan(out _); + return s6; + } + static Span ReturnsSpan(out Span x) + { + x = default; + return x; + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics(); + } + + // ReturnsSpan() and callers return ref Span. + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DiscardExpressionSpan_05(LanguageVersion languageVersion) + { + string source = """ + using System; + class Program + { + static ref Span Test1() + { + var s1 = ReturnsSpan(out _); + return ref s1; // 1 + } + static ref Span Test2() + { + var s2 = ReturnsSpan(out var _); + return ref s2; // 2 + } + static ref Span Test3() + { + var s3 = ReturnsSpan(out Span _); + return ref s3; // 3 + } + static ref Span Test4() + { + var s4 = ReturnsSpan(out var unused); + return ref s4; // 4 + } + static ref Span Test5() + { + Span _; + var s5 = ReturnsSpan(out _); + return ref s5; // 5 + } + static ref Span Test6(out Span _) + { + var s6 = ReturnsSpan(out _); + return ref s6; // 6 + } + static ref Span ReturnsSpan(out Span x) + { + throw null; + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (7,20): error CS8168: Cannot return local 's1' by reference because it is not a ref local + // return ref s1; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s1").WithArguments("s1").WithLocation(7, 20), + // (12,20): error CS8168: Cannot return local 's2' by reference because it is not a ref local + // return ref s2; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s2").WithArguments("s2").WithLocation(12, 20), + // (17,20): error CS8168: Cannot return local 's3' by reference because it is not a ref local + // return ref s3; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s3").WithArguments("s3").WithLocation(17, 20), + // (22,20): error CS8168: Cannot return local 's4' by reference because it is not a ref local + // return ref s4; // 4 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s4").WithArguments("s4").WithLocation(22, 20), + // (28,20): error CS8168: Cannot return local 's5' by reference because it is not a ref local + // return ref s5; // 5 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s5").WithArguments("s5").WithLocation(28, 20), + // (33,20): error CS8168: Cannot return local 's6' by reference because it is not a ref local + // return ref s6; // 6 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s6").WithArguments("s6").WithLocation(33, 20)); + } + + // ReturnsSpan() and callers return ref int. + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DiscardExpressionSpan_06(LanguageVersion languageVersion) + { + string source = """ + class Program + { + static ref int Test1() + { + var s1 = ReturnsSpan(out _); + return ref s1; // 1 + } + static ref int Test2() + { + var s2 = ReturnsSpan(out var _); + return ref s2; // 2 + } + static ref int Test3() + { + var s3 = ReturnsSpan(out int _); + return ref s3; // 3 + } + static ref int Test4() + { + var s4 = ReturnsSpan(out var unused); + return ref s4; // 4 + } + static ref int Test5() + { + int _; + var s5 = ReturnsSpan(out _); + return ref s5; // 5 + } + static ref int Test6(out int _) + { + var s6 = ReturnsSpan(out _); + return ref s6; // 6 + } + static ref int ReturnsSpan(out int x) + { + throw null; + } + } + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (6,20): error CS8168: Cannot return local 's1' by reference because it is not a ref local + // return ref s1; // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s1").WithArguments("s1").WithLocation(6, 20), + // (11,20): error CS8168: Cannot return local 's2' by reference because it is not a ref local + // return ref s2; // 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s2").WithArguments("s2").WithLocation(11, 20), + // (16,20): error CS8168: Cannot return local 's3' by reference because it is not a ref local + // return ref s3; // 3 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s3").WithArguments("s3").WithLocation(16, 20), + // (21,20): error CS8168: Cannot return local 's4' by reference because it is not a ref local + // return ref s4; // 4 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s4").WithArguments("s4").WithLocation(21, 20), + // (27,20): error CS8168: Cannot return local 's5' by reference because it is not a ref local + // return ref s5; // 5 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s5").WithArguments("s5").WithLocation(27, 20), + // (32,20): error CS8168: Cannot return local 's6' by reference because it is not a ref local + // return ref s6; // 6 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s6").WithArguments("s6").WithLocation(32, 20)); + } + + [Theory] + [InlineData("out var _")] + [InlineData("out _")] + [InlineData("out Span _")] + [InlineData("out var unused")] + [InlineData("out Span unused")] + public void DiscardExpressionSpan_07(string outVarDeclaration) + { + string source = $$""" + using System; + using System.Diagnostics.CodeAnalysis; + class Program + { + static Span Test1() + { + var s1 = ReturnsSpan({{outVarDeclaration}}); + return s1; // 1 + } + static Span Test2() + { + ref var s2 = ref ReturnsSpan({{outVarDeclaration}}); + s2 = stackalloc int[1]; + return s2; // 2 + } + static void Test3() + { + ReturnsSpan({{outVarDeclaration}}) = stackalloc int[1]; + } + static ref Span ReturnsSpan([UnscopedRef] out Span x) + { + x = default; + return ref x; + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,16): error CS8352: Cannot use variable 's1' in this context because it may expose referenced variables outside of their declaration scope + // return s1; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s1").WithArguments("s1").WithLocation(8, 16), + // (14,16): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope + // return s2; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(14, 16)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_01(LanguageVersion languageVersion) + { + string source = """ + using System; + class Program + { + static void F1() + { + Span s1 = stackalloc int[1]; + _ = s1; + } + static void F2() + { + Span s2 = stackalloc int[1]; + Span _; + _ = s2; // 1 + } + } + """; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (13,13): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope + // _ = s2; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(13, 13)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_02(LanguageVersion languageVersion) + { + string source = """ + class Program + { + static void F1(ref int x1) + { + int y1 = 1; + _ = ref y1; + _ = ref x1; + } + static void F2(ref int x2) + { + int y2 = 2; + _ = ref x2; + _ = ref y2; + } + static void F3() + { + int y3 = 3; + ref int _ = ref y3; + _ = ref y3; + } + static void F4(ref int x4) + { + int y4 = 4; + ref int _ = ref x4; + _ = ref y4; // 1 + } + } + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (25,9): error CS8374: Cannot ref-assign 'y4' to '_' because 'y4' has a narrower escape scope than '_'. + // _ = ref y4; // 1 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "_ = ref y4").WithArguments("_", "y4").WithLocation(25, 9)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_03(LanguageVersion languageVersion) + { + var source = +@"class Program +{ + static void F1() + { + (var x1, _) = F(); + (var x2, var _) = F(); + (var x3, R _) = F(); + var (x4, _) = F(); + } + static void F2() + { + R _; + (var x5, _) = F(); + } + static R F() => default; +} +ref struct R +{ + public void Deconstruct(out R x, out R y) => throw null; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyEmitDiagnostics(); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_04(LanguageVersion languageVersion) + { + var source = +@"using System; +class Program +{ + static void F1() + { + Span s1 = default; + s1.Deconstruct(out s1, out _); + } + static void F2() + { + Span s2 = default; + (s2, _) = s2; + } + static void F3() + { + Span s3 = default; + s3.Deconstruct(out s3, out var _); + } + static void F4() + { + Span s4 = default; + (s4, var _) = s4; + } + static void F5() + { + Span s5 = default; + s5.Deconstruct(out s5, out Span _); + } + static void F6() + { + Span s6 = default; + (s6, Span _) = s6; + } + static void F7() + { + Span s7 = default; + s7.Deconstruct(out s7, out var unused); + } + static void F8() + { + Span s8 = default; + (s8, var unused) = s8; + } +} +static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) + { + throw null; + } +} +"; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyEmitDiagnostics(); + } + + [WorkItem(65522, "https://github.com/dotnet/roslyn/issues/65522")] + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_05(LanguageVersion languageVersion) + { + var source = +@"using System; +class Program +{ + static void F1() + { + Span s1 = stackalloc int[10]; + s1.Deconstruct(out s1, out _); + } + static void F2() + { + Span s2 = stackalloc int[10]; + (s2, _) = s2; + } + static void F3() + { + Span s3 = stackalloc int[10]; + s3.Deconstruct(out s3, out var _); + } + static void F4() + { + Span s4 = stackalloc int[10]; + (s4, var _) = s4; + } + static void F5() + { + Span s5 = stackalloc int[10]; + s5.Deconstruct(out s5, out Span _); + } + static void F6() + { + Span s6 = stackalloc int[10]; + (s6, Span _) = s6; + } + static void F7() + { + Span s7 = stackalloc int[10]; + s7.Deconstruct(out s7, out var unused); + } + static void F8() + { + Span s8 = stackalloc int[10]; + (s8, var unused) = s8; + } +} +static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) + { + throw null; + } +} +"; + var comp = CreateCompilationWithMscorlibAndSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyEmitDiagnostics(); + } + [Fact()] public void OrdinaryLocalAndOutSpan() { @@ -3459,6 +4027,7 @@ .maxstack 1 "); } + [WorkItem(65522, "https://github.com/dotnet/roslyn/issues/65522")] [Theory] [InlineData(LanguageVersion.CSharp10)] [InlineData(LanguageVersion.CSharp11)] @@ -3510,13 +4079,7 @@ public static void Deconstruct(this Span self, out Span x, out Span, out Span, out Span)' is disallowed because it may expose variables referenced by parameter 'self' outside of their declaration scope // (global, _) = local; // error 3 - Diagnostic(ErrorCode.ERR_CallArgMixing, "local").WithArguments("Extensions.Deconstruct(System.Span, out System.Span, out System.Span)", "self").WithLocation(13, 23), - // (14,9): error CS8352: Cannot use variable '(local, _) = local' in this context because it may expose referenced variables outside of their declaration scope - // (local, _) = local; - Diagnostic(ErrorCode.ERR_EscapeVariable, "(local, _) = local").WithArguments("(local, _) = local").WithLocation(14, 9), - // (14,22): error CS8350: This combination of arguments to 'Extensions.Deconstruct(Span, out Span, out Span)' is disallowed because it may expose variables referenced by parameter 'self' outside of their declaration scope - // (local, _) = local; - Diagnostic(ErrorCode.ERR_CallArgMixing, "local").WithArguments("Extensions.Deconstruct(System.Span, out System.Span, out System.Span)", "self").WithLocation(14, 22)); + Diagnostic(ErrorCode.ERR_CallArgMixing, "local").WithArguments("Extensions.Deconstruct(System.Span, out System.Span, out System.Span)", "self").WithLocation(13, 23)); } [Theory] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index 0c21cadaf8b73..ba8647022d8d8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -14000,7 +14000,7 @@ static void Refs(ref R r1, scoped ref R r2) } [Fact] - public void LocalScope_01_Foreach() + public void LocalScope_01_Foreach_01() { var source = @"#pragma warning disable 219 @@ -14095,8 +14095,55 @@ static void verify(CSharpCompilation comp) } } + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void LocalScope_01_Foreach_02(LanguageVersion languageVersion) + { + var source = +@"#pragma warning disable 219 +ref struct R { } +class Program +{ + static void F(ref R r) + { + foreach (var _ in new Enumerable1()) break; + foreach (R _ in new Enumerable1()) break; + foreach (ref var _ in new Enumerable2(ref r)) break; + foreach (ref readonly var _ in new Enumerable2(ref r)) break; + foreach (ref R _ in new Enumerable2(ref r)) break; + } +} + +class Enumerable1 +{ + public Enumerator1 GetEnumerator() => default; +} + +class Enumerator1 +{ + public R Current => default; + public bool MoveNext() => false; +} + +class Enumerable2 +{ + public Enumerable2(ref R x) {} + public Enumerator2 GetEnumerator() => default; +} + +class Enumerator2 +{ + public ref R Current => throw null; + public bool MoveNext() => false; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyEmitDiagnostics(); + } + [Fact] - public void LocalScope_01_Foreach_Deconstruction() + public void LocalScope_01_Foreach_Deconstruction_01() { var source = @"#pragma warning disable 219 @@ -14175,21 +14222,12 @@ class Enumerator2 // (9,52): error CS9072: A deconstruction variable cannot be declared as a ref local // foreach ((scoped ref readonly R r5, scoped ref readonly var _) in new Enumerable2(ref r)) break; Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(9, 52), - // (11,18): error CS8352: Cannot use variable '(scoped var r11, scoped R _)' in this context because it may expose referenced variables outside of their declaration scope - // foreach ((scoped var r11, scoped R _) in new Enumerable1()) break; - Diagnostic(ErrorCode.ERR_EscapeVariable, "(scoped var r11, scoped R _)").WithArguments("(scoped var r11, scoped R _)").WithLocation(11, 18), // (11,19): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater. // foreach ((scoped var r11, scoped R _) in new Enumerable1()) break; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "scoped").WithArguments("ref fields", "11.0").WithLocation(11, 19), // (11,35): error CS9061: The 'scoped' modifier cannot be used with discard. // foreach ((scoped var r11, scoped R _) in new Enumerable1()) break; Diagnostic(ErrorCode.ERR_ScopedDiscard, "scoped").WithLocation(11, 35), - // (11,50): error CS8350: This combination of arguments to 'R.Deconstruct(out R, out R)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope - // foreach ((scoped var r11, scoped R _) in new Enumerable1()) break; - Diagnostic(ErrorCode.ERR_CallArgMixing, "new Enumerable1()").WithArguments("R.Deconstruct(out R, out R)", "y").WithLocation(11, 50), - // (12,18): error CS8352: Cannot use variable '(scoped ref var r21, scoped ref R _)' in this context because it may expose referenced variables outside of their declaration scope - // foreach ((scoped ref var r21, scoped ref R _) in new Enumerable2(ref r)) break; - Diagnostic(ErrorCode.ERR_EscapeVariable, "(scoped ref var r21, scoped ref R _)").WithArguments("(scoped ref var r21, scoped ref R _)").WithLocation(12, 18), // (12,19): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater. // foreach ((scoped ref var r21, scoped ref R _) in new Enumerable2(ref r)) break; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "scoped").WithArguments("ref fields", "11.0").WithLocation(12, 19), @@ -14202,12 +14240,6 @@ class Enumerator2 // (12,46): error CS9072: A deconstruction variable cannot be declared as a ref local // foreach ((scoped ref var r21, scoped ref R _) in new Enumerable2(ref r)) break; Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(12, 46), - // (12,58): error CS8350: This combination of arguments to 'R.Deconstruct(out R, out R)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope - // foreach ((scoped ref var r21, scoped ref R _) in new Enumerable2(ref r)) break; - Diagnostic(ErrorCode.ERR_CallArgMixing, "new Enumerable2(ref r)").WithArguments("R.Deconstruct(out R, out R)", "y").WithLocation(12, 58), - // (13,18): error CS8352: Cannot use variable '(scoped ref readonly var r51, scoped ref readonly R _)' in this context because it may expose referenced variables outside of their declaration scope - // foreach ((scoped ref readonly var r51, scoped ref readonly R _) in new Enumerable2(ref r)) break; - Diagnostic(ErrorCode.ERR_EscapeVariable, "(scoped ref readonly var r51, scoped ref readonly R _)").WithArguments("(scoped ref readonly var r51, scoped ref readonly R _)").WithLocation(13, 18), // (13,19): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater. // foreach ((scoped ref readonly var r51, scoped ref readonly R _) in new Enumerable2(ref r)) break; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "scoped").WithArguments("ref fields", "11.0").WithLocation(13, 19), @@ -14219,10 +14251,7 @@ class Enumerator2 Diagnostic(ErrorCode.ERR_ScopedDiscard, "scoped").WithLocation(13, 48), // (13,55): error CS9072: A deconstruction variable cannot be declared as a ref local // foreach ((scoped ref readonly var r51, scoped ref readonly R _) in new Enumerable2(ref r)) break; - Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(13, 55), - // (13,76): error CS8350: This combination of arguments to 'R.Deconstruct(out R, out R)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope - // foreach ((scoped ref readonly var r51, scoped ref readonly R _) in new Enumerable2(ref r)) break; - Diagnostic(ErrorCode.ERR_CallArgMixing, "new Enumerable2(ref r)").WithArguments("R.Deconstruct(out R, out R)", "y").WithLocation(13, 76) + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(13, 55) ); verify(comp); @@ -14339,6 +14368,95 @@ static void verify(CSharpCompilation comp) } } + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void LocalScope_01_Foreach_Deconstruction_02(LanguageVersion languageVersion) + { + var source = +@"#pragma warning disable 219 + +class Program +{ + static void F0(ref R r) + { + foreach ((R r0, _) in new Enumerable1()) break; + + foreach ((R r1, var _) in new Enumerable1()) break; + foreach ((ref R r2, ref var _) in new Enumerable2(ref r)) break; + foreach ((ref readonly R r5, ref readonly var _) in new Enumerable2(ref r)) break; + + foreach ((var r11, R _) in new Enumerable1()) break; + foreach ((ref var r21, ref R _) in new Enumerable2(ref r)) break; + foreach ((ref readonly var r51, ref readonly R _) in new Enumerable2(ref r)) break; + } + + static void F1() + { + var e1 = new Enumerable1().GetEnumerator(); + e1.MoveNext(); + e1.Current.Deconstruct(out R r0, out _); + e1.Current.Deconstruct(out R r1, out var _); + e1.Current.Deconstruct(out var r11, out R _); + } +} + +ref struct R +{ + public void Deconstruct(out R x, out R y) => throw null; +} + +class Enumerable1 +{ + public Enumerator1 GetEnumerator() => default; +} + +class Enumerator1 +{ + public R Current => default; + public bool MoveNext() => false; +} + +class Enumerable2 +{ + public Enumerable2(ref R x) {} + public Enumerator2 GetEnumerator() => default; +} + +class Enumerator2 +{ + public ref R Current => throw null; + public bool MoveNext() => false; +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyEmitDiagnostics( + // (10,19): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref R r2, ref var _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(10, 19), + // (10,29): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref R r2, ref var _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(10, 29), + // (11,19): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref readonly R r5, ref readonly var _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(11, 19), + // (11,38): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref readonly R r5, ref readonly var _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(11, 38), + // (14,19): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref var r21, ref R _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(14, 19), + // (14,32): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref var r21, ref R _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(14, 32), + // (15,19): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref readonly var r51, ref readonly R _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(15, 19), + // (15,41): error CS9072: A deconstruction variable cannot be declared as a ref local + // foreach ((ref readonly var r51, ref readonly R _) in new Enumerable2(ref r)) break; + Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(15, 41)); + } + [Fact] public void LocalScope_04_Foreach() { @@ -19871,8 +19989,6 @@ ref struct R2 public ref int _f; } "; - // Diagnostic is missing parameter name - // Tracked by https://github.com/dotnet/roslyn/issues/62096 var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); comp.VerifyDiagnostics( // (7,31): error CS8331: Cannot assign to variable 'i' or use it as the right hand side of a ref assignment because it is a readonly variable From aac742b264cf9e96c0b38bf2222d905863ff5674 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 20 Dec 2022 11:50:00 +0530 Subject: [PATCH 130/274] Update comment --- .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 553f6e54e12c3..dbc0d66c42850 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -53,9 +53,10 @@ internal partial class DiagnosticIncrementalAnalyzer return existingData; } - // Check whether analyzer is suppressed for project or document and avoid getting diagnostics if suppressed. - // Note that we do not return empty DocumentAnalysisData for suppressed analyzer as that would clear the - // error list and remove the previously reported diagnostics from "Run Code Analysis" command. + // Check whether analyzer is suppressed for project or document. + // If so, we set the flag indicating that the client can skip analysis for this document. + // Regardless of whether or not the analyzer is suppressed for project or document, + // we return null to indicate that no diagnostics are cached for this document for the given version. skipAnalysisForNonCachedDocument = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || !IsAnalyzerEnabledForDocument(stateSet.Analyzer, existingData, analysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument); From e17d6ad58c1025642f246474e1e7446dac817c98 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:24:11 +0000 Subject: [PATCH 131/274] Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20221219.1 (#66075) [main] Update dependencies from dotnet/roslyn-analyzers --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0f7df75e5d499..cb75f5b24a50a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -26,9 +26,9 @@ https://github.com/dotnet/arcade 57ba56de330e50f9012493b8ba24818e24ec7817 - + https://github.com/dotnet/roslyn-analyzers - ea8aa10a9b2cf153701dc79994d6c3c5e9c17d0a + 10632e6dfeb051173788f4e5cd616abeba6d9ff0 diff --git a/eng/Versions.props b/eng/Versions.props index 29b1a7ae04713..0be36054ee7ed 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,7 +20,7 @@ 3.3.4-beta1.22579.2 - 8.0.0-preview1.22614.3 + 8.0.0-preview1.22619.1 1.1.2-beta1.22512.1 0.1.149-beta From 0f14b1603c182508054c666bbeed838e70c6bd1c Mon Sep 17 00:00:00 2001 From: Jonathon Marolf Date: Tue, 20 Dec 2022 08:24:25 -0800 Subject: [PATCH 132/274] Add unique color scheme for high contrast mode --- .../View/WhitespaceBoolSettingView.xaml | 140 +++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/Whitespace/View/WhitespaceBoolSettingView.xaml b/src/VisualStudio/Core/Def/EditorConfigSettings/Whitespace/View/WhitespaceBoolSettingView.xaml index 9c00c73c7294d..54c723619f8da 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/Whitespace/View/WhitespaceBoolSettingView.xaml +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/Whitespace/View/WhitespaceBoolSettingView.xaml @@ -4,11 +4,147 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" - Resources="{StaticResource {x:Static vsshell:VsResourceKeys.ThemedDialogDefaultStylesKey}}" + xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0" mc:Ignorable="d" x:ClassModifier="internal"> + + + + + AutomationProperties.Name="{Binding AutomationName}" + FocusVisualStyle="{DynamicResource FocusVisualStyleKey}"> + + + + From 76cd2f1f0d55172a428e4fdae95fd7b0a2b2682f Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 20 Dec 2022 18:32:33 -0800 Subject: [PATCH 133/274] Adjust calculation of invocation escape when return type is ref to ref struct (#65857) --- .../Portable/Binder/Binder.ValueChecks.cs | 79 +++- .../CodeGen/CodeGenFunctionPointersTests.cs | 4 - .../Semantic/Semantics/RefEscapingTests.cs | 21 +- .../Test/Semantic/Semantics/RefFieldTests.cs | 428 +++++++++++++++++- 4 files changed, 484 insertions(+), 48 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 04667f6e498b4..c70f1312a4f2f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1729,18 +1729,27 @@ private uint GetInvocationEscapeWithUpdatedRules( isRefEscape, ignoreArglistRefKinds: true, // https://github.com/dotnet/roslyn/issues/63325: for compatibility with C#10 implementation. argsAndParamsAll); - foreach (var argAndParam in argsAndParamsAll) - { - var argument = argAndParam.Argument; - uint argEscape = argAndParam.IsRefEscape ? - GetRefEscape(argument, scopeOfTheContainingExpression) : - GetValEscape(argument, scopeOfTheContainingExpression); - escapeScope = Math.Max(escapeScope, argEscape); - if (escapeScope >= scopeOfTheContainingExpression) + var returnsRefToRefStruct = ReturnsRefToRefStruct(symbol); + foreach (var (param, argument, _, isArgumentRefEscape) in argsAndParamsAll) + { + // SPEC: + // If `M()` does return ref-to-ref-struct, the *safe-to-escape* is the same as the *safe-to-escape* of all arguments which are ref-to-ref-struct. It is an error if there are multiple arguments with different *safe-to-escape* because of *method arguments must match*. + // If `M()` does return ref-to-ref-struct, the *ref-safe-to-escape* is the narrowest *ref-safe-to-escape* contributed by all arguments which are ref-to-ref-struct. + // + if (!returnsRefToRefStruct + || (param is null or { RefKind: not RefKind.None, Type.IsRefLikeType: true } && isArgumentRefEscape == isRefEscape)) { - // can't get any worse - break; + uint argEscape = isArgumentRefEscape ? + GetRefEscape(argument, scopeOfTheContainingExpression) : + GetValEscape(argument, scopeOfTheContainingExpression); + + escapeScope = Math.Max(escapeScope, argEscape); + if (escapeScope >= scopeOfTheContainingExpression) + { + // can't get any worse + break; + } } } argsAndParamsAll.Free(); @@ -1748,6 +1757,19 @@ private uint GetInvocationEscapeWithUpdatedRules( return escapeScope; } + private static bool ReturnsRefToRefStruct(Symbol symbol) + { + var method = symbol switch + { + MethodSymbol m => m, + // We are only getting the method in order to handle a special condition where the method returns by-ref. + // It is an error for a property to have a setter and return by-ref, so we only bother looking for a getter here. + PropertySymbol p => p.GetMethod, + _ => null + }; + return method is { RefKind: not RefKind.None, ReturnType.IsRefLikeType: true }; + } + /// /// Validates whether given invocation can allow its results to escape from level to level. /// The result indicates whether the escape is possible. @@ -1869,24 +1891,33 @@ private bool CheckInvocationEscapeWithUpdatedRules( isRefEscape, ignoreArglistRefKinds: true, // https://github.com/dotnet/roslyn/issues/63325: for compatibility with C#10 implementation. argsAndParamsAll); - foreach (var argAndParam in argsAndParamsAll) - { - var argument = argAndParam.Argument; - bool valid = argAndParam.IsRefEscape ? - CheckRefEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics) : - CheckValEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics); - if (!valid) + var returnsRefToRefStruct = ReturnsRefToRefStruct(symbol); + foreach (var (param, argument, _, isArgumentRefEscape) in argsAndParamsAll) + { + // SPEC: + // If `M()` does return ref-to-ref-struct, the *safe-to-escape* is the same as the *safe-to-escape* of all arguments which are ref-to-ref-struct. It is an error if there are multiple arguments with different *safe-to-escape* because of *method arguments must match*. + // If `M()` does return ref-to-ref-struct, the *ref-safe-to-escape* is the narrowest *ref-safe-to-escape* contributed by all arguments which are ref-to-ref-struct. + // + if (!returnsRefToRefStruct + || (param is null or { RefKind: not RefKind.None, Type.IsRefLikeType: true } && isArgumentRefEscape == isRefEscape)) { - // For consistency with C#10 implementation, we don't report an additional error - // for the receiver. (In both implementations, the call to Check*Escape() above - // will have reported a specific escape error for the receiver though.) - if ((object)((argument as BoundCapturedReceiverPlaceholder)?.Receiver ?? argument) != receiver) + bool valid = isArgumentRefEscape ? + CheckRefEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics) : + CheckValEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics); + + if (!valid) { - ReportInvocationEscapeError(syntax, symbol, argAndParam.Parameter, checkingReceiver, diagnostics); + // For consistency with C#10 implementation, we don't report an additional error + // for the receiver. (In both implementations, the call to Check*Escape() above + // will have reported a specific escape error for the receiver though.) + if ((object)((argument as BoundCapturedReceiverPlaceholder)?.Receiver ?? argument) != receiver) + { + ReportInvocationEscapeError(syntax, symbol, param, checkingReceiver, diagnostics); + } + result = false; + break; } - result = false; - break; } } argsAndParamsAll.Free(); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs index a442f7093b6f0..ce0aa7abb8edd 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenFunctionPointersTests.cs @@ -10944,12 +10944,8 @@ static ref Span ReturnPtrByRef(delegate*, ref Span> ptr) Diagnostic(ErrorCode.WRN_RefReturnLocal, "span").WithArguments("span").WithLocation(10, 28) ); - // delegate*<,> parameter is implicitly scoped ref in C#11. comp = CreateCompilationWithSpan(source, options: TestOptions.UnsafeReleaseExe); comp.VerifyDiagnostics( - // (10,28): warning CS9080: Use of variable 'span' in this context may expose referenced variables outside of their declaration scope - // return ref ptr(ref span); - Diagnostic(ErrorCode.WRN_EscapeVariable, "span").WithArguments("span").WithLocation(10, 28), // (10,28): warning CS9091: This returns local 'span' by reference but it is not a ref local // return ref ptr(ref span); Diagnostic(ErrorCode.WRN_RefReturnLocal, "span").WithArguments("span").WithLocation(10, 28) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 4219cffac258e..5493a32bae53f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -1381,17 +1381,18 @@ class Program static Span Test1() { var s1 = ReturnsSpan({{outVarDeclaration}}); - return s1; // 1 + return s1; } static Span Test2() { ref var s2 = ref ReturnsSpan({{outVarDeclaration}}); - s2 = stackalloc int[1]; - return s2; // 2 + s2 = stackalloc int[1]; // 1 + return s2; } static void Test3() { - ReturnsSpan({{outVarDeclaration}}) = stackalloc int[1]; + ReturnsSpan({{outVarDeclaration}}) = + stackalloc int[1]; // 2 } static ref Span ReturnsSpan([UnscopedRef] out Span x) { @@ -1402,12 +1403,12 @@ static ref Span ReturnsSpan([UnscopedRef] out Span x) """; var comp = CreateCompilationWithMscorlibAndSpan(new[] { source, UnscopedRefAttributeDefinition }); comp.VerifyDiagnostics( - // (8,16): error CS8352: Cannot use variable 's1' in this context because it may expose referenced variables outside of their declaration scope - // return s1; // 1 - Diagnostic(ErrorCode.ERR_EscapeVariable, "s1").WithArguments("s1").WithLocation(8, 16), - // (14,16): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope - // return s2; // 2 - Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(14, 16)); + // (13,14): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // s2 = stackalloc int[1]; // 1 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(13, 14), + // (19,13): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // stackalloc int[1]; // 2 + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(19, 13)); } [Theory] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index ba8647022d8d8..325c36422de6b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -10991,23 +10991,20 @@ static ref R F1() { int i = 42; var r1 = new R(ref i); - return ref ReturnRef(ref r1); // 1 + return ref ReturnRef(ref r1); } static ref R F2(ref int i) { var r2 = new R(ref i); return ref ReturnRef(ref r2); } + + // NB: there is actually no valid implementation here except to throw. + // With this signature, we will never be able to return any ref struct by reference. static ref R ReturnRef(scoped ref R r) => throw null; }"; var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); - comp.VerifyDiagnostics( - // (12,20): error CS8347: Cannot use a result of 'Program.ReturnRef(scoped ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope - // return ref ReturnRef(ref r1); // 1 - Diagnostic(ErrorCode.ERR_EscapeCall, "ReturnRef(ref r1)").WithArguments("Program.ReturnRef(scoped ref R)", "r").WithLocation(12, 20), - // (12,34): error CS8352: Cannot use variable 'r1' in this context because it may expose referenced variables outside of their declaration scope - // return ref ReturnRef(ref r1); // 1 - Diagnostic(ErrorCode.ERR_EscapeVariable, "r1").WithArguments("r1").WithLocation(12, 34)); + comp.VerifyDiagnostics(); VerifyParameterSymbol(comp.GetMember("Program.ReturnRef").Parameters[0], "scoped ref R r", RefKind.Ref, DeclarationScope.RefScoped); } @@ -26886,9 +26883,9 @@ public R() { } // (4,37): error CS8347: Cannot use a result of 'R.F(in Span)' in this context because it may expose variables referenced by parameter 's' outside of their declaration scope // ref readonly Span _s = ref F(stackalloc int[1]); Diagnostic(ErrorCode.ERR_EscapeCall, "F(stackalloc int[1])").WithArguments("R.F(in System.Span)", "s").WithLocation(4, 37), - // (4,39): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // (4,39): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference // ref readonly Span _s = ref F(stackalloc int[1]); - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[1]").WithArguments("System.Span").WithLocation(4, 39)); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "stackalloc int[1]").WithLocation(4, 39)); } [WorkItem(64720, "https://github.com/dotnet/roslyn/issues/64720")] @@ -27199,6 +27196,417 @@ ref struct RS Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "i4").WithArguments("i4").WithLocation(14, 31)); } + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_ValEscape_01() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + public class Repro + { + private static void Bad1(int value) + { + RefStruct s1 = new RefStruct(); + s1.RefField = ref value; // 1 + } + + private static void Bad2(int value) + { + RefStruct s1 = new RefStruct(); + s1.RefProperty.RefField = ref value; // 2 + } + + private static void Bad3(int value) + { + RefStruct s1 = new RefStruct(); + s1.RefMethod().RefField = ref value; // 3 + } + + private ref struct RefStruct + { + public ref int RefField; + [UnscopedRef] public ref RefStruct RefProperty => ref this; + [UnscopedRef] public ref RefStruct RefMethod() => ref this; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (8,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefField = ref value; // 1 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefField = ref value").WithArguments("RefField", "value").WithLocation(8, 9), + // (14,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefProperty.RefField = ref value; // 2 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefProperty.RefField = ref value").WithArguments("RefField", "value").WithLocation(14, 9), + // (20,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefMethod().RefField = ref value; // 3 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefMethod().RefField = ref value").WithArguments("RefField", "value").WithLocation(20, 9)); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_ValEscape_02() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + public class Repro + { + private static void Bad1(scoped ref RefStruct s1, int value) + { + s1.RefField = ref value; // 1 + } + + private static void Bad2(scoped ref RefStruct s1, int value) + { + s1.RefProperty.RefField = ref value; // 2 + } + + private static void Bad3(scoped ref RefStruct s1, int value) + { + s1.RefMethod().RefField = ref value; // 3 + } + + private static void Bad4(scoped in RefStruct s1, int value) + { + s1.RefField = ref value; // 4 + } + + private static void Bad5(scoped in RefStruct s1, int value) + { + s1.RefProperty.RefField = ref value; // 5 + } + + private static void Bad6(scoped in RefStruct s1, int value) + { + s1.RefMethod().RefField = ref value; // 6 + } + + private static void Bad7(in RefStruct s1, int value) + { + s1.RefField = ref value; // 7 + } + + private static void Bad8(in RefStruct s1, int value) + { + s1.RefProperty.RefField = ref value; // 8 + } + + private static void Bad9(in RefStruct s1, int value) + { + s1.RefMethod().RefField = ref value; // 9 + } + + private ref struct RefStruct + { + public ref int RefField; + [UnscopedRef] public ref RefStruct RefProperty => ref this; + [UnscopedRef] public ref RefStruct RefMethod() => ref this; + } + } + """; + + // NB: 8 and 9 are not strictly necessary here because they are assigning to an implicit copy of a readonly variable, not to the original variable. + // However, it is not deeply problematic that an error is given here. + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (7,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefField = ref value; // 1 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefField = ref value").WithArguments("RefField", "value").WithLocation(7, 9), + // (12,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefProperty.RefField = ref value; // 2 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefProperty.RefField = ref value").WithArguments("RefField", "value").WithLocation(12, 9), + // (17,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefMethod().RefField = ref value; // 3 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefMethod().RefField = ref value").WithArguments("RefField", "value").WithLocation(17, 9), + // (22,9): error CS8332: Cannot assign to a member of variable 's1' or use it as the right hand side of a ref assignment because it is a readonly variable + // s1.RefField = ref value; // 4 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "s1.RefField").WithArguments("variable", "s1").WithLocation(22, 9), + // (27,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefProperty.RefField = ref value; // 5 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefProperty.RefField = ref value").WithArguments("RefField", "value").WithLocation(27, 9), + // (32,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefMethod().RefField = ref value; // 6 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefMethod().RefField = ref value").WithArguments("RefField", "value").WithLocation(32, 9), + // (37,9): error CS8332: Cannot assign to a member of variable 's1' or use it as the right hand side of a ref assignment because it is a readonly variable + // s1.RefField = ref value; // 7 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "s1.RefField").WithArguments("variable", "s1").WithLocation(37, 9), + // (42,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefProperty.RefField = ref value; // 8 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefProperty.RefField = ref value").WithArguments("RefField", "value").WithLocation(42, 9), + // (47,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // s1.RefMethod().RefField = ref value; // 9 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s1.RefMethod().RefField = ref value").WithArguments("RefField", "value").WithLocation(47, 9)); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_ValEscape_03() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + public class Repro + { + private static void Bad1(ref RefStruct s1, int value) + { + s1 = new RefStruct(ref value); // 1 + } + + private static void Bad2(scoped ref RefStruct s1, int value) + { + s1.RefProperty = new RefStruct(ref value); // 2 + } + + private static void Bad3(scoped ref RefStruct s1, int value) + { + s1.RefMethod() = new RefStruct(ref value); // 3 + } + + private static void Bad4(scoped ref RefStruct s1, int value) + { + GetRef(ref s1) = new RefStruct(ref value); // 4 + } + + private static ref RefStruct GetRef(ref RefStruct s) => ref s; + + private ref struct RefStruct + { + public RefStruct(ref int i) => RefField = ref i; + public ref int RefField; + [UnscopedRef] public ref RefStruct RefProperty => ref this; + [UnscopedRef] public ref RefStruct RefMethod() => ref this; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (7,14): error CS8347: Cannot use a result of 'Repro.RefStruct.RefStruct(ref int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // s1 = new RefStruct(ref value); // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "new RefStruct(ref value)").WithArguments("Repro.RefStruct.RefStruct(ref int)", "i").WithLocation(7, 14), + // (7,32): error CS8166: Cannot return a parameter by reference 'value' because it is not a ref parameter + // s1 = new RefStruct(ref value); // 1 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "value").WithArguments("value").WithLocation(7, 32), + // (12,26): error CS8347: Cannot use a result of 'Repro.RefStruct.RefStruct(ref int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // s1.RefProperty = new RefStruct(ref value); // 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "new RefStruct(ref value)").WithArguments("Repro.RefStruct.RefStruct(ref int)", "i").WithLocation(12, 26), + // (12,44): error CS8166: Cannot return a parameter by reference 'value' because it is not a ref parameter + // s1.RefProperty = new RefStruct(ref value); // 2 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "value").WithArguments("value").WithLocation(12, 44), + // (17,26): error CS8347: Cannot use a result of 'Repro.RefStruct.RefStruct(ref int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // s1.RefMethod() = new RefStruct(ref value); // 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "new RefStruct(ref value)").WithArguments("Repro.RefStruct.RefStruct(ref int)", "i").WithLocation(17, 26), + // (17,44): error CS8166: Cannot return a parameter by reference 'value' because it is not a ref parameter + // s1.RefMethod() = new RefStruct(ref value); // 3 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "value").WithArguments("value").WithLocation(17, 44), + // (22,26): error CS8347: Cannot use a result of 'Repro.RefStruct.RefStruct(ref int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // GetRef(ref s1) = new RefStruct(ref value); // 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "new RefStruct(ref value)").WithArguments("Repro.RefStruct.RefStruct(ref int)", "i").WithLocation(22, 26), + // (22,44): error CS8166: Cannot return a parameter by reference 'value' because it is not a ref parameter + // GetRef(ref s1) = new RefStruct(ref value); // 4 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "value").WithArguments("value").WithLocation(22, 44)); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_ValEscape_04() + { + // test that the appropriate filtering of escape-values is occurring when the RTRS expression is on the RHS of an an assignment. + var source = """ + using System.Diagnostics.CodeAnalysis; + + public class Repro + { + private static void M1(ref RefStruct s1, int value) + { + // 's2' only contributes STE, not RSTE, to the STE of 'RefMethod()' invocation. + // STE is equal to RSTE for 's2', so it doesn't matter. + var s2 = new RefStruct(ref value); + s1 = s2.RefMethod(); // 1 + } + + private static void M2(ref RefStruct s1, ref RefStruct s2) + { + // 's2' only contributes STE, not RSTE, to the STE of 'RefMethod()' invocation. + // RSTE of `s2` is narrower than STE of 's1', but STE of 's2' equals STE of 's1', so we expect no error here. + s1 = s2.RefMethod(); + } + + private ref struct RefStruct + { + public RefStruct(ref int i) => RefField = ref i; + public ref int RefField; + [UnscopedRef] public ref RefStruct RefMethod() => ref this; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (10,14): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope + // s1 = s2.RefMethod(); // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(10, 14)); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_RefEscape_01() + { + var source = """ + public class Repro + { + private static ref RefStruct M1(ref RefStruct s1, ref RefStruct s2) + { + bool b = false; + return ref b ? ref s1 : ref s2; + } + + private static ref RefStruct M2(ref RefStruct s1) + { + RefStruct s2 = default; + // RSTE of s1 is ReturnOnly + // RSTE of s2 is CurrentMethod + return ref M1(ref s1, ref s2); // 1 + } + + private static ref RefStruct M3(ref RefStruct s1) + { + return ref M1(ref s1, ref s1); + } + + private ref struct RefStruct { } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (14,20): error CS8347: Cannot use a result of 'Repro.M1(ref Repro.RefStruct, ref Repro.RefStruct)' in this context because it may expose variables referenced by parameter 's2' outside of their declaration scope + // return ref M1(ref s1, ref s2); // 1 + Diagnostic(ErrorCode.ERR_EscapeCall, "M1(ref s1, ref s2)").WithArguments("Repro.M1(ref Repro.RefStruct, ref Repro.RefStruct)", "s2").WithLocation(14, 20), + // (14,35): error CS8168: Cannot return local 's2' by reference because it is not a ref local + // return ref M1(ref s1, ref s2); // 1 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "s2").WithArguments("s2").WithLocation(14, 35) + ); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_RefEscape_BothScopedAndUnscopedRefParameters() + { + var source = """ + public class Repro + { + private static ref RefStruct M1(ref RefStruct s1, scoped ref RefStruct s2) + { + return ref s1; + } + + private static ref RefStruct M2(ref RefStruct s1) + { + RefStruct s2 = default; + // RSTE of s1 is ReturnOnly + // RSTE of s2 is CurrentMethod, but it doesn't contribute to RSTE of the invocation. + return ref M1(ref s1, ref s2); + } + + private static ref RefStruct M3(ref RefStruct s1) + { + return ref M1(ref s1, ref s1); + } + + private ref struct RefStruct { } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_RefEscape_02() + { + var source = """ + public class Repro + { + private static ref RefStruct M1(ref RefStruct s1, RefStruct s2) + { + return ref s1; + } + + private static ref RefStruct M2(ref RefStruct s1, int param) + { + RefStruct s2 = new RefStruct(ref param); + // RSTE of s1 is ReturnOnly + // STE of s2 is CurrentMethod, but this is not contributed to the call to M1. + // We error due to arg mixing, not due to the return value. + return ref M1(ref s1, s2); + } + + private ref struct RefStruct + { + public RefStruct(ref int i) { } + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (14,20): error CS8350: This combination of arguments to 'Repro.M1(ref Repro.RefStruct, Repro.RefStruct)' is disallowed because it may expose variables referenced by parameter 's2' outside of their declaration scope + // return ref M1(ref s1, s2); + Diagnostic(ErrorCode.ERR_CallArgMixing, "M1(ref s1, s2)").WithArguments("Repro.M1(ref Repro.RefStruct, Repro.RefStruct)", "s2").WithLocation(14, 20), + // (14,31): error CS8352: Cannot use variable 's2' in this context because it may expose referenced variables outside of their declaration scope + // return ref M1(ref s1, s2); + Diagnostic(ErrorCode.ERR_EscapeVariable, "s2").WithArguments("s2").WithLocation(14, 31) + ); + } + + [Fact, WorkItem(65648, "https://github.com/dotnet/roslyn/issues/65648")] + public void ReturnRefToRefStruct_VariousInputAndOutputRefKinds() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + public class Repro + { + private static void Bad1(int value) + { + RefStruct s1 = new RefStruct(); + GetReference1(ref s1).RefField = ref value; // 1 + GetReference2(in s1).RefField = ref value; // 2 + GetReference3(out s1).RefField = ref value; // 3 + + GetReadonlyReference1(ref s1).RefField = ref value; // 4 + GetReadonlyReference2(in s1).RefField = ref value; // 5 + GetReadonlyReference3(out s1).RefField = ref value; // 6 + } + + static ref RefStruct GetReference1(ref RefStruct rs) => throw null!; + static ref RefStruct GetReference2(in RefStruct rs) => throw null!; + static ref RefStruct GetReference3([UnscopedRef] out RefStruct rs) => throw null!; + + static ref readonly RefStruct GetReadonlyReference1(ref RefStruct rs) => throw null!; + static ref readonly RefStruct GetReadonlyReference2(in RefStruct rs) => throw null!; + static ref readonly RefStruct GetReadonlyReference3([UnscopedRef] out RefStruct rs) => throw null!; + + private ref struct RefStruct + { + public ref int RefField; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (8,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // GetReference1(ref s1).RefField = ref value; // 1 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "GetReference1(ref s1).RefField = ref value").WithArguments("RefField", "value").WithLocation(8, 9), + // (9,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // GetReference2(in s1).RefField = ref value; // 2 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "GetReference2(in s1).RefField = ref value").WithArguments("RefField", "value").WithLocation(9, 9), + // (10,9): error CS8374: Cannot ref-assign 'value' to 'RefField' because 'value' has a narrower escape scope than 'RefField'. + // GetReference3(out s1).RefField = ref value; // 3 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "GetReference3(out s1).RefField = ref value").WithArguments("RefField", "value").WithLocation(10, 9), + // (12,9): error CS8332: Cannot assign to a member of method 'GetReadonlyReference1' or use it as the right hand side of a ref assignment because it is a readonly variable + // GetReadonlyReference1(ref s1).RefField = ref value; // 4 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference1(ref s1).RefField").WithArguments("method", "GetReadonlyReference1").WithLocation(12, 9), + // (13,9): error CS8332: Cannot assign to a member of method 'GetReadonlyReference2' or use it as the right hand side of a ref assignment because it is a readonly variable + // GetReadonlyReference2(in s1).RefField = ref value; // 5 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference2(in s1).RefField").WithArguments("method", "GetReadonlyReference2").WithLocation(13, 9), + // (14,9): error CS8332: Cannot assign to a member of method 'GetReadonlyReference3' or use it as the right hand side of a ref assignment because it is a readonly variable + // GetReadonlyReference3(out s1).RefField = ref value; // 6 + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "GetReadonlyReference3(out s1).RefField").WithArguments("method", "GetReadonlyReference3").WithLocation(14, 9)); + } + [Theory] [InlineData(LanguageVersion.CSharp10)] [InlineData(LanguageVersion.CSharp11)] From 0ae1a724770e6335a46fac8763c6a37bbf6f8b5f Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 21 Dec 2022 11:07:15 +0530 Subject: [PATCH 134/274] Address feedback and rename --- .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 8 ++++---- .../DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index dbc0d66c42850..81cf37d95f12f 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -25,7 +25,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// /// Return all cached local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer). /// Otherwise, return null. - /// For the latter case, indicates if the analyzer is suppressed + /// For the latter case, indicates if the analyzer is suppressed /// for the given document/project. If suppressed, the caller does not need to compute the diagnostics for the given /// analyzer. Otherwise, diagnostics need to be computed. /// @@ -37,11 +37,11 @@ internal partial class DiagnosticIncrementalAnalyzer bool isActiveDocument, bool isVisibleDocument, bool isOpenDocument, bool isGeneratedRazorDocument, CancellationToken cancellationToken, - out bool skipAnalysisForNonCachedDocument) + out bool isAnalyzerSuppressed) { Debug.Assert(isActiveDocument || isOpenDocument || isGeneratedRazorDocument); - skipAnalysisForNonCachedDocument = false; + isAnalyzerSuppressed = false; try { @@ -57,7 +57,7 @@ internal partial class DiagnosticIncrementalAnalyzer // If so, we set the flag indicating that the client can skip analysis for this document. // Regardless of whether or not the analyzer is suppressed for project or document, // we return null to indicate that no diagnostics are cached for this document for the given version. - skipAnalysisForNonCachedDocument = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || + isAnalyzerSuppressed = !DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) || !IsAnalyzerEnabledForDocument(stateSet.Analyzer, existingData, analysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, isOpenDocument, isGeneratedRazorDocument); return null; diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 34d4cfa71d3dd..f65bbfdc99777 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -73,12 +73,12 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi { var data = TryGetCachedDocumentAnalysisData(document, stateSet, kind, version, backgroundAnalysisScope, compilerDiagnosticsScope, isActiveDocument, isVisibleDocument, - isOpenDocument, isGeneratedRazorDocument, cancellationToken, out var skipAnalysisForNonCachedDocument); + isOpenDocument, isGeneratedRazorDocument, cancellationToken, out var isAnalyzerSuppressed); if (data.HasValue) { PersistAndRaiseDiagnosticsIfNeeded(data.Value, stateSet); } - else if (!skipAnalysisForNonCachedDocument) + else if (!isAnalyzerSuppressed) { nonCachedStateSets.Add(stateSet); } From 9931fba69a706fc33cc5e5af98e2b2df862d3a93 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 21 Dec 2022 11:27:49 +0530 Subject: [PATCH 135/274] Add regression test for #66085. Confirmed it fails with `treeBasedOptions = false`, i.e. disabled by default analyzer is enabled with SpecificDiagnosticOptions --- .../Diagnostics/DiagnosticAnalyzerTests.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticAnalyzerTests.cs index 583ee75b166e4..9238b58cfb663 100644 --- a/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Diagnostics/DiagnosticAnalyzerTests.cs @@ -4010,19 +4010,40 @@ public record A(int X, int Y);"; Diagnostic("MyDiagnostic", @"public record A(int X, int Y);").WithLocation(2, 1)); } - [Fact, WorkItem(64771, "https://github.com/dotnet/roslyn/issues/64771")] - public void TestDisabledByDefaultAnalyzerEnabledForSingleFile() + [Theory, CombinatorialData] + [WorkItem(64771, "https://github.com/dotnet/roslyn/issues/64771")] + [WorkItem(66085, "https://github.com/dotnet/roslyn/issues/66085")] + public void TestDisabledByDefaultAnalyzerEnabledForSingleFile(bool treeBasedOptions) { var source1 = "class C1 { }"; var source2 = "class C2 { }"; var source3 = "class C3 { }"; var analyzer = new AnalyzerWithDisabledRules(); - // Enable disabled by default analyzer for first source file with analyzer config options. var compilation = CreateCompilation(new[] { source1, source2, source3 }); - var tree1 = compilation.SyntaxTrees[0]; - var options = compilation.Options.WithSyntaxTreeOptionsProvider( - new TestSyntaxTreeOptionsProvider(tree1, (AnalyzerWithDisabledRules.Rule.Id, ReportDiagnostic.Warn))); + + var options = compilation.Options; + if (treeBasedOptions) + { + // Enable disabled by default analyzer for first source file with analyzer config options. + var tree1 = compilation.SyntaxTrees[0]; + options = compilation.Options.WithSyntaxTreeOptionsProvider( + new TestSyntaxTreeOptionsProvider(tree1, (AnalyzerWithDisabledRules.Rule.Id, ReportDiagnostic.Warn))); + } + else + { + // Enable disabled by default analyzer for entire compilation with SpecificDiagnosticOptions + // and disable the analyzer for second and third source file with analyzer config options. + // So, effectively the analyzer is enabled only for first source file. + var tree2 = compilation.SyntaxTrees[1]; + var tree3 = compilation.SyntaxTrees[2]; + options = compilation.Options + .WithSpecificDiagnosticOptions(ImmutableDictionary.Empty.Add(AnalyzerWithDisabledRules.Rule.Id, ReportDiagnostic.Warn)) + .WithSyntaxTreeOptionsProvider(new TestSyntaxTreeOptionsProvider( + (tree2, new[] { (AnalyzerWithDisabledRules.Rule.Id, ReportDiagnostic.Suppress) }), + (tree3, new[] { (AnalyzerWithDisabledRules.Rule.Id, ReportDiagnostic.Suppress) }))); + } + compilation = compilation.WithOptions(options); // Verify single analyzer diagnostic reported in the compilation. From 8c096384eb256d66cc4d34f7d82bb7dc61519bb7 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Wed, 21 Dec 2022 11:40:31 +0530 Subject: [PATCH 136/274] Fixes #66085 Ensure that the code in analyzer driver that checks if disabled by default analyzer is enabled for a specific tree checks for configuration through CompilationOptions.SpecificDiagnosticOptions. These options can be set through ruleset file or via API in test scenarios, so we need to ensure they are respected. --- .../Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 1168a7f9dc9ae..802cc4eb1389e 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -1333,10 +1333,11 @@ private ImmutableHashSet ComputeSuppressedAnalyzersForTree(S var hasUnsuppressedDiagnostic = false; foreach (var descriptor in descriptors) { - _ = options.TryGetGlobalDiagnosticValue(descriptor.Id, AnalyzerExecutor.CancellationToken, out var configuredSeverity); - if (options.TryGetDiagnosticValue(tree, descriptor.Id, AnalyzerExecutor.CancellationToken, out var diagnosticSeverity)) + var configuredSeverity = descriptor.GetEffectiveSeverity(AnalyzerExecutor.Compilation.Options); + if (options.TryGetDiagnosticValue(tree, descriptor.Id, AnalyzerExecutor.CancellationToken, out var severityFromOptions) || + options.TryGetGlobalDiagnosticValue(descriptor.Id, AnalyzerExecutor.CancellationToken, out severityFromOptions)) { - configuredSeverity = diagnosticSeverity; + configuredSeverity = severityFromOptions; } // Disabled by default descriptor with default configured severity is equivalent to suppressed. From 228d04a337234778daf75406e9e984164e92a8ed Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Wed, 21 Dec 2022 17:58:03 +0330 Subject: [PATCH 137/274] BoundIsPatternExpression.GetDecisionDagForLowering - when recreating a DAG, do that for the the pattern with any 'not's removed, as consumers expect. (#65878) Use the inner pattern to recreate the decision DAG for is-expressions Fixes #65876 --- .../BoundTree/BoundIsPatternExpression.cs | 4 +- .../PatternMatchingTests_ListPatterns.cs | 157 ++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundIsPatternExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundIsPatternExpression.cs index a5500883aa3c0..bab83be24fbf5 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundIsPatternExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundIsPatternExpression.cs @@ -13,11 +13,13 @@ public BoundDecisionDag GetDecisionDagForLowering(CSharpCompilation compilation) BoundDecisionDag decisionDag = this.ReachabilityDecisionDag; if (decisionDag.ContainsAnySynthesizedNodes()) { + bool negated = this.Pattern.IsNegated(out var innerPattern); + Debug.Assert(negated == this.IsNegated); decisionDag = DecisionDagBuilder.CreateDecisionDagForIsPattern( compilation, this.Syntax, this.Expression, - this.Pattern, + innerPattern, this.WhenTrueLabel, this.WhenFalseLabel, BindingDiagnosticBag.Discarded, diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs index 2078ea76d3e0b..343df5fc5a17c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs @@ -3090,6 +3090,163 @@ public void Test2(int[] a) ); } + [Fact, WorkItem(65876, "https://github.com/dotnet/roslyn/issues/65876")] + public void ListPattern_Negated_03() + { + var source = """ +using System; +public class C +{ + static void Main() + { + Console.WriteLine(M1(new[]{1,2})); + Console.WriteLine(M1(new[]{2,1})); + Console.WriteLine(M1(new[]{1})); + Console.WriteLine(M1(new[]{0})); + + Console.WriteLine(M2(new[]{1,2})); + Console.WriteLine(M2(new[]{2,1})); + Console.WriteLine(M2(new[]{1})); + Console.WriteLine(M2(new[]{0})); + } + + public static bool M1(int[] a) { + return a is not ([1,2,..] or [..,2,1] or [1]); + } + public static bool M2(int[] a) { + return !(a is ([1,2,..] or [..,2,1] or [1])); + } +} +"""; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: @" +False +False +False +True +False +False +False +True +"); + } + + [Fact] + public void ListPattern_Negated_04() + { + var source = """ +using System; +public class C +{ + static void Main() + { + Console.WriteLine(M1(new[]{1,2})); + Console.WriteLine(M1(new[]{2,1})); + Console.WriteLine(M1(new[]{1})); + Console.WriteLine(M1(new[]{0})); + + Console.WriteLine(M2(new[]{1,2})); + Console.WriteLine(M2(new[]{2,1})); + Console.WriteLine(M2(new[]{1})); + Console.WriteLine(M2(new[]{0})); + } + + public static int M1(int[] a) { + return a switch + { + not ([1,2,..] or [..,2,1] or [1]) => 1, + [1,2,..] => 2, + [..,2,1] => 3, + [1] => 4, + }; + } + public static int M2(int[] a) { + switch (a) + { + case not ([1,2,..] or [..,2,1] or [1]): + return 1; + case [1,2,..]: + return 2; + case [..,2,1]: + return 3; + case [1]: + return 4; + } + } +} +"""; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: @" +2 +3 +4 +1 +2 +3 +4 +1 +"); + } + + [Fact] + public void ListPattern_Negated_05() + { + var source = """ +using System; +public class C +{ + static void Main() + { + Console.WriteLine(M1(new[]{1,2}, new[]{1})); + Console.WriteLine(M1(new[]{1}, new[]{2,1})); + Console.WriteLine(M1(new[]{2,1}, new[]{1,2})); + Console.WriteLine(M1(new[]{0}, new[]{0})); + + Console.WriteLine(M2(new[]{1,2}, new[]{1})); + Console.WriteLine(M2(new[]{1}, new[]{2,1})); + Console.WriteLine(M2(new[]{2,1}, new[]{1,2})); + Console.WriteLine(M2(new[]{0}, new[]{0})); + } + + public static int M1(int[] a, int[] b) { + return (a, b) switch + { + (not ([1,2,..] or [..,2,1] or [1]), + not ([1,2,..] or [..,2,1] or [1])) => 1, + ([1,2,..] or [1], [..,2,1] or [1]) => 2, + ([..,2,1], [1,2,..]) => 3, + _ => 0 + }; + } + public static int M2(int[] a, int[] b) { + switch (a, b) + { + case (not ([1,2,..] or [..,2,1] or [1]), + not ([1,2,..] or [..,2,1] or [1])): + return 1; + case ([1,2,..] or [1], [..,2,1] or [1]): + return 2; + case ([..,2,1], [1,2,..]): + return 3; + default: + return 0; + } + } +} +"""; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: @" +2 +2 +3 +1 +2 +2 +3 +1 +"); + } + [Fact] public void ListPattern_UseSiteErrorOnIndexerAndSlice() { From 45046d0a51e7aa1f67e11b9dd00621535f9fe39c Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 21 Dec 2022 06:32:44 -0800 Subject: [PATCH 138/274] Split out ref safety analysis into a separate pass after binding (#64928) --- .../Compiler Breaking Changes - DotNet 7.md | 62 ++ .../Portable/Binder/Binder.ValueChecks.cs | 192 ++-- .../CSharp/Portable/Binder/Binder.cs | 2 +- .../CSharp/Portable/Binder/Binder_Await.cs | 2 +- .../Portable/Binder/Binder_Deconstruct.cs | 27 +- .../Portable/Binder/Binder_Expressions.cs | 57 +- .../Portable/Binder/Binder_Initializers.cs | 12 - .../Binder/Binder_InterpolatedString.cs | 11 +- .../Portable/Binder/Binder_Invocation.cs | 28 - .../Portable/Binder/Binder_Operators.cs | 45 +- .../CSharp/Portable/Binder/Binder_Patterns.cs | 131 +-- .../Portable/Binder/Binder_Statements.cs | 178 ++-- .../Portable/Binder/BuckStopsHereBinder.cs | 4 +- .../Portable/Binder/ForEachLoopBinder.cs | 15 +- .../Portable/Binder/InContainerBinder.cs | 2 - .../CSharp/Portable/Binder/InMethodBinder.cs | 3 - .../Portable/Binder/LocalScopeBinder.cs | 34 - .../Portable/Binder/RefSafetyAnalysis.cs | 897 ++++++++++++++++++ .../CSharp/Portable/Binder/SwitchBinder.cs | 4 +- .../Portable/Binder/SwitchBinder_Patterns.cs | 2 +- .../Binder/SwitchExpressionArmBinder.cs | 10 +- .../Portable/Binder/SwitchExpressionBinder.cs | 8 +- .../Portable/Binder/UsingStatementBinder.cs | 2 +- .../Binder/WithExternAliasesBinder.cs | 2 - .../Binder/WithLambdaParametersBinder.cs | 2 - .../WithUsingNamespacesAndTypesBinder.cs | 2 - .../CSharp/Portable/BoundTree/BoundNodes.xml | 10 +- .../CSharp/Portable/BoundTree/Constructors.cs | 2 +- .../InterpolatedStringHandlerData.cs | 7 - .../OutDeconstructVarPendingInference.cs | 2 +- .../CSharp/Portable/CodeGen/Optimizer.cs | 12 - .../Portable/Compiler/MethodCompiler.cs | 7 +- .../Portable/FlowAnalysis/FlowAnalysisPass.cs | 6 +- .../NullableWalker.PlaceholderLocal.cs | 2 - .../Generated/BoundNodes.xml.Generated.cs | 145 ++- .../ClosureConversion/ClosureConversion.cs | 2 +- .../Instrumentation/DebugInfoInjector.cs | 2 +- .../Lowering/LocalRewriter/LocalRewriter.cs | 2 +- .../LocalRewriter/LocalRewriter_Block.cs | 8 +- .../LocalRewriter_StringInterpolation.cs | 2 +- .../Lowering/MethodToClassRewriter.cs | 4 +- .../Lowering/SyntheticBoundNodeFactory.cs | 6 +- .../CSharp/Portable/Symbols/LocalSymbol.cs | 12 - .../Symbols/Source/SourceLocalSymbol.cs | 96 +- .../Source/SourceMemberContainerSymbol.cs | 2 + .../InterpolatedStringBuilderLocalSymbol.cs | 41 - .../Symbols/Synthesized/SynthesizedLocal.cs | 15 +- .../Synthesized/TypeSubstitutedLocalSymbol.cs | 12 - .../Symbols/UpdatedContainingSymbolLocal.cs | 2 - .../Diagnostics/OperationAnalyzerTests.cs | 7 +- .../Semantic/Semantics/InterpolationTests.cs | 154 +++ .../Semantics/NullableReferenceTypesTests.cs | 5 +- .../Semantic/Semantics/RefEscapingTests.cs | 18 +- .../Test/Semantic/Semantics/RefFieldTests.cs | 608 +++++++++++- .../Semantics/StackAllocInitializerTests.cs | 11 +- .../Semantics/StructConstructorTests.cs | 20 +- .../Rewriters/CapturedVariableRewriter.cs | 2 +- .../Symbols/EELocalSymbolBase.cs | 12 - .../Symbols/EEMethodSymbol.cs | 2 +- 59 files changed, 2117 insertions(+), 855 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs delete mode 100644 src/Compilers/CSharp/Portable/Symbols/Synthesized/InterpolatedStringBuilderLocalSymbol.cs diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index a749eb2266dc6..8cd2f482029e5 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -1,5 +1,67 @@ # This document lists known breaking changes in Roslyn after .NET 6 all the way to .NET 7. +## Ref safety errors do not affect conversion from lambda expression to delegate + +***Introduced in Visual Studio 2022 version 17.5*** + +Ref safety errors reported in a lambda body no longer affect whether the lambda expression is convertible to a delegate type. This change can affect overload resolution. + +In the example below, the call to `M(x => ...)` is ambiguous with Visual Studio 17.5 because both `M(D1)` and `M(D2)` are now considered applicable, even though the call to `F(ref x, ref y)` within the lambda body will result in a ref safety with `M(D1)` (see the examples in `d1` and `d2` for comparison). Previously, the call bound unambiguously to `M(D2)` because the `M(D1)` overload was considered not applicable. +```csharp +using System; + +ref struct R { } + +delegate R D1(R r); +delegate object D2(object o); + +class Program +{ + static void M(D1 d1) { } + static void M(D2 d2) { } + + static void F(ref R x, ref Span y) { } + static void F(ref object x, ref Span y) { } + + static void Main() + { + // error CS0121: ambiguous between: 'M(D1)' and 'M(D2)' + M(x => + { + Span y = stackalloc int[1]; + F(ref x, ref y); + return x; + }); + + D1 d1 = x1 => + { + Span y1 = stackalloc int[1]; + F(ref x1, ref y1); // error CS8352: 'y2' may expose referenced variables + return x1; + }; + + D2 d2 = x2 => + { + Span y2 = stackalloc int[1]; + F(ref x2, ref y2); // ok: F(ref object x, ref Span y) + return x2; + }; + } +} +``` + +To workaround the overload resolution changes, use explicit types for the lambda parameters or delegate. + +```csharp + // ok: M(D2) + M((object x) => + { + Span y = stackalloc int[1]; + F(ref x, ref y); // ok: F(ref object x, ref Span y) + return x; + }); +``` + ## Raw string interpolations at start of line. ***Introduced in Visual Studio 2022 version 17.5*** diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index c70f1312a4f2f..af6b493e6a954 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -11,17 +11,14 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { - internal partial class Binder - { #nullable enable - + internal partial class RefSafetyAnalysis + { private enum EscapeLevel : uint { CallingMethod = Binder.CallingMethodScope, @@ -156,9 +153,11 @@ public void Deconstruct(out ParameterSymbol? parameter, out BoundExpression argu ? p.ToString() : Argument.ToString(); } - + } #nullable disable + internal partial class Binder + { /// /// For the purpose of escape verification we operate with the depth of local scopes. /// The depth is a uint, with smaller number representing shallower/wider scopes. @@ -850,19 +849,22 @@ private bool CheckLocalValueKind(SyntaxNode node, BoundLocal local, BindValueKin return true; } + } + internal partial class RefSafetyAnalysis + { private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics) { LocalSymbol localSymbol = local.LocalSymbol; // if local symbol can escape to the same or wider/shallower scope then escapeTo // then it is all ok, otherwise it is an error. - if (localSymbol.RefEscapeScope <= escapeTo) + if (GetLocalScopes(localSymbol).RefEscapeScope <= escapeTo) { return true; } - var inUnsafeRegion = this.InUnsafeRegion; + var inUnsafeRegion = _inUnsafeRegion; if (escapeTo is Binder.CallingMethodScope or Binder.ReturnOnlyScope) { if (localSymbol.RefKind == RefKind.None) @@ -892,7 +894,10 @@ private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, uint escapeT Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, localSymbol); return inUnsafeRegion; } + } + internal partial class Binder + { private bool CheckParameterValueKind(SyntaxNode node, BoundParameter parameter, BindValueKind valueKind, bool checkingReceiver, BindingDiagnosticBag diagnostics) { ParameterSymbol parameterSymbol = parameter.ParameterSymbol; @@ -921,7 +926,10 @@ private bool CheckParameterValueKind(SyntaxNode node, BoundParameter parameter, return true; } + } + internal partial class RefSafetyAnalysis + { private static EscapeLevel? EscapeLevelFromScope(uint scope) => scope switch { Binder.ReturnOnlyScope => EscapeLevel.ReturnOnly, @@ -960,12 +968,12 @@ private static uint GetParameterRefEscape(ParameterSymbol parameter) private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, uint escapeTo, BindingDiagnosticBag diagnostics) { Debug.Assert(escapeTo is Binder.CallingMethodScope or Binder.ReturnOnlyScope); - if (UseUpdatedEscapeRules) + if (_useUpdatedEscapeRules) { if (GetParameterValEscape(parameter) > escapeTo) { - Error(diagnostics, this.InUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameter); - return this.InUnsafeRegion; + Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameter); + return _inUnsafeRegion; } return true; } @@ -983,7 +991,7 @@ private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, { var isRefScoped = parameterSymbol.EffectiveScope == DeclarationScope.RefScoped; Debug.Assert(parameterSymbol.RefKind == RefKind.None || isRefScoped || refSafeToEscape == Binder.ReturnOnlyScope); - var inUnsafeRegion = this.InUnsafeRegion; + var inUnsafeRegion = _inUnsafeRegion; if (parameter is BoundThisReference) { @@ -1015,7 +1023,10 @@ private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, // can ref-escape to any scope otherwise return true; } + } + internal partial class Binder + { private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess, BindValueKind valueKind, bool checkingReceiver, BindingDiagnosticBag diagnostics) { var fieldSymbol = fieldAccess.FieldSymbol; @@ -1142,7 +1153,10 @@ private bool CheckSimpleAssignmentValueKind(SyntaxNode node, BoundAssignmentOper Error(diagnostics, GetStandardLvalueError(valueKind), node); return false; } + } + internal partial class RefSafetyAnalysis + { private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheContainingExpression) { var fieldSymbol = fieldAccess.FieldSymbol; @@ -1153,7 +1167,7 @@ private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheCont return Binder.CallingMethodScope; } - if (UseUpdatedEscapeRules) + if (_useUpdatedEscapeRules) { // SPEC: If `F` is a `ref` field its ref-safe-to-escape scope is the safe-to-escape scope of `e`. if (fieldSymbol.RefKind != RefKind.None) @@ -1177,7 +1191,7 @@ private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, Debug.Assert(fieldAccess.ReceiverOpt is { }); - if (UseUpdatedEscapeRules) + if (_useUpdatedEscapeRules) { // SPEC: If `F` is a `ref` field its ref-safe-to-escape scope is the safe-to-escape scope of `e`. if (fieldSymbol.RefKind != RefKind.None) @@ -1203,7 +1217,10 @@ private bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess even // for other events defer to the receiver. return CheckRefEscape(node, eventAccess.ReceiverOpt, escapeFrom, escapeTo, checkingReceiver: true, diagnostics: diagnostics); } + } + internal partial class Binder + { private bool CheckEventValueKind(BoundEventAccess boundEvent, BindValueKind valueKind, BindingDiagnosticBag diagnostics) { // Compound assignment (actually "event assignment") is allowed "everywhere", subject to the restrictions of @@ -1581,7 +1598,10 @@ private bool IsBadBaseAccess(SyntaxNode node, BoundExpression receiverOpt, Symbo return false; } + } + internal partial class RefSafetyAnalysis + { internal uint GetInterpolatedStringHandlerConversionEscapeScope( BoundExpression expression, uint scopeOfTheContainingExpression) @@ -1952,18 +1972,18 @@ private void GetInvocationArgumentsForEscape( _ => throw ExceptionUtilities.UnexpectedValue(symbol) }; - if (receiver is not BoundValuePlaceholderBase && method is not null && receiver.Type!.IsValueType) + if (receiver is not BoundValuePlaceholderBase && method is not null && receiver.Type?.IsValueType == true) { - var receiverAddressKind = method.IsEffectivelyReadOnly ? AddressKind.ReadOnly : AddressKind.Writeable; + var receiverAddressKind = method.IsEffectivelyReadOnly ? Binder.AddressKind.ReadOnly : Binder.AddressKind.Writeable; if (!Binder.HasHome(receiver, receiverAddressKind, - ContainingMemberOrLambda, - Compilation.IsPeVerifyCompatEnabled, + _symbol, + _compilation.IsPeVerifyCompatEnabled, stackLocalsOpt: null)) { // Equivalent to a non-ref local with the underlying receiver as an initializer provided at declaration - receiver = new BoundCapturedReceiverPlaceholder(receiver.Syntax, receiver, LocalScopeDepth, receiver.Type).MakeCompilerGenerated(); + receiver = new BoundCapturedReceiverPlaceholder(receiver.Syntax, receiver, _localScopeDepth, receiver.Type).MakeCompilerGenerated(); } } @@ -2261,7 +2281,7 @@ private bool UseUpdatedEscapeRulesForInvocation(Symbol symbol) return method?.UseUpdatedEscapeRules == true; } - private static bool ShouldInferDeclarationExpressionValEscape(BoundExpression argument, [NotNullWhen(true)] out SourceLocalSymbol? localSymbol) + private bool ShouldInferDeclarationExpressionValEscape(BoundExpression argument, [NotNullWhen(true)] out SourceLocalSymbol? localSymbol) { var symbol = argument switch { @@ -2269,7 +2289,8 @@ private static bool ShouldInferDeclarationExpressionValEscape(BoundExpression ar BoundLocal { DeclarationKind: not BoundLocalDeclarationKind.None } l => l.LocalSymbol, _ => null }; - if (symbol is SourceLocalSymbol { ValEscapeScope: CallingMethodScope } local) + if (symbol is SourceLocalSymbol local && + GetLocalScopes(local).ValEscapeScope == Binder.CallingMethodScope) { localSymbol = local; return true; @@ -2361,7 +2382,7 @@ private bool CheckInvocationArgMixing( // track the widest scope that arguments could safely escape to. // use this scope as the inferred STE of declaration expressions. - var inferredDestinationValEscape = CallingMethodScope; + var inferredDestinationValEscape = Binder.CallingMethodScope; foreach (var (parameter, argument, _) in escapeArguments) { // in the old rules, we assume that refs cannot escape into ref struct variables. @@ -2379,7 +2400,7 @@ private bool CheckInvocationArgMixing( { if (ShouldInferDeclarationExpressionValEscape(argument, out var localSymbol)) { - localSymbol.SetValEscape(inferredDestinationValEscape); + SetLocalScopes(localSymbol, refEscapeScope: _localScopeDepth, valEscapeScope: inferredDestinationValEscape); } } @@ -2462,7 +2483,7 @@ void inferDeclarationExpressionValEscape() { // find the widest scope that arguments could safely escape to. // use this scope as the inferred STE of declaration expressions. - var inferredDestinationValEscape = CallingMethodScope; + var inferredDestinationValEscape = Binder.CallingMethodScope; foreach (var (_, fromArg, _, isRefEscape) in escapeValues) { inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, isRefEscape @@ -2474,7 +2495,7 @@ void inferDeclarationExpressionValEscape() { if (ShouldInferDeclarationExpressionValEscape(argument, out var localSymbol)) { - localSymbol.SetValEscape(inferredDestinationValEscape); + SetLocalScopes(localSymbol, refEscapeScope: _localScopeDepth, valEscapeScope: inferredDestinationValEscape); } } } @@ -2528,7 +2549,10 @@ private static ErrorCode GetStandardCallEscapeError(bool checkingReceiver) { return checkingReceiver ? ErrorCode.ERR_EscapeCall2 : ErrorCode.ERR_EscapeCall; } + } + internal partial class Binder + { private static void ReportReadonlyLocalError(SyntaxNode node, LocalSymbol local, BindValueKind kind, bool checkingReceiver, BindingDiagnosticBag diagnostics) { Debug.Assert((object)local != null); @@ -2671,7 +2695,10 @@ private static ErrorCode GetStandardLvalueError(BindValueKind kind) throw ExceptionUtilities.UnexpectedValue(kind); } + } + internal partial class RefSafetyAnalysis + { private static ErrorCode GetStandardRValueRefEscapeError(uint escapeTo) { if (escapeTo is Binder.CallingMethodScope or Binder.ReturnOnlyScope) @@ -2681,7 +2708,10 @@ private static ErrorCode GetStandardRValueRefEscapeError(uint escapeTo) return ErrorCode.ERR_EscapeOther; } + } + internal partial class Binder + { private static void ReportReadOnlyFieldError(FieldSymbol field, SyntaxNode node, BindValueKind kind, bool checkingReceiver, BindingDiagnosticBag diagnostics) { Debug.Assert((object)field != null); @@ -2750,12 +2780,14 @@ private static void ReportReadOnlyError(Symbol symbol, SyntaxNode node, BindValu int index = (checkingReceiver ? 3 : 0) + (kind == BindValueKind.RefReturn ? 0 : (RequiresRefOrOut(kind) ? 1 : 2)); Error(diagnostics, ReadOnlyErrors[index], node, symbolKind, new FormattedSymbol(symbol, SymbolDisplayFormat.ShortFormat)); } + } + internal partial class RefSafetyAnalysis + { /// - /// Checks whether given expression can escape from the current scope to the - /// In a case if it cannot a bad expression is returned and diagnostics is produced. + /// Checks whether given expression can escape from the current scope to the . /// - internal BoundExpression ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, BindingDiagnosticBag diagnostics) + internal void ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, BindingDiagnosticBag diagnostics) { // The result of escape analysis is affected by the expression's type. // We can't do escape analysis on expressions which lack a type, such as 'target typed new()', until they are converted. @@ -2763,20 +2795,12 @@ internal BoundExpression ValidateEscape(BoundExpression expr, uint escapeTo, boo if (isByRef) { - if (CheckRefEscape(expr.Syntax, expr, this.LocalScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics)) - { - return expr; - } + CheckRefEscape(expr.Syntax, expr, _localScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics); } else { - if (CheckValEscape(expr.Syntax, expr, this.LocalScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics)) - { - return expr; - } + CheckValEscape(expr.Syntax, expr, _localScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics); } - - return ToBadExpression(expr); } /// @@ -2837,14 +2861,14 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres return GetParameterRefEscape(((BoundParameter)expr).ParameterSymbol); case BoundKind.Local: - return ((BoundLocal)expr).LocalSymbol.RefEscapeScope; + return GetLocalScopes(((BoundLocal)expr).LocalSymbol).RefEscapeScope; case BoundKind.CapturedReceiverPlaceholder: // Equivalent to a non-ref local with the underlying receiver as an initializer provided at declaration return ((BoundCapturedReceiverPlaceholder)expr).LocalScopeDepth; case BoundKind.ThisReference: - var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter; + var thisParam = ((MethodSymbol)_symbol).ThisParameter; Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything)); return GetParameterRefEscape(thisParam); @@ -3098,7 +3122,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF break; case BoundKind.ThisReference: - var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter; + var thisParam = ((MethodSymbol)_symbol).ThisParameter; Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything)); return CheckParameterRefEscape(node, expr, thisParam, escapeTo, checkingReceiver, diagnostics); @@ -3300,6 +3324,17 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF escapeTo, checkingReceiver: false, diagnostics); + + case BoundKind.Conversion: + var conversion = (BoundConversion)expr; + if (conversion.Conversion == Conversion.ImplicitThrow) + { + return CheckRefEscape(node, conversion.Operand, escapeFrom, escapeTo, checkingReceiver, diagnostics); + } + break; + + case BoundKind.ThrowExpression: + return true; } // At this point we should have covered all the possible cases for anything that is not a strict RValue. @@ -3358,7 +3393,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres switch (expr.Kind) { case BoundKind.ThisReference: - var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter; + var thisParam = ((MethodSymbol)_symbol).ThisParameter; Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything)); return GetParameterValEscape(thisParam); case BoundKind.DefaultLiteral: @@ -3391,10 +3426,12 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres return Binder.CallingMethodScope; case BoundKind.DeconstructValuePlaceholder: - return ((BoundDeconstructValuePlaceholder)expr).ValEscape; + case BoundKind.InterpolatedStringArgumentPlaceholder: + case BoundKind.AwaitableValuePlaceholder: + return GetPlaceholderScope((BoundValuePlaceholderBase)expr); case BoundKind.Local: - return ((BoundLocal)expr).LocalSymbol.ValEscapeScope; + return GetLocalScopes(((BoundLocal)expr).LocalSymbol).ValEscapeScope; case BoundKind.CapturedReceiverPlaceholder: // Equivalent to a non-ref local with the underlying receiver as an initializer provided at declaration @@ -3485,15 +3522,6 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres isRefEscape: false); } - case BoundKind.ImplicitIndexerReceiverPlaceholder: - return ((BoundImplicitIndexerReceiverPlaceholder)expr).ValEscape; - - case BoundKind.ListPatternReceiverPlaceholder: - return ((BoundListPatternReceiverPlaceholder)expr).ValEscape; - - case BoundKind.SlicePatternReceiverPlaceholder: - return ((BoundSlicePatternReceiverPlaceholder)expr).ValEscape; - case BoundKind.ImplicitIndexerAccess: var implicitIndexerAccess = (BoundImplicitIndexerAccess)expr; @@ -3654,18 +3682,11 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres // location. return scopeOfTheContainingExpression; - case BoundKind.InterpolatedStringArgumentPlaceholder: - // We saved off the safe-to-escape of the argument when we did binding - return ((BoundInterpolatedStringArgumentPlaceholder)expr).ValSafeToEscape; - case BoundKind.DisposableValuePlaceholder: // Disposable value placeholder is only ever used to lookup a pattern dispose method // then immediately discarded. The actual expression will be generated during lowering return scopeOfTheContainingExpression; - case BoundKind.AwaitableValuePlaceholder: - return ((BoundAwaitableValuePlaceholder)expr).ValEscape; - case BoundKind.PointerElementAccess: case BoundKind.PointerIndirectionOperator: // Unsafe code will always be allowed to escape. @@ -3774,12 +3795,12 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF return true; } - bool inUnsafeRegion = this.InUnsafeRegion; + bool inUnsafeRegion = _inUnsafeRegion; switch (expr.Kind) { case BoundKind.ThisReference: - var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter; + var thisParam = ((MethodSymbol)_symbol).ThisParameter; Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything)); return CheckParameterValEscape(node, thisParam, escapeTo, diagnostics); @@ -3807,23 +3828,9 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF return true; case BoundKind.DeconstructValuePlaceholder: - if (((BoundDeconstructValuePlaceholder)expr).ValEscape > escapeTo) - { - Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); - return inUnsafeRegion; - } - return true; - case BoundKind.AwaitableValuePlaceholder: - if (((BoundAwaitableValuePlaceholder)expr).ValEscape > escapeTo) - { - Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); - return inUnsafeRegion; - } - return true; - case BoundKind.InterpolatedStringArgumentPlaceholder: - if (((BoundInterpolatedStringArgumentPlaceholder)expr).ValSafeToEscape > escapeTo) + if (GetPlaceholderScope((BoundValuePlaceholderBase)expr) > escapeTo) { Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); return inUnsafeRegion; @@ -3832,7 +3839,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF case BoundKind.Local: var localSymbol = ((BoundLocal)expr).LocalSymbol; - if (localSymbol.ValEscapeScope > escapeTo) + if (GetLocalScopes(localSymbol).ValEscapeScope > escapeTo) { Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, localSymbol); return inUnsafeRegion; @@ -3953,30 +3960,6 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF isRefEscape: false); } - case BoundKind.ImplicitIndexerReceiverPlaceholder: - if (((BoundImplicitIndexerReceiverPlaceholder)expr).ValEscape > escapeTo) - { - Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); - return inUnsafeRegion; - } - return true; - - case BoundKind.ListPatternReceiverPlaceholder: - if (((BoundListPatternReceiverPlaceholder)expr).ValEscape > escapeTo) - { - Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); - return inUnsafeRegion; - } - return true; - - case BoundKind.SlicePatternReceiverPlaceholder: - if (((BoundSlicePatternReceiverPlaceholder)expr).ValEscape > escapeTo) - { - Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax); - return inUnsafeRegion; - } - return true; - case BoundKind.ImplicitIndexerAccess: var implicitIndexerAccess = (BoundImplicitIndexerAccess)expr; @@ -4461,7 +4444,7 @@ void getParts(BoundInterpolatedString interpolatedString) // SPEC: For a given argument `a` that is passed to parameter `p`: // SPEC: 1. ... // SPEC: 2. If `p` is `scoped` then `a` does not contribute *safe-to-escape* when considering arguments. - if (UseUpdatedEscapeRules && + if (_useUpdatedEscapeRules && call.Method.Parameters[0].EffectiveScope == DeclarationScope.ValueScoped) { continue; @@ -4471,7 +4454,10 @@ void getParts(BoundInterpolatedString interpolatedString) } } } + } + internal partial class Binder + { internal enum AddressKind { // reference may be written to diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.cs index f5b1a6aa5fa1e..6c01969cec385 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.cs @@ -867,7 +867,7 @@ internal BoundStatement WrapWithVariablesAndLocalFunctionsIfAny(CSharpSyntaxNode return statement; } - return new BoundBlock(statement.Syntax, locals, localFunctions, + return new BoundBlock(statement.Syntax, locals, localFunctions, hasUnsafeModifier: false, ImmutableArray.Create(statement)) { WasCompilerGenerated = true }; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs index ffc9012e02949..43d1f98cf1ef0 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs @@ -29,7 +29,7 @@ private BoundExpression BindAwait(AwaitExpressionSyntax node, BindingDiagnosticB private BoundAwaitExpression BindAwait(BoundExpression expression, SyntaxNode node, BindingDiagnosticBag diagnostics) { bool hasErrors = false; - var placeholder = new BoundAwaitableValuePlaceholder(expression.Syntax, GetValEscape(expression, LocalScopeDepth), expression.Type); + var placeholder = new BoundAwaitableValuePlaceholder(expression.Syntax, expression.Type); ReportBadAwaitDiagnostics(node, node.Location, diagnostics, ref hasErrors); var info = BindAwaitInfo(placeholder, node, diagnostics, ref hasErrors, expressionOpt: expression); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index f4e4895e47e8f..063bf10c1083a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -118,12 +118,11 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( BindingDiagnosticBag diagnostics) { Debug.Assert(diagnostics.DiagnosticBag is object); - uint rightEscape = GetValEscape(boundRHS, this.LocalScopeDepth); if ((object?)boundRHS.Type == null || boundRHS.Type.IsErrorType()) { // we could still not infer a type for the RHS - FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); + FailRemainingInferences(checkedVariables, diagnostics); var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); var type = boundRHS.Type ?? voidType; @@ -144,7 +143,6 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( boundRHS.Type, node, boundRHS.Syntax, - rightEscape, diagnostics, checkedVariables, out conversion); @@ -154,7 +152,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( CheckImplicitThisCopyInReadOnlyMember(boundRHS, conversion.Method, diagnostics); } - FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); + FailRemainingInferences(checkedVariables, diagnostics); var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); Debug.Assert(hasErrors || lhsTuple.Type is object); @@ -245,7 +243,6 @@ private bool MakeDeconstructionConversion( TypeSymbol type, SyntaxNode syntax, SyntaxNode rightSyntax, - uint rightValEscape, BindingDiagnosticBag diagnostics, ArrayBuilder variables, out Conversion conversion) @@ -276,7 +273,7 @@ private bool MakeDeconstructionConversion( return false; } - var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, variableSymbol: null, rightValEscape, isDiscardExpression: false, type); + var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, variableSymbol: null, isDiscardExpression: false, type); BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders, out _, variables); @@ -305,7 +302,7 @@ private bool MakeDeconstructionConversion( { var elementSyntax = syntax.Kind() == SyntaxKind.TupleExpression ? ((TupleExpressionSyntax)syntax).Arguments[i] : syntax; - hasErrors |= !MakeDeconstructionConversion(tupleOrDeconstructedTypes[i], elementSyntax, rightSyntax, rightValEscape, diagnostics, + hasErrors |= !MakeDeconstructionConversion(tupleOrDeconstructedTypes[i], elementSyntax, rightSyntax, diagnostics, variable.NestedVariables, out nestedConversion); Debug.Assert(nestedConversion.Kind == ConversionKind.Deconstruction); @@ -391,8 +388,7 @@ private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol t /// Find any deconstruction locals that are still pending inference and fail their inference. /// Set the safe-to-escape scope for all deconstruction locals. /// - private void FailRemainingInferencesAndSetValEscape(ArrayBuilder variables, BindingDiagnosticBag diagnostics, - uint rhsValEscape) + private void FailRemainingInferences(ArrayBuilder variables, BindingDiagnosticBag diagnostics) { int count = variables.Count; for (int i = 0; i < count; i++) @@ -400,21 +396,13 @@ private void FailRemainingInferencesAndSetValEscape(ArrayBuilder symbol, BoundLocal { DeclarationKind: BoundLocalDeclarationKind.WithExplicitType or BoundLocalDeclarationKind.WithInferredType, LocalSymbol: var symbol } => symbol, _ => null, }; - var variable = new OutDeconstructVarPendingInference(receiverSyntax, variableSymbol: variableSymbol, valEscape, isDiscardExpression: variableOpt is BoundDiscardExpression); + var variable = new OutDeconstructVarPendingInference(receiverSyntax, variableSymbol: variableSymbol, isDiscardExpression: variableOpt is BoundDiscardExpression); analyzedArguments.Arguments.Add(variable); analyzedArguments.RefKinds.Add(RefKind.Out); outVars.Add(variable); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 88414d78dbf8e..fbb52389db570 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -804,9 +804,9 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, BindingDia } #nullable enable - internal virtual BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, uint switchGoverningValEscape, BindingDiagnosticBag diagnostics) + internal virtual BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, BindingDiagnosticBag diagnostics) { - return this.NextRequired.BindSwitchExpressionArm(node, switchGoverningType, switchGoverningValEscape, diagnostics); + return this.NextRequired.BindSwitchExpressionArm(node, switchGoverningType, diagnostics); } #nullable disable @@ -3089,7 +3089,7 @@ private void CoerceArguments( Debug.Assert(argument is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); TypeWithAnnotations parameterTypeWithAnnotations = GetCorrespondingParameterTypeWithAnnotations(ref result, parameters, arg); reportUnsafeIfNeeded(methodResult, diagnostics, argument, parameterTypeWithAnnotations); - arguments[arg] = BindInterpolatedStringHandlerInMemberCall(argument, arguments, parameters, ref result, arg, receiver, methodResult.LeastOverriddenMember.RequiresInstanceReceiver(), diagnostics); + arguments[arg] = BindInterpolatedStringHandlerInMemberCall(argument, arguments, parameters, ref result, arg, receiver, diagnostics); } // https://github.com/dotnet/roslyn/issues/37119 : should we create an (Identity) conversion when the kind is Identity but the types differ? else if (!kind.IsIdentity) @@ -4192,19 +4192,6 @@ private BoundExpression BindConstructorInitializerCore( var arguments = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); - if (!hasErrors) - { - hasErrors = !CheckInvocationArgMixing( - nonNullSyntax, - resultMember, - receiver, - resultMember.Parameters, - arguments, - refKinds, - argsToParamsOpt, - this.LocalScopeDepth, - diagnostics); - } if (resultMember.HasSetsRequiredMembers && !constructor.HasSetsRequiredMembers) { @@ -4780,10 +4767,7 @@ private BoundExpression BindInitializerMemberAssignment( diagnostics: diagnostics); // Bind member initializer assignment expression - // We don't verify escape safety of initializers against the instance because the initializers - // get factored in when determining the safe-to-escape of the instance (the initializers contribute - // like constructor arguments). - return BindAssignment(initializer, boundLeft, boundRight, isRef, verifyEscapeSafety: false, diagnostics); + return BindAssignment(initializer, boundLeft, boundRight, isRef, diagnostics); } } @@ -5619,20 +5603,6 @@ protected BoundExpression BindClassCreationExpression( var arguments = analyzedArguments.Arguments.ToImmutable(); var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); - if (!hasError) - { - hasError = !CheckInvocationArgMixing( - node, - method, - null, - method.Parameters, - arguments, - refKinds, - argToParams, - this.LocalScopeDepth, - diagnostics); - } - boundInitializerOpt = makeBoundInitializerOpt(); var creation = new BoundObjectCreationExpression( node, @@ -7875,7 +7845,7 @@ private BoundExpression BindArrayAccess(SyntaxNode node, BoundExpression expr, A Debug.Assert(convertedArguments.Length == 1); var int32 = GetSpecialType(SpecialType.System_Int32, diagnostics, node); - var receiverPlaceholder = new BoundImplicitIndexerReceiverPlaceholder(expr.Syntax, GetValEscape(expr, LocalScopeDepth), isEquivalentToThisReference: expr.IsEquivalentToThisReference, expr.Type) { WasCompilerGenerated = true }; + var receiverPlaceholder = new BoundImplicitIndexerReceiverPlaceholder(expr.Syntax, isEquivalentToThisReference: expr.IsEquivalentToThisReference, expr.Type) { WasCompilerGenerated = true }; var argumentPlaceholders = ImmutableArray.Create(new BoundImplicitIndexerValuePlaceholder(convertedArguments[0].Syntax, int32) { WasCompilerGenerated = true }); return new BoundImplicitIndexerAccess( @@ -8342,20 +8312,6 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( var arguments = analyzedArguments.Arguments.ToImmutable(); - if (!gotError) - { - gotError = !CheckInvocationArgMixing( - syntax, - property, - receiver, - property.Parameters, - arguments, - argumentRefKinds, - argsToParams, - this.LocalScopeDepth, - diagnostics); - } - // Note that we do not bind default arguments here, because at this point we do not know whether // the indexer is being used in a 'get', or 'set', or 'get+set' (compound assignment) context. propertyAccess = new BoundIndexerAccess( @@ -8411,8 +8367,7 @@ private bool TryBindIndexOrRangeImplicitIndexer( } bool argIsIndex = argIsIndexNotRange.Value(); - var receiverValEscape = GetValEscape(receiver, LocalScopeDepth); - var receiverPlaceholder = new BoundImplicitIndexerReceiverPlaceholder(receiver.Syntax, receiverValEscape, isEquivalentToThisReference: receiver.IsEquivalentToThisReference, receiver.Type) { WasCompilerGenerated = true }; + var receiverPlaceholder = new BoundImplicitIndexerReceiverPlaceholder(receiver.Syntax, isEquivalentToThisReference: receiver.IsEquivalentToThisReference, receiver.Type) { WasCompilerGenerated = true }; if (!TryBindIndexOrRangeImplicitIndexerParts(syntax, receiverPlaceholder, argIsIndex: argIsIndex, out var lengthOrCountAccess, out var indexerOrSliceAccess, out var argumentPlaceholders, diagnostics)) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs index 850e7d3320cd2..765c53efae8a2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs @@ -114,18 +114,6 @@ internal static void BindRegularCSharpFieldInitializers( parentBinder = parentBinder.GetFieldInitializerBinder(fieldSymbol); BoundFieldEqualsValue boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics); - - if (!boundInitializer.Value.HasAnyErrors) - { - var field = boundInitializer.Field; - bool isByRef = field.RefKind != RefKind.None; - if (isByRef || field.Type.IsRefLikeType) - { - BoundExpression value = parentBinder.ValidateEscape(boundInitializer.Value, CallingMethodScope, isByRef: isByRef, diagnostics); - boundInitializer = boundInitializer.Update(field, boundInitializer.Locals, value); - } - } - boundInitializers.Add(boundInitializer); break; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs index 752435122aa87..4a896f5c58ffc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs @@ -575,7 +575,7 @@ private BoundBinaryOperator BindUnconvertedBinaryOperatorToInterpolatedStringHan // because we want to track that we're using the type no matter what. var boolType = GetSpecialType(SpecialType.System_Boolean, diagnostics, syntax); var trailingConstructorValidityPlaceholder = - new BoundInterpolatedStringArgumentPlaceholder(syntax, BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter, valSafeToEscape: LocalScopeDepth, boolType) + new BoundInterpolatedStringArgumentPlaceholder(syntax, BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter, boolType) { WasCompilerGenerated = true }; var outConstructorAdditionalArguments = additionalConstructorArguments.Add(trailingConstructorValidityPlaceholder); refKindsBuilder.Add(RefKind.Out); @@ -662,7 +662,6 @@ private BoundBinaryOperator BindUnconvertedBinaryOperatorToInterpolatedStringHan interpolatedStringHandlerType, constructorCall, usesBoolReturn, - LocalScopeDepth, additionalConstructorArguments.NullToEmpty(), positionInfo, implicitBuilderReceiver); @@ -852,7 +851,6 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( ref MemberAnalysisResult memberAnalysisResult, int interpolatedStringArgNum, BoundExpression? receiver, - bool requiresInstanceReceiver, BindingDiagnosticBag diagnostics) { Debug.Assert(unconvertedString is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }); @@ -1006,27 +1004,21 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( } SyntaxNode placeholderSyntax; - uint valSafeToEscapeScope; bool isSuppressed; switch (argumentIndex) { case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: Debug.Assert(receiver != null); - valSafeToEscapeScope = requiresInstanceReceiver - ? receiver.GetRefKind().IsWritableReference() == true ? GetRefEscape(receiver, LocalScopeDepth) : GetValEscape(receiver, LocalScopeDepth) - : Binder.CallingMethodScope; isSuppressed = receiver.IsSuppressed; placeholderSyntax = receiver.Syntax; break; case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: placeholderSyntax = unconvertedString.Syntax; - valSafeToEscapeScope = Binder.CallingMethodScope; isSuppressed = false; break; case >= 0: placeholderSyntax = arguments[argumentIndex].Syntax; - valSafeToEscapeScope = GetValEscape(arguments[argumentIndex], LocalScopeDepth); isSuppressed = arguments[argumentIndex].IsSuppressed; break; default: @@ -1037,7 +1029,6 @@ private BoundExpression BindInterpolatedStringHandlerInMemberCall( (BoundInterpolatedStringArgumentPlaceholder)(new BoundInterpolatedStringArgumentPlaceholder( placeholderSyntax, argumentIndex, - valSafeToEscapeScope, placeholderType, hasErrors: argumentIndex == BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter) { WasCompilerGenerated = true }.WithSuppression(isSuppressed))); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 455be2871a30d..de47c942648ca 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1126,20 +1126,6 @@ private BoundCall BindInvocationExpressionContinued( Debug.Assert(args.IsDefaultOrEmpty || (object)receiver != (object)args[0]); - if (!gotError) - { - CheckInvocationArgMixing( - node, - method, - receiver, - method.Parameters, - args, - argRefKinds, - argsToParams, - this.LocalScopeDepth, - diagnostics); - } - bool isDelegateCall = (object)delegateTypeOpt != null; if (!isDelegateCall) { @@ -2051,20 +2037,6 @@ private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); bool hasErrors = ReportUnsafeIfNotAllowed(node, diagnostics); - if (!hasErrors) - { - hasErrors = !CheckInvocationArgMixing( - node, - funcPtr.Signature, - receiverOpt: null, - funcPtr.Signature.Parameters, - args, - refKinds, - methodResult.Result.ArgsToParamsOpt, - LocalScopeDepth, - diagnostics); - } - return new BoundFunctionPointerInvocation( node, boundExpression, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index ea9ff23d68b10..c2c1cceebf4e4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -4277,32 +4277,37 @@ private BoundExpression BindRefConditionalOperator(ConditionalExpressionSyntax n Debug.Assert(Conversions.HasIdentityConversion(falseType, type)); } - if (!hasErrors) - { - var currentScope = this.LocalScopeDepth; + trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false); + falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false); + return new BoundConditionalOperator(node, isRef: true, condition, trueExpr, falseExpr, constantValueOpt: null, type, wasTargetTyped: false, type, hasErrors); + } + } - // val-escape must agree on both branches. - uint whenTrueEscape = GetValEscape(trueExpr, currentScope); - uint whenFalseEscape = GetValEscape(falseExpr, currentScope); + partial class RefSafetyAnalysis + { + private void ValidateRefConditionalOperator(SyntaxNode node, BoundExpression trueExpr, BoundExpression falseExpr, BindingDiagnosticBag diagnostics) + { + var currentScope = _localScopeDepth; - if (whenTrueEscape != whenFalseEscape) - { - // ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix. - if (whenTrueEscape < whenFalseEscape) - CheckValEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics); - else - CheckValEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics); + // val-escape must agree on both branches. + uint whenTrueEscape = GetValEscape(trueExpr, currentScope); + uint whenFalseEscape = GetValEscape(falseExpr, currentScope); - diagnostics.Add(this.InUnsafeRegion ? ErrorCode.WRN_MismatchedRefEscapeInTernary : ErrorCode.ERR_MismatchedRefEscapeInTernary, node.Location); - hasErrors = !this.InUnsafeRegion; - } - } + if (whenTrueEscape != whenFalseEscape) + { + // ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix. + if (whenTrueEscape < whenFalseEscape) + CheckValEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics); + else + CheckValEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics); - trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false); - falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false); - return new BoundConditionalOperator(node, isRef: true, condition, trueExpr, falseExpr, constantValueOpt: null, type, wasTargetTyped: false, type, hasErrors); + diagnostics.Add(_inUnsafeRegion ? ErrorCode.WRN_MismatchedRefEscapeInTernary : ErrorCode.ERR_MismatchedRefEscapeInTernary, node.Location); + } } + } + partial class Binder + { /// /// Constant folding for conditional (aka ternary) operators. /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 03f630b52dae4..97e56eb413dc8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -35,8 +35,7 @@ private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node, } Debug.Assert(expression.Type is { }); - uint inputValEscape = GetValEscape(expression, LocalScopeDepth); - BoundPattern pattern = BindPattern(node.Pattern, expression.Type, inputValEscape, permitDesignations: true, hasErrors, diagnostics, underIsPattern: true); + BoundPattern pattern = BindPattern(node.Pattern, expression.Type, permitDesignations: true, hasErrors, diagnostics, underIsPattern: true); hasErrors |= pattern.HasErrors; return MakeIsPatternExpression( node, expression, pattern, GetSpecialType(SpecialType.System_Boolean, diagnostics, node), @@ -164,7 +163,6 @@ internal virtual BoundExpression BindSwitchExpressionCore( internal BoundPattern BindPattern( PatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics, @@ -173,17 +171,17 @@ internal BoundPattern BindPattern( return node switch { DiscardPatternSyntax p => BindDiscardPattern(p, inputType, diagnostics), - DeclarationPatternSyntax p => BindDeclarationPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics), + DeclarationPatternSyntax p => BindDeclarationPattern(p, inputType, permitDesignations, hasErrors, diagnostics), ConstantPatternSyntax p => BindConstantPatternWithFallbackToTypePattern(p, inputType, hasErrors, diagnostics), - RecursivePatternSyntax p => BindRecursivePattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics), - VarPatternSyntax p => BindVarPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics), - ParenthesizedPatternSyntax p => BindParenthesizedPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics, underIsPattern), - BinaryPatternSyntax p => BindBinaryPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics), - UnaryPatternSyntax p => BindUnaryPattern(p, inputType, inputValEscape, hasErrors, diagnostics, underIsPattern), + RecursivePatternSyntax p => BindRecursivePattern(p, inputType, permitDesignations, hasErrors, diagnostics), + VarPatternSyntax p => BindVarPattern(p, inputType, permitDesignations, hasErrors, diagnostics), + ParenthesizedPatternSyntax p => BindParenthesizedPattern(p, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern), + BinaryPatternSyntax p => BindBinaryPattern(p, inputType, permitDesignations, hasErrors, diagnostics), + UnaryPatternSyntax p => BindUnaryPattern(p, inputType, hasErrors, diagnostics, underIsPattern), RelationalPatternSyntax p => BindRelationalPattern(p, inputType, hasErrors, diagnostics), TypePatternSyntax p => BindTypePattern(p, inputType, hasErrors, diagnostics), - ListPatternSyntax p => BindListPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics), - SlicePatternSyntax p => BindSlicePattern(p, inputType, inputValEscape, permitDesignations, ref hasErrors, misplaced: true, diagnostics), + ListPatternSyntax p => BindListPattern(p, inputType, permitDesignations, hasErrors, diagnostics), + SlicePatternSyntax p => BindSlicePattern(p, inputType, permitDesignations, ref hasErrors, misplaced: true, diagnostics), _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), }; } @@ -191,20 +189,18 @@ internal BoundPattern BindPattern( private BoundPattern BindParenthesizedPattern( ParenthesizedPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics, bool underIsPattern) { MessageID.IDS_FeatureParenthesizedPattern.CheckFeatureAvailability(diagnostics, node, node.OpenParenToken.GetLocation()); - return BindPattern(node.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics, underIsPattern); + return BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern); } private BoundPattern BindSlicePattern( SlicePatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, ref bool hasErrors, bool misplaced, @@ -224,7 +220,7 @@ private BoundPattern BindSlicePattern( // We don't require the type to be sliceable if there's no subpattern. if (node.Pattern is not null) { - receiverPlaceholder = new BoundSlicePatternReceiverPlaceholder(node, GetValEscape(inputType, inputValEscape), inputType) { WasCompilerGenerated = true }; + receiverPlaceholder = new BoundSlicePatternReceiverPlaceholder(node, inputType) { WasCompilerGenerated = true }; var systemRangeType = GetWellKnownType(WellKnownType.System_Range, diagnostics, node); argumentPlaceholder = new BoundSlicePatternRangePlaceholder(node, systemRangeType) { WasCompilerGenerated = true }; @@ -253,7 +249,7 @@ private BoundPattern BindSlicePattern( sliceType = indexerAccess.Type; } - pattern = BindPattern(node.Pattern, sliceType, GetValEscape(sliceType, inputValEscape), permitDesignations, hasErrors, diagnostics); + pattern = BindPattern(node.Pattern, sliceType, permitDesignations, hasErrors, diagnostics); } return new BoundSlicePattern(node, pattern, indexerAccess, receiverPlaceholder, argumentPlaceholder, inputType: inputType, narrowedType: inputType, hasErrors); @@ -262,7 +258,6 @@ private BoundPattern BindSlicePattern( private ImmutableArray BindListPatternSubpatterns( SeparatedSyntaxList subpatterns, TypeSymbol inputType, - uint inputValEscape, TypeSymbol elementType, bool permitDesignations, ref bool hasErrors, @@ -276,12 +271,12 @@ private ImmutableArray BindListPatternSubpatterns( BoundPattern boundPattern; if (pattern is SlicePatternSyntax slice) { - boundPattern = BindSlicePattern(slice, inputType, GetValEscape(inputType, inputValEscape), permitDesignations, ref hasErrors, misplaced: sawSlice, diagnostics: diagnostics); + boundPattern = BindSlicePattern(slice, inputType, permitDesignations, ref hasErrors, misplaced: sawSlice, diagnostics: diagnostics); sawSlice = true; } else { - boundPattern = BindPattern(pattern, elementType, GetValEscape(elementType, inputValEscape), permitDesignations, hasErrors, diagnostics); + boundPattern = BindPattern(pattern, elementType, permitDesignations, hasErrors, diagnostics); } builder.Add(boundPattern); @@ -293,7 +288,6 @@ private ImmutableArray BindListPatternSubpatterns( private BoundListPattern BindListPattern( ListPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) @@ -323,20 +317,20 @@ private BoundListPattern BindListPattern( } else { - hasErrors |= !BindLengthAndIndexerForListPattern(node, narrowedType, inputValEscape, diagnostics, out indexerAccess, out lengthAccess, out receiverPlaceholder, out argumentPlaceholder); + hasErrors |= !BindLengthAndIndexerForListPattern(node, narrowedType, diagnostics, out indexerAccess, out lengthAccess, out receiverPlaceholder, out argumentPlaceholder); Debug.Assert(indexerAccess!.Type is not null); elementType = indexerAccess.Type; } ImmutableArray subpatterns = BindListPatternSubpatterns( - node.Patterns, inputType: narrowedType, inputValEscape, elementType: elementType, + node.Patterns, inputType: narrowedType, elementType: elementType, permitDesignations, ref hasErrors, out bool sawSlice, diagnostics); BindPatternDesignation( node.Designation, declType: TypeWithAnnotations.Create(narrowedType, NullableAnnotation.NotAnnotated), - inputValEscape, permitDesignations, typeSyntax: null, diagnostics, ref hasErrors, + permitDesignations, typeSyntax: null, diagnostics, ref hasErrors, out Symbol? variableSymbol, out BoundExpression? variableAccess); return new BoundListPattern( @@ -351,19 +345,19 @@ private BoundListPattern BindListPattern( /// private bool IsCountableAndIndexable(SyntaxNode node, TypeSymbol inputType, out PropertySymbol? lengthProperty) { - var success = BindLengthAndIndexerForListPattern(node, inputType, inputValEscape: CallingMethodScope, BindingDiagnosticBag.Discarded, + var success = BindLengthAndIndexerForListPattern(node, inputType, BindingDiagnosticBag.Discarded, indexerAccess: out _, out var lengthAccess, receiverPlaceholder: out _, argumentPlaceholder: out _); lengthProperty = success ? GetPropertySymbol(lengthAccess, out _, out _) : null; return success; } - private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inputType, uint inputValEscape, BindingDiagnosticBag diagnostics, + private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inputType, BindingDiagnosticBag diagnostics, out BoundExpression indexerAccess, out BoundExpression lengthAccess, out BoundListPatternReceiverPlaceholder? receiverPlaceholder, out BoundListPatternIndexPlaceholder argumentPlaceholder) { Debug.Assert(!inputType.IsDynamic()); bool hasErrors = false; - receiverPlaceholder = new BoundListPatternReceiverPlaceholder(node, GetValEscape(inputType, inputValEscape), inputType) { WasCompilerGenerated = true }; + receiverPlaceholder = new BoundListPatternReceiverPlaceholder(node, inputType) { WasCompilerGenerated = true }; if (inputType.IsSZArray()) { hasErrors |= !TryGetSpecialTypeMember(Compilation, SpecialMember.System_Array__Length, node, diagnostics, out PropertySymbol lengthProperty); @@ -833,16 +827,14 @@ internal static ConstantValue ExpressionOfTypeMatchesPatternType( private BoundPattern BindDeclarationPattern( DeclarationPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) { TypeSyntax typeSyntax = node.Type; BoundTypeExpression boundDeclType = BindTypeForPattern(typeSyntax, inputType, diagnostics, ref hasErrors); - var valEscape = GetValEscape(boundDeclType.Type, inputValEscape); BindPatternDesignation( - designation: node.Designation, declType: boundDeclType.TypeWithAnnotations, valEscape, permitDesignations, typeSyntax, diagnostics, + designation: node.Designation, declType: boundDeclType.TypeWithAnnotations, permitDesignations, typeSyntax, diagnostics, hasErrors: ref hasErrors, variableSymbol: out Symbol? variableSymbol, variableAccess: out BoundExpression? variableAccess); return new BoundDeclarationPattern(node, boundDeclType, isVar: false, variableSymbol, variableAccess, inputType: inputType, narrowedType: boundDeclType.Type, hasErrors); } @@ -864,7 +856,6 @@ private BoundTypeExpression BindTypeForPattern( private void BindPatternDesignation( VariableDesignationSyntax? designation, TypeWithAnnotations declType, - uint inputValEscape, bool permitDesignations, TypeSyntax? typeSyntax, BindingDiagnosticBag diagnostics, @@ -888,7 +879,6 @@ private void BindPatternDesignation( CheckFeatureAvailability(designation, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics); localSymbol.SetTypeWithAnnotations(declType); - localSymbol.SetValEscape(GetValEscape(declType.Type, inputValEscape)); // Check for variable declaration errors. hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); @@ -924,16 +914,6 @@ private void BindPatternDesignation( } } - /// - /// Compute the val escape of an expression of the given , which is known to be derived - /// from an expression whose escape scope is . By the language rules, the - /// result is either that same scope (if the type is a ref struct type) or . - /// - private static uint GetValEscape(TypeSymbol type, uint possibleValEscape) - { - return type.IsRefLikeType ? possibleValEscape : Binder.CallingMethodScope; - } - private TypeWithAnnotations BindRecursivePatternType( TypeSyntax? typeSyntax, TypeSymbol inputType, @@ -967,7 +947,6 @@ internal static bool IsZeroElementTupleType(TypeSymbol type) private BoundPattern BindRecursivePattern( RecursivePatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) @@ -984,7 +963,6 @@ private BoundPattern BindRecursivePattern( TypeSyntax? typeSyntax = node.Type; TypeWithAnnotations declTypeWithAnnotations = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression? boundDeclType); TypeSymbol declType = declTypeWithAnnotations.Type; - inputValEscape = GetValEscape(declType, inputValEscape); MethodSymbol? deconstructMethod = null; ImmutableArray deconstructionSubpatterns = default; @@ -998,12 +976,12 @@ private BoundPattern BindRecursivePattern( // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type. We explicitly perform the tests // required to identify it. When that bug is fixed we should be able to remove this if statement. BindValueTupleSubpatterns( - positionalClause, declType, ImmutableArray.Empty, inputValEscape, permitDesignations, ref hasErrors, patternsBuilder, diagnostics); + positionalClause, declType, ImmutableArray.Empty, permitDesignations, ref hasErrors, patternsBuilder, diagnostics); } else if (declType.IsTupleType) { // It is a tuple type. Work according to its elements - BindValueTupleSubpatterns(positionalClause, declType, declType.TupleElementTypesWithAnnotations, inputValEscape, permitDesignations, ref hasErrors, patternsBuilder, diagnostics); + BindValueTupleSubpatterns(positionalClause, declType, declType.TupleElementTypesWithAnnotations, permitDesignations, ref hasErrors, patternsBuilder, diagnostics); } else { @@ -1030,7 +1008,7 @@ private BoundPattern BindRecursivePattern( } deconstructMethod = BindDeconstructSubpatterns( - positionalClause, inputValEscape, permitDesignations, deconstruct, outPlaceholders, patternsBuilder, ref hasErrors, diagnostics); + positionalClause, permitDesignations, deconstruct, outPlaceholders, patternsBuilder, ref hasErrors, diagnostics); } deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree(); @@ -1039,11 +1017,11 @@ private BoundPattern BindRecursivePattern( ImmutableArray properties = default; if (node.PropertyPatternClause != null) { - properties = BindPropertyPatternClause(node.PropertyPatternClause, declType, inputValEscape, permitDesignations, diagnostics, ref hasErrors); + properties = BindPropertyPatternClause(node.PropertyPatternClause, declType, permitDesignations, diagnostics, ref hasErrors); } BindPatternDesignation( - node.Designation, declTypeWithAnnotations, inputValEscape, permitDesignations, typeSyntax, diagnostics, + node.Designation, declTypeWithAnnotations, permitDesignations, typeSyntax, diagnostics, ref hasErrors, out Symbol? variableSymbol, out BoundExpression? variableAccess); bool isExplicitNotNullTest = node.Designation is null && @@ -1060,7 +1038,6 @@ deconstructMethod is null && private MethodSymbol? BindDeconstructSubpatterns( PositionalPatternClauseSyntax node, - uint inputValEscape, bool permitDesignations, BoundExpression deconstruct, ImmutableArray outPlaceholders, @@ -1082,13 +1059,16 @@ deconstructMethod is null && ParameterSymbol? parameter = null; if (!isError) { + int parameterIndex = i + skippedExtensionParameters; + if (parameterIndex < deconstructMethod!.ParameterCount) + { + parameter = deconstructMethod.Parameters[parameterIndex]; + } if (subPattern.NameColon != null) { // Check that the given name is the same as the corresponding parameter of the method. - int parameterIndex = i + skippedExtensionParameters; - if (parameterIndex < deconstructMethod!.ParameterCount) + if (parameter is { }) { - parameter = deconstructMethod.Parameters[parameterIndex]; string name = subPattern.NameColon.Name.Identifier.ValueText; string parameterName = parameter.Name; if (name != parameterName) @@ -1108,7 +1088,7 @@ deconstructMethod is null && var boundSubpattern = new BoundPositionalSubpattern( subPattern, parameter, - BindPattern(subPattern.Pattern, elementType, GetValEscape(elementType, inputValEscape), permitDesignations, isError, diagnostics) + BindPattern(subPattern.Pattern, elementType, permitDesignations, isError, diagnostics) ); patterns.Add(boundSubpattern); } @@ -1122,8 +1102,6 @@ private void BindITupleSubpatterns( bool permitDesignations, BindingDiagnosticBag diagnostics) { - // Since the input has been cast to ITuple, it must be escapable. - const uint valEscape = Binder.CallingMethodScope; var objectType = Compilation.GetSpecialType(SpecialType.System_Object); foreach (var subpatternSyntax in node.Subpatterns) { @@ -1140,7 +1118,7 @@ private void BindITupleSubpatterns( var boundSubpattern = new BoundPositionalSubpattern( subpatternSyntax, null, - BindPattern(subpatternSyntax.Pattern, objectType, valEscape, permitDesignations, hasErrors: false, diagnostics)); + BindPattern(subpatternSyntax.Pattern, objectType, permitDesignations, hasErrors: false, diagnostics)); patterns.Add(boundSubpattern); } } @@ -1151,12 +1129,10 @@ private void BindITupleSubpatterns( bool permitDesignations, BindingDiagnosticBag diagnostics) { - // Since the input has been cast to ITuple, it must be escapable. - const uint valEscape = Binder.CallingMethodScope; var objectType = Compilation.GetSpecialType(SpecialType.System_Object); foreach (var variable in node.Variables) { - BoundPattern pattern = BindVarDesignation(variable, objectType, valEscape, permitDesignations, hasErrors: false, diagnostics); + BoundPattern pattern = BindVarDesignation(variable, objectType, permitDesignations, hasErrors: false, diagnostics); var boundSubpattern = new BoundPositionalSubpattern( variable, null, @@ -1169,7 +1145,6 @@ private void BindValueTupleSubpatterns( PositionalPatternClauseSyntax node, TypeSymbol declType, ImmutableArray elementTypesWithAnnotations, - uint inputValEscape, bool permitDesignations, ref bool hasErrors, ArrayBuilder patterns, @@ -1203,7 +1178,7 @@ private void BindValueTupleSubpatterns( BoundPositionalSubpattern boundSubpattern = new BoundPositionalSubpattern( subpatternSyntax, foundField, - BindPattern(subpatternSyntax.Pattern, elementType, GetValEscape(elementType, inputValEscape), permitDesignations, isError, diagnostics)); + BindPattern(subpatternSyntax.Pattern, elementType, permitDesignations, isError, diagnostics)); patterns.Add(boundSubpattern); } } @@ -1332,7 +1307,6 @@ bool hasBaseInterface(TypeSymbol type, NamedTypeSymbol possibleBaseInterface) private BoundPattern BindVarPattern( VarPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) @@ -1353,13 +1327,12 @@ private BoundPattern BindVarPattern( hasErrors = true; } - return BindVarDesignation(node.Designation, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics); + return BindVarDesignation(node.Designation, inputType, permitDesignations, hasErrors, diagnostics); } private BoundPattern BindVarDesignation( VariableDesignationSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) @@ -1374,7 +1347,7 @@ private BoundPattern BindVarDesignation( { var declType = TypeWithState.ForType(inputType).ToTypeWithAnnotations(Compilation); BindPatternDesignation( - designation: node, declType: declType, inputValEscape: inputValEscape, permitDesignations: permitDesignations, + designation: node, declType: declType, permitDesignations: permitDesignations, typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors, variableSymbol: out Symbol? variableSymbol, variableAccess: out BoundExpression? variableAccess); var boundOperandType = new BoundTypeExpression(syntax: node, aliasOpt: null, typeWithAnnotations: declType); // fake a type expression for the variable's type @@ -1438,7 +1411,7 @@ private BoundPattern BindVarDesignation( var variable = tupleDesignation.Variables[i]; bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length; TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type; - BoundPattern pattern = BindVarDesignation(variable, elementType, GetValEscape(elementType, inputValEscape), permitDesignations, isError, diagnostics); + BoundPattern pattern = BindVarDesignation(variable, elementType, permitDesignations, isError, diagnostics); subPatterns.Add(new BoundPositionalSubpattern(variable, symbol: null, pattern)); } } @@ -1461,7 +1434,7 @@ void addSubpatternsForTuple(ImmutableArray elementTypes) var variable = tupleDesignation.Variables[i]; bool isError = i >= elementTypes.Length; TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].Type; - BoundPattern pattern = BindVarDesignation(variable, elementType, GetValEscape(elementType, inputValEscape), permitDesignations, isError, diagnostics); + BoundPattern pattern = BindVarDesignation(variable, elementType, permitDesignations, isError, diagnostics); subPatterns.Add(new BoundPositionalSubpattern(variable, symbol: null, pattern)); } } @@ -1476,7 +1449,6 @@ void addSubpatternsForTuple(ImmutableArray elementTypes) private ImmutableArray BindPropertyPatternClause( PropertyPatternClauseSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, BindingDiagnosticBag diagnostics, ref bool hasErrors) @@ -1503,7 +1475,7 @@ private ImmutableArray BindPropertyPatternClause( } else { - member = LookupMembersForPropertyPattern(inputType, expr, diagnostics, ref inputValEscape, ref hasErrors); + member = LookupMembersForPropertyPattern(inputType, expr, diagnostics, ref hasErrors); memberType = member.Type; // If we're dealing with the member that makes the type countable, and the type is also indexable, then it will be assumed to always return a non-negative value if (memberType.SpecialType == SpecialType.System_Int32 && @@ -1518,7 +1490,7 @@ private ImmutableArray BindPropertyPatternClause( } } - BoundPattern boundPattern = BindPattern(pattern, memberType, inputValEscape, permitDesignations, hasErrors, diagnostics); + BoundPattern boundPattern = BindPattern(pattern, memberType, permitDesignations, hasErrors, diagnostics); builder.Add(new BoundPropertySubpattern(p, member, isLengthOrCount, boundPattern)); } @@ -1526,7 +1498,7 @@ private ImmutableArray BindPropertyPatternClause( } private BoundPropertySubpatternMember LookupMembersForPropertyPattern( - TypeSymbol inputType, ExpressionSyntax expr, BindingDiagnosticBag diagnostics, ref uint inputValEscape, ref bool hasErrors) + TypeSymbol inputType, ExpressionSyntax expr, BindingDiagnosticBag diagnostics, ref bool hasErrors) { BoundPropertySubpatternMember? receiver = null; Symbol? symbol = null; @@ -1536,7 +1508,7 @@ private BoundPropertySubpatternMember LookupMembersForPropertyPattern( symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics); break; case MemberAccessExpressionSyntax { Name: IdentifierNameSyntax name } memberAccess when memberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression): - receiver = LookupMembersForPropertyPattern(inputType, memberAccess.Expression, diagnostics, ref inputValEscape, ref hasErrors); + receiver = LookupMembersForPropertyPattern(inputType, memberAccess.Expression, diagnostics, ref hasErrors); symbol = BindPropertyPatternMember(receiver.Type.StrippedType(), name, ref hasErrors, diagnostics); break; default: @@ -1552,8 +1524,6 @@ private BoundPropertySubpatternMember LookupMembersForPropertyPattern( _ => CreateErrorType() }; - // Note: since we're recursing on the receiver, the val escape correctly flows from inside out. - inputValEscape = GetValEscape(memberType, inputValEscape); return new BoundPropertySubpatternMember(expr, receiver, symbol, type: memberType, hasErrors); } @@ -1736,7 +1706,6 @@ private BoundPattern BindRelationalPattern( private BoundPattern BindUnaryPattern( UnaryPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool hasErrors, BindingDiagnosticBag diagnostics, bool underIsPattern) @@ -1744,14 +1713,13 @@ private BoundPattern BindUnaryPattern( MessageID.IDS_FeatureNotPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); bool permitDesignations = underIsPattern; // prevent designators under 'not' except under an is-pattern - var subPattern = BindPattern(node.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics, underIsPattern); + var subPattern = BindPattern(node.Pattern, inputType, permitDesignations, hasErrors, diagnostics, underIsPattern); return new BoundNegatedPattern(node, subPattern, inputType: inputType, narrowedType: inputType, hasErrors); } private BoundPattern BindBinaryPattern( BinaryPatternSyntax node, TypeSymbol inputType, - uint inputValEscape, bool permitDesignations, bool hasErrors, BindingDiagnosticBag diagnostics) @@ -1762,8 +1730,8 @@ private BoundPattern BindBinaryPattern( MessageID.IDS_FeatureOrPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); permitDesignations = false; // prevent designators under 'or' - var left = BindPattern(node.Left, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics); - var right = BindPattern(node.Right, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics); + var left = BindPattern(node.Left, inputType, permitDesignations, hasErrors, diagnostics); + var right = BindPattern(node.Right, inputType, permitDesignations, hasErrors, diagnostics); // Compute the common type. This algorithm is quadratic, but disjunctive patterns are unlikely to be huge var narrowedTypeCandidates = ArrayBuilder.GetInstance(2); @@ -1846,9 +1814,8 @@ static void collectCandidates(BoundPattern pat, ArrayBuilder candida { MessageID.IDS_FeatureAndPattern.CheckFeatureAvailability(diagnostics, node, node.OperatorToken.GetLocation()); - var left = BindPattern(node.Left, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics); - var leftOutputValEscape = GetValEscape(left.NarrowedType, inputValEscape); - var right = BindPattern(node.Right, left.NarrowedType, leftOutputValEscape, permitDesignations, hasErrors, diagnostics); + var left = BindPattern(node.Left, inputType, permitDesignations, hasErrors, diagnostics); + var right = BindPattern(node.Right, left.NarrowedType, permitDesignations, hasErrors, diagnostics); return new BoundBinaryPattern(node, disjunction: isDisjunction, left, right, inputType: inputType, narrowedType: right.NarrowedType, hasErrors); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 20ed3c50a8a57..9c39d8a60cb10 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -243,7 +243,6 @@ private BoundStatement BindYieldReturnStatement(YieldStatementSyntax node, Bindi if (!argument.HasAnyErrors) { argument = GenerateConversionForAssignment(elementType, argument, diagnostics); - argument = ValidateEscape(argument, ReturnOnlyScope, isByRef: false, diagnostics: diagnostics); } else { @@ -1110,34 +1109,6 @@ protected BoundLocalDeclaration BindVariableDeclaration( localSymbol.SetTypeWithAnnotations(declTypeOpt); - if (initializerOpt != null) - { - if (UseUpdatedEscapeRules && localSymbol.Scope != DeclarationScope.Unscoped) - { - // If the local has a scoped modifier, then the lifetime is not inferred from - // the initializer. Validate the escape values for the initializer instead. - - Debug.Assert(localSymbol.RefKind == RefKind.None || - localSymbol.RefEscapeScope >= GetRefEscape(initializerOpt, LocalScopeDepth)); - - if (declTypeOpt.Type.IsRefLikeType) - { - initializerOpt = ValidateEscape(initializerOpt, localSymbol.ValEscapeScope, isByRef: false, diagnostics); - } - } - else - { - var currentScope = LocalScopeDepth; - - localSymbol.SetValEscape(GetValEscape(initializerOpt, currentScope)); - - if (localSymbol.RefKind != RefKind.None) - { - localSymbol.SetRefEscape(GetRefEscape(initializerOpt, currentScope)); - } - } - } - ImmutableArray arguments = BindDeclaratorArguments(declarator, localDiagnostics); if (kind == LocalDeclarationKind.FixedVariable || kind == LocalDeclarationKind.UsingVariable) @@ -1473,7 +1444,7 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, verifyEscapeSafety: !discardAssignment, diagnostics); + return BindAssignment(node, op1, op2, isRef, diagnostics); } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) @@ -1516,7 +1487,6 @@ private BoundAssignmentOperator BindAssignment( BoundExpression op1, BoundExpression op2, bool isRef, - bool verifyEscapeSafety, BindingDiagnosticBag diagnostics) { Debug.Assert(op1 != null); @@ -1543,64 +1513,6 @@ private BoundAssignmentOperator BindAssignment( { op2 = BindToNaturalType(op2, diagnostics); } - - if (verifyEscapeSafety) - { - if (isRef) - { - // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#rules-ref-reassignment - // For a ref reassignment in the form `e1 = ref e2` both of the following must be true: - // 1. `e2` must have *ref-safe-to-escape* at least as large as the *ref-safe-to-escape* of `e1` - // 2. `e1` must have the same *safe-to-escape* as `e2` - - var leftEscape = GetRefEscape(op1, LocalScopeDepth); - var rightEscape = GetRefEscape(op2, LocalScopeDepth); - if (leftEscape < rightEscape) - { - var errorCode = (rightEscape, this.InUnsafeRegion) switch - { - (Binder.ReturnOnlyScope, false) => ErrorCode.ERR_RefAssignReturnOnly, - (Binder.ReturnOnlyScope, true) => ErrorCode.WRN_RefAssignReturnOnly, - (_, false) => ErrorCode.ERR_RefAssignNarrower, - (_, true) => ErrorCode.WRN_RefAssignNarrower - }; - - Error(diagnostics, errorCode, node, getName(op1), op2.Syntax); - if (!this.InUnsafeRegion) - { - op2 = ToBadExpression(op2); - } - } - else if (op1.Kind is BoundKind.Local or BoundKind.Parameter) - { - leftEscape = GetValEscape(op1, LocalScopeDepth); - rightEscape = GetValEscape(op2, LocalScopeDepth); - - Debug.Assert(leftEscape == rightEscape || op1.Type.IsRefLikeType); - - // We only check if the safe-to-escape of e2 is wider than the safe-to-escape of e1 here, - // we don't check for equality. The case where the safe-to-escape of e2 is narrower than - // e1 is handled in the if (op1.Type.IsRefLikeType) { ... } block later. - if (leftEscape > rightEscape) - { - Debug.Assert(op1.Kind != BoundKind.Parameter); // If the assert fails, add a corresponding test. - - var errorCode = this.InUnsafeRegion ? ErrorCode.WRN_RefAssignValEscapeWider : ErrorCode.ERR_RefAssignValEscapeWider; - Error(diagnostics, errorCode, node, getName(op1), op2.Syntax); - if (!this.InUnsafeRegion) - { - op2 = ToBadExpression(op2); - } - } - } - } - - if (op1.Type.IsRefLikeType) - { - var leftEscape = GetValEscape(op1, LocalScopeDepth); - op2 = ValidateEscape(op2, leftEscape, isByRef: false, diagnostics); - } - } } else { @@ -1620,6 +1532,81 @@ private BoundAssignmentOperator BindAssignment( } return new BoundAssignmentOperator(node, op1, op2, isRef, type, hasErrors); + } + } + + partial class RefSafetyAnalysis + { + private void ValidateAssignment( + SyntaxNode node, + BoundExpression op1, + BoundExpression op2, + bool isRef, + BindingDiagnosticBag diagnostics) + { + Debug.Assert(op1 != null); + Debug.Assert(op2 != null); + + if (!op1.HasAnyErrors) + { + Debug.Assert(op1.Type is { }); + + bool hasErrors = false; + if (isRef) + { + // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#rules-ref-reassignment + // For a ref reassignment in the form `e1 = ref e2` both of the following must be true: + // 1. `e2` must have *ref-safe-to-escape* at least as large as the *ref-safe-to-escape* of `e1` + // 2. `e1` must have the same *safe-to-escape* as `e2` + + var leftEscape = GetRefEscape(op1, _localScopeDepth); + var rightEscape = GetRefEscape(op2, _localScopeDepth); + if (leftEscape < rightEscape) + { + var errorCode = (rightEscape, _inUnsafeRegion) switch + { + (Binder.ReturnOnlyScope, false) => ErrorCode.ERR_RefAssignReturnOnly, + (Binder.ReturnOnlyScope, true) => ErrorCode.WRN_RefAssignReturnOnly, + (_, false) => ErrorCode.ERR_RefAssignNarrower, + (_, true) => ErrorCode.WRN_RefAssignNarrower + }; + + Error(diagnostics, errorCode, node, getName(op1), op2.Syntax); + if (!_inUnsafeRegion) + { + hasErrors = true; + } + } + else if (op1.Kind is BoundKind.Local or BoundKind.Parameter) + { + leftEscape = GetValEscape(op1, _localScopeDepth); + rightEscape = GetValEscape(op2, _localScopeDepth); + + Debug.Assert(leftEscape == rightEscape || op1.Type.IsRefLikeType); + + // We only check if the safe-to-escape of e2 is wider than the safe-to-escape of e1 here, + // we don't check for equality. The case where the safe-to-escape of e2 is narrower than + // e1 is handled in the if (op1.Type.IsRefLikeType) { ... } block later. + if (leftEscape > rightEscape) + { + Debug.Assert(op1.Kind != BoundKind.Parameter); // If the assert fails, add a corresponding test. + + var errorCode = _inUnsafeRegion ? ErrorCode.WRN_RefAssignValEscapeWider : ErrorCode.ERR_RefAssignValEscapeWider; + Error(diagnostics, errorCode, node, getName(op1), op2.Syntax); + if (!_inUnsafeRegion) + { + hasErrors = true; + } + } + } + } + + if (!hasErrors && op1.Type.IsRefLikeType) + { + var leftEscape = GetValEscape(op1, _localScopeDepth); + ValidateEscape(op2, leftEscape, isByRef: false, diagnostics); + } + } static object getName(BoundExpression expr) { @@ -1640,7 +1627,10 @@ static object getName(BoundExpression expr) return ""; } } + } + partial class Binder + { internal static PropertySymbol GetPropertySymbol(BoundExpression expr, out BoundExpression receiver, out SyntaxNode propertySyntax) { if (expr is null) @@ -1859,13 +1849,6 @@ protected virtual LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken) return Next.LookupLocalFunction(nameToken); } - /// - /// Returns a value that tells how many local scopes are visible, including the current. - /// I.E. outside of any method will be 0 - /// immediately inside a method - 1 - /// - internal virtual uint LocalScopeDepth => Next.LocalScopeDepth; - internal virtual BoundBlock BindEmbeddedBlock(BlockSyntax node, BindingDiagnosticBag diagnostics) { return BindBlock(node, diagnostics); @@ -1908,6 +1891,7 @@ private BoundBlock FinishBindBlockParts(CSharpSyntaxNode node, ImmutableArray Binder.CallingMethodScope; - protected override bool InExecutableBinder => false; protected override SyntaxNode? EnclosingNameofArgument => null; internal override bool IsInsideNameof => false; @@ -203,7 +201,7 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel throw ExceptionUtilities.Unreachable(); } - internal override BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, uint switchGoverningValEscape, BindingDiagnosticBag diagnostics) + internal override BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, BindingDiagnosticBag diagnostics) { // There's supposed to be an overrider of this method (e.g. SwitchExpressionArmBinder) for the arm in the chain. throw ExceptionUtilities.Unreachable(); diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 0e730ce919f50..0bbe92f257e7d 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -182,7 +182,7 @@ internal override BoundStatement BindForEachDeconstruction(BindingDiagnosticBag ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable; // Tracking narrowest safe-to-escape scope by default, the proper val escape will be set when doing full binding of the foreach statement - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, this.LocalScopeDepth, isDiscardExpression: false, inferredType.Type ?? CreateErrorType("var")); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, isDiscardExpression: false, inferredType.Type ?? CreateErrorType("var")); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; @@ -237,7 +237,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno { var expr = _syntax.Expression; ReportBadAwaitDiagnostics(expr, _syntax.AwaitKeyword.GetLocation(), diagnostics, ref hasErrors); - var placeholder = new BoundAwaitableValuePlaceholder(expr, valEscape: this.LocalScopeDepth, builder.MoveNextInfo?.Method.ReturnType ?? CreateErrorType()); + var placeholder = new BoundAwaitableValuePlaceholder(expr, builder.MoveNextInfo?.Method.ReturnType ?? CreateErrorType()); awaitInfo = BindAwaitInfo(placeholder, expr, diagnostics, ref hasErrors); if (!hasErrors && awaitInfo.GetResult?.ReturnType.SpecialType != SpecialType.System_Boolean) @@ -252,7 +252,6 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno bool hasNameConflicts = false; BoundForEachDeconstructStep deconstructStep = null; BoundExpression iterationErrorExpression = null; - uint collectionEscape = GetValEscape(collectionExpr, this.LocalScopeDepth); switch (_syntax.Kind()) { case SyntaxKind.ForEachStatement: @@ -298,7 +297,6 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno SourceLocalSymbol local = this.IterationVariable; local.SetTypeWithAnnotations(declType); - local.SetValEscape(collectionEscape); if (local.Scope == DeclarationScope.ValueScoped && !declType.Type.IsErrorTypeOrRefLikeType()) { @@ -307,11 +305,6 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno if (local.RefKind != RefKind.None) { - // The ref-escape of a ref-returning property is decided - // by the value escape of its receiver, in this case the - // collection - local.SetRefEscape(collectionEscape); - if (CheckRefLocalInAsyncOrIteratorMethod(local.IdentifierToken, diagnostics)) { hasErrors = true; @@ -355,7 +348,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno var variables = node.Variable; if (variables.IsDeconstructionLeft()) { - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, collectionEscape, isDiscardExpression: false, iterationVariableType.Type).MakeCompilerGenerated(); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, variableSymbol: null, isDiscardExpression: false, iterationVariableType.Type).MakeCompilerGenerated(); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( @@ -603,7 +596,7 @@ private bool GetAwaitDisposeAsyncInfo(ref ForEachEnumeratorInfo.Builder builder, var expr = _syntax.Expression; ReportBadAwaitDiagnostics(expr, _syntax.AwaitKeyword.GetLocation(), diagnostics, ref hasErrors); - var placeholder = new BoundAwaitableValuePlaceholder(expr, valEscape: this.LocalScopeDepth, awaitableType); + var placeholder = new BoundAwaitableValuePlaceholder(expr, awaitableType); builder.DisposeAwaitableInfo = BindAwaitInfo(placeholder, expr, diagnostics, ref hasErrors); return hasErrors; } diff --git a/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs b/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs index 2e7edb6dbdad1..d5f45168cd481 100644 --- a/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/InContainerBinder.cs @@ -142,7 +142,5 @@ protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken { return null; } - - internal override uint LocalScopeDepth => Binder.CallingMethodScope; } } diff --git a/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs b/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs index 756174d5d5477..43a57d585688f 100644 --- a/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/InMethodBinder.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; @@ -54,8 +53,6 @@ protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken return null; } - internal override uint LocalScopeDepth => Binder.CurrentMethodScope; - protected override bool InExecutableBinder => true; internal override Symbol ContainingMemberOrLambda diff --git a/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs b/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs index c4810acd4e57e..7b4d9c029e45b 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs @@ -4,8 +4,6 @@ #nullable disable -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; @@ -20,7 +18,6 @@ internal partial class LocalScopeBinder : Binder private ImmutableArray _locals; private ImmutableArray _localFunctions; private ImmutableArray _labels; - private readonly uint _localScopeDepth; internal LocalScopeBinder(Binder next) : this(next, next.Flags) @@ -30,35 +27,6 @@ internal LocalScopeBinder(Binder next) internal LocalScopeBinder(Binder next, BinderFlags flags) : base(next, flags) { - var parentDepth = next.LocalScopeDepth; - - if (parentDepth != Binder.CurrentMethodScope) - { - _localScopeDepth = parentDepth + 1; - } - else - { - //NOTE: TopLevel is special. - //For our purpose parameters and top level locals are on that level. - var parentScope = next; - while (parentScope != null) - { - if (parentScope is InMethodBinder || parentScope is WithLambdaParametersBinder) - { - _localScopeDepth = Binder.CurrentMethodScope; - break; - } - - if (parentScope is LocalScopeBinder) - { - _localScopeDepth = Binder.CurrentMethodScope + 1; - break; - } - - parentScope = parentScope.Next; - Debug.Assert(parentScope != null); - } - } } internal sealed override ImmutableArray Locals @@ -404,8 +372,6 @@ protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken return base.LookupLocalFunction(nameToken); } - internal override uint LocalScopeDepth => _localScopeDepth; - internal override void LookupSymbolsInSingleBinder( LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo useSiteInfo) { diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs new file mode 100644 index 0000000000000..e2cab1878e7f1 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -0,0 +1,897 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed partial class RefSafetyAnalysis : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator + { + internal static void Analyze(CSharpCompilation compilation, Symbol symbol, BoundNode node, BindingDiagnosticBag diagnostics) + { + var visitor = new RefSafetyAnalysis( + compilation, + symbol, + inUnsafeRegion: InUnsafeMethod(symbol), + useUpdatedEscapeRules: symbol.ContainingModule.UseUpdatedEscapeRules, + diagnostics); + try + { + visitor.Visit(node); + } + catch (CancelledByStackGuardException e) + { + e.AddAnError(diagnostics); + } + } + + internal static void Analyze(CSharpCompilation compilation, Symbol symbol, ImmutableArray fieldAndPropertyInitializers, BindingDiagnosticBag diagnostics) + { + var visitor = new RefSafetyAnalysis( + compilation, + symbol, + inUnsafeRegion: InUnsafeMethod(symbol), + useUpdatedEscapeRules: symbol.ContainingModule.UseUpdatedEscapeRules, + diagnostics); + foreach (var initializer in fieldAndPropertyInitializers) + { + try + { + visitor.VisitFieldOrPropertyInitializer(initializer); + } + catch (CancelledByStackGuardException e) + { + e.AddAnError(diagnostics); + } + } + } + + private static bool InUnsafeMethod(Symbol symbol) + { + if (symbol is SourceMemberMethodSymbol { IsUnsafe: true }) + { + return true; + } + + var type = symbol.ContainingType; + while (type is { }) + { + var def = type.OriginalDefinition; + if (def is SourceMemberContainerTypeSymbol { IsUnsafe: true }) + { + return true; + } + type = def.ContainingType; + } + + return false; + } + + private readonly CSharpCompilation _compilation; + private readonly Symbol _symbol; + private readonly bool _useUpdatedEscapeRules; + private readonly BindingDiagnosticBag _diagnostics; + private bool _inUnsafeRegion; + private uint _localScopeDepth; + private Dictionary? _localEscapeScopes; + private Dictionary? _placeholderScopes; + private uint _patternInputValEscape; + + private RefSafetyAnalysis( + CSharpCompilation compilation, + Symbol symbol, + bool inUnsafeRegion, + bool useUpdatedEscapeRules, + BindingDiagnosticBag diagnostics, + Dictionary? localEscapeScopes = null) + { + _compilation = compilation; + _symbol = symbol; + _useUpdatedEscapeRules = useUpdatedEscapeRules; + _diagnostics = diagnostics; + _inUnsafeRegion = inUnsafeRegion; + // _localScopeDepth is incremented at each block in the method, including the + // outermost. To ensure that locals in the outermost block are considered at + // the same depth as parameters, _localScopeDepth is initialized to one less. + _localScopeDepth = Binder.CurrentMethodScope - 1; + _localEscapeScopes = localEscapeScopes; + } + + private ref struct LocalScope + { + private readonly RefSafetyAnalysis _analysis; + private readonly ImmutableArray _locals; + + public LocalScope(RefSafetyAnalysis analysis, ImmutableArray locals) + { + _analysis = analysis; + _locals = locals; + _analysis._localScopeDepth++; + foreach (var local in locals) + { + _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: Binder.CallingMethodScope); + } + } + + public void Dispose() + { + foreach (var local in _locals) + { + _analysis.RemoveLocalScopes(local); + } + _analysis._localScopeDepth--; + } + } + + private ref struct UnsafeRegion + { + private readonly RefSafetyAnalysis _analysis; + private readonly bool _previousRegion; + + public UnsafeRegion(RefSafetyAnalysis analysis, bool inUnsafeRegion) + { + _analysis = analysis; + _previousRegion = analysis._inUnsafeRegion; + _analysis._inUnsafeRegion = inUnsafeRegion; + } + + public void Dispose() + { + _analysis._inUnsafeRegion = _previousRegion; + } + } + + private ref struct PatternInput + { + private readonly RefSafetyAnalysis _analysis; + private readonly uint _previousInputValEscape; + + public PatternInput(RefSafetyAnalysis analysis, uint patternInputValEscape) + { + _analysis = analysis; + _previousInputValEscape = analysis._patternInputValEscape; + _analysis._patternInputValEscape = patternInputValEscape; + } + + public void Dispose() + { + _analysis._patternInputValEscape = _previousInputValEscape; + } + } + + private ref struct PlaceholderRegion + { + private readonly RefSafetyAnalysis _analysis; + private readonly ArrayBuilder<(BoundValuePlaceholderBase, uint)> _placeholders; + + public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders) + { + _analysis = analysis; + _placeholders = placeholders; + foreach (var (placeholder, valEscapeScope) in placeholders) + { + _analysis.AddPlaceholderScope(placeholder, valEscapeScope); + } + } + + public void Dispose() + { + foreach (var (placeholder, _) in _placeholders) + { + _analysis.RemovePlaceholderScope(placeholder); + } + _placeholders.Free(); + } + } + + private (uint RefEscapeScope, uint ValEscapeScope) GetLocalScopes(LocalSymbol local) + { + Debug.Assert(_localEscapeScopes is { }); + return _localEscapeScopes[local]; + } + + private void SetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope) + { + Debug.Assert(_localEscapeScopes is { }); + _localEscapeScopes[local] = (refEscapeScope, valEscapeScope); + } + + private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, uint valEscapeScope) + { + _placeholderScopes ??= new Dictionary(); + _placeholderScopes.Add(placeholder, valEscapeScope); + } + +#pragma warning disable IDE0060 + private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder) + { + Debug.Assert(_placeholderScopes is { }); + // https://github.com/dotnet/roslyn/issues/65961: Currently, analysis may require subsequent calls + // to GetRefEscape(), etc. for the same expression so we cannot remove placeholders eagerly. + //_placeholderScopes.Remove(placeholder); + } +#pragma warning restore IDE0060 + + private uint GetPlaceholderScope(BoundValuePlaceholderBase placeholder) + { + Debug.Assert(_placeholderScopes is { }); + return _placeholderScopes[placeholder]; + } + + public override BoundNode? VisitBlock(BoundBlock node) + { + using var _1 = new UnsafeRegion(this, _inUnsafeRegion || node.HasUnsafeModifier); + using var _2 = new LocalScope(this, node.Locals); + return base.VisitBlock(node); + } + + public override BoundNode? Visit(BoundNode? node) + { +#if DEBUG + if (node is BoundValuePlaceholderBase placeholder + // CheckValEscapeOfObjectInitializer() does not use BoundObjectOrCollectionValuePlaceholder. + // CheckInterpolatedStringHandlerConversionEscape() does not use BoundInterpolatedStringHandlerPlaceholder. + && node is not (BoundObjectOrCollectionValuePlaceholder or BoundInterpolatedStringHandlerPlaceholder)) + { + Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true); + } +#endif + return base.Visit(node); + } + + private void VisitFieldOrPropertyInitializer(BoundInitializer initializer) + { + var fieldEqualsValue = (BoundFieldEqualsValue)initializer; + var field = fieldEqualsValue.Field; + var value = fieldEqualsValue.Value; + + using var _ = new LocalScope(this, fieldEqualsValue.Locals); + + base.Visit(value); + + ValidateEscape(value, Binder.CallingMethodScope, isByRef: field.RefKind != RefKind.None, _diagnostics); + } + + public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node) + { + var localFunction = node.Symbol; + // https://github.com/dotnet/roslyn/issues/65353: We should not reuse _localEscapeScopes + // across nested local functions or lambdas because _localScopeDepth is reset when entering + // the function or lambda so the scopes across the methods are unrelated. + var analysis = new RefSafetyAnalysis(_compilation, localFunction, _inUnsafeRegion || localFunction.IsUnsafe, _useUpdatedEscapeRules, _diagnostics, _localEscapeScopes); + analysis.Visit(node.BlockBody); + analysis.Visit(node.ExpressionBody); + return null; + } + + public override BoundNode? VisitLambda(BoundLambda node) + { + var lambda = node.Symbol; + // https://github.com/dotnet/roslyn/issues/65353: We should not reuse _localEscapeScopes + // across nested local functions or lambdas because _localScopeDepth is reset when entering + // the function or lambda so the scopes across the methods are unrelated. + var analysis = new RefSafetyAnalysis(_compilation, lambda, _inUnsafeRegion, _useUpdatedEscapeRules, _diagnostics, _localEscapeScopes); + analysis.Visit(node.Body); + return null; + } + + public override BoundNode? VisitConstructorMethodBody(BoundConstructorMethodBody node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitConstructorMethodBody(node); + } + + public override BoundNode? VisitForStatement(BoundForStatement node) + { + using var outerLocals = new LocalScope(this, node.OuterLocals); + using var innerLocals = new LocalScope(this, node.InnerLocals); + return base.VisitForStatement(node); + } + + public override BoundNode? VisitUsingStatement(BoundUsingStatement node) + { + using var _ = new LocalScope(this, node.Locals); + + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + if (node.AwaitOpt is { } awaitableInfo) + { + uint valEscapeScope = node.ExpressionOpt is { } expr + ? GetValEscape(expr, _localScopeDepth) + : _localScopeDepth; + GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, valEscapeScope); + } + + using var region = new PlaceholderRegion(this, placeholders); + return base.VisitUsingStatement(node); + } + + public override BoundNode? VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node) + { + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + if (node.AwaitOpt is { } awaitableInfo) + { + GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, _localScopeDepth); + } + + using var _ = new PlaceholderRegion(this, placeholders); + return base.VisitUsingLocalDeclarations(node); + } + + public override BoundNode? VisitFixedStatement(BoundFixedStatement node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitFixedStatement(node); + } + + public override BoundNode? VisitDoStatement(BoundDoStatement node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitDoStatement(node); + } + + public override BoundNode? VisitWhileStatement(BoundWhileStatement node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitWhileStatement(node); + } + + public override BoundNode? VisitSwitchStatement(BoundSwitchStatement node) + { + using var _1 = new LocalScope(this, node.InnerLocals); + using var _2 = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth)); + base.VisitSwitchStatement(node); + return null; + } + + public override BoundNode? VisitConvertedSwitchExpression(BoundConvertedSwitchExpression node) + { + using var _ = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth)); + base.VisitConvertedSwitchExpression(node); + return null; + } + + public override BoundNode? VisitSwitchSection(BoundSwitchSection node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitSwitchSection(node); + } + + public override BoundNode? VisitSwitchExpressionArm(BoundSwitchExpressionArm node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitSwitchExpressionArm(node); + } + + public override BoundNode? VisitCatchBlock(BoundCatchBlock node) + { + using var _ = new LocalScope(this, node.Locals); + return base.VisitCatchBlock(node); + } + + public override BoundNode? VisitLocal(BoundLocal node) + { + // _localEscapeScopes may be null for locals in top-level statements. + Debug.Assert(_localEscapeScopes?.ContainsKey(node.LocalSymbol) == true || + (node.LocalSymbol.ContainingSymbol is SynthesizedSimpleProgramEntryPointSymbol entryPoint && _symbol != entryPoint)); + + return base.VisitLocal(node); + } + + private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope) + { + // From https://github.com/dotnet/csharplang/blob/main/csharp-11.0/proposals/low-level-struct-improvements.md: + // + // | Parameter or Local | ref-safe-to-escape | safe-to-escape | + // |------------------------|--------------------|----------------| + // | Span s | current method | calling method | + // | scoped Span s | current method | current method | + // | ref Span s | calling method | calling method | + // | scoped ref Span s | current method | calling method | + + var scopedModifier = _useUpdatedEscapeRules ? local.Scope : DeclarationScope.Unscoped; + if (scopedModifier != DeclarationScope.Unscoped) + { + refEscapeScope = scopedModifier == DeclarationScope.RefScoped ? + _localScopeDepth : + Binder.CurrentMethodScope; + valEscapeScope = scopedModifier == DeclarationScope.ValueScoped ? + _localScopeDepth : + Binder.CallingMethodScope; + } + + _localEscapeScopes ??= new Dictionary(); + _localEscapeScopes.Add(local, (refEscapeScope, valEscapeScope)); + } + +#pragma warning disable IDE0060 + private void RemoveLocalScopes(LocalSymbol local) + { + Debug.Assert(_localEscapeScopes is { }); + // https://github.com/dotnet/roslyn/issues/65961: Currently, analysis may require subsequent calls + // to GetRefEscape(), etc. for the same expression so we cannot remove locals eagerly. + //_localEscapeScopes.Remove(local); + } +#pragma warning restore IDE0060 + + public override BoundNode? VisitLocalDeclaration(BoundLocalDeclaration node) + { + base.VisitLocalDeclaration(node); + + if (node.InitializerOpt is { } initializer) + { + var localSymbol = (SourceLocalSymbol)node.LocalSymbol; + (uint refEscapeScope, uint valEscapeScope) = GetLocalScopes(localSymbol); + + if (_useUpdatedEscapeRules && localSymbol.Scope != DeclarationScope.Unscoped) + { + // If the local has a scoped modifier, then the lifetime is not inferred from + // the initializer. Validate the escape values for the initializer instead. + + Debug.Assert(localSymbol.RefKind == RefKind.None || + refEscapeScope >= GetRefEscape(initializer, _localScopeDepth)); + + if (node.DeclaredTypeOpt?.Type.IsRefLikeType == true) + { + ValidateEscape(initializer, valEscapeScope, isByRef: false, _diagnostics); + } + } + else + { + // default to the current scope in case we need to handle self-referential error cases. + SetLocalScopes(localSymbol, _localScopeDepth, _localScopeDepth); + + valEscapeScope = GetValEscape(initializer, _localScopeDepth); + if (localSymbol.RefKind != RefKind.None) + { + refEscapeScope = GetRefEscape(initializer, _localScopeDepth); + } + + SetLocalScopes(localSymbol, refEscapeScope, valEscapeScope); + } + } + + return null; + } + + public override BoundNode? VisitReturnStatement(BoundReturnStatement node) + { + base.VisitReturnStatement(node); + if (node.ExpressionOpt is { Type: { } } expr) + { + ValidateEscape(expr, Binder.ReturnOnlyScope, node.RefKind != RefKind.None, _diagnostics); + } + return null; + } + + public override BoundNode? VisitYieldReturnStatement(BoundYieldReturnStatement node) + { + base.VisitYieldReturnStatement(node); + if (node.Expression is { Type: { } } expr) + { + ValidateEscape(expr, Binder.ReturnOnlyScope, isByRef: false, _diagnostics); + } + return null; + } + + public override BoundNode? VisitAssignmentOperator(BoundAssignmentOperator node) + { + base.VisitAssignmentOperator(node); + if (node.Left.Kind != BoundKind.DiscardExpression) + { + ValidateAssignment(node.Syntax, node.Left, node.Right, node.IsRef, _diagnostics); + } + return null; + } + + public override BoundNode? VisitIsPatternExpression(BoundIsPatternExpression node) + { + using var _ = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth)); + return base.VisitIsPatternExpression(node); + } + + public override BoundNode? VisitDeclarationPattern(BoundDeclarationPattern node) + { + SetPatternLocalScopes(node); + + using var _ = new PatternInput(this, getDeclarationValEscape(node.DeclaredType, _patternInputValEscape)); + return base.VisitDeclarationPattern(node); + + static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint valEscape) + { + return typeExpression.Type.IsRefLikeType ? valEscape : Binder.CallingMethodScope; + } + } + + public override BoundNode? VisitListPattern(BoundListPattern node) + { + SetPatternLocalScopes(node); + return base.VisitListPattern(node); + } + + public override BoundNode? VisitRecursivePattern(BoundRecursivePattern node) + { + SetPatternLocalScopes(node); + return base.VisitRecursivePattern(node); + } + + public override BoundNode? VisitPositionalSubpattern(BoundPositionalSubpattern node) + { + using var _ = new PatternInput(this, getPositionalValEscape(node.Symbol, _patternInputValEscape)); + return base.VisitPositionalSubpattern(node); + + static uint getPositionalValEscape(Symbol? symbol, uint valEscape) + { + return symbol is null + ? valEscape + : symbol.GetTypeOrReturnType().IsRefLikeType() ? valEscape : Binder.CallingMethodScope; + } + } + + public override BoundNode? VisitPropertySubpattern(BoundPropertySubpattern node) + { + using var _ = new PatternInput(this, getMemberValEscape(node.Member, _patternInputValEscape)); + return base.VisitPropertySubpattern(node); + + static uint getMemberValEscape(BoundPropertySubpatternMember? member, uint valEscape) + { + if (member is null) return valEscape; + valEscape = getMemberValEscape(member.Receiver, valEscape); + return member.Type.IsRefLikeType ? valEscape : Binder.CallingMethodScope; + } + } + + private void SetPatternLocalScopes(BoundObjectPattern pattern) + { + if (pattern.Variable is LocalSymbol local) + { + SetLocalScopes(local, _localScopeDepth, _patternInputValEscape); + } + } + + public override BoundNode? VisitConditionalOperator(BoundConditionalOperator node) + { + base.VisitConditionalOperator(node); + if (node.IsRef) + { + ValidateRefConditionalOperator(node.Syntax, node.Consequence, node.Alternative, _diagnostics); + } + return null; + } + + private PlaceholderRegion GetArgumentPlaceholders(BoundExpression? receiverOpt, ImmutableArray arguments) + { + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + foreach (var arg in arguments) + { + if (arg is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion) + { + var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData(); + GetInterpolatedStringPlaceholders(placeholders, interpolationData, receiverOpt, arguments); + } + } + return new PlaceholderRegion(this, placeholders); + } + + public override BoundNode? VisitCall(BoundCall node) + { + using var _ = GetArgumentPlaceholders(node.ReceiverOpt, node.Arguments); + base.VisitCall(node); + + if (!node.HasErrors) + { + var method = node.Method; + CheckInvocationArgMixing( + node.Syntax, + method, + node.ReceiverOpt, + method.Parameters, + node.Arguments, + node.ArgumentRefKindsOpt, + node.ArgsToParamsOpt, + _localScopeDepth, + _diagnostics); + } + + return null; + } + + private void GetInterpolatedStringPlaceholders( + ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, + in InterpolatedStringHandlerData interpolationData, + BoundExpression? receiver, + ImmutableArray arguments) + { + placeholders.Add((interpolationData.ReceiverPlaceholder, _localScopeDepth)); + + foreach (var placeholder in interpolationData.ArgumentPlaceholders) + { + uint valEscapeScope; + int argIndex = placeholder.ArgumentIndex; + switch (argIndex) + { + case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter: + Debug.Assert(receiver != null); + valEscapeScope = receiver.GetRefKind().IsWritableReference() ? GetRefEscape(receiver, _localScopeDepth) : GetValEscape(receiver, _localScopeDepth); + break; + case BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter: + case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter: + continue; + case >= 0: + valEscapeScope = GetValEscape(arguments[argIndex], _localScopeDepth); + break; + default: + throw ExceptionUtilities.UnexpectedValue(placeholder.ArgumentIndex); + } + placeholders.Add((placeholder, valEscapeScope)); + } + } + + public override BoundNode? VisitObjectCreationExpression(BoundObjectCreationExpression node) + { + using var _ = GetArgumentPlaceholders(receiverOpt: null, node.Arguments); + base.VisitObjectCreationExpression(node); + + if (!node.HasErrors) + { + var constructor = node.Constructor; + CheckInvocationArgMixing( + node.Syntax, + constructor, + receiverOpt: null, + constructor.Parameters, + node.Arguments, + node.ArgumentRefKindsOpt, + node.ArgsToParamsOpt, + _localScopeDepth, + _diagnostics); + } + + return null; + } + + public override BoundNode? VisitIndexerAccess(BoundIndexerAccess node) + { + using var _ = GetArgumentPlaceholders(node.ReceiverOpt, node.Arguments); + base.VisitIndexerAccess(node); + + if (!node.HasErrors) + { + var indexer = node.Indexer; + CheckInvocationArgMixing( + node.Syntax, + indexer, + node.ReceiverOpt, + indexer.Parameters, + node.Arguments, + node.ArgumentRefKindsOpt, + node.ArgsToParamsOpt, + _localScopeDepth, + _diagnostics); + } + + return null; + } + + public override BoundNode? VisitFunctionPointerInvocation(BoundFunctionPointerInvocation node) + { + using var _ = GetArgumentPlaceholders(receiverOpt: null, node.Arguments); + base.VisitFunctionPointerInvocation(node); + + if (!node.HasErrors) + { + var method = node.FunctionPointer.Signature; + CheckInvocationArgMixing( + node.Syntax, + method, + receiverOpt: null, + method.Parameters, + node.Arguments, + node.ArgumentRefKindsOpt, + argsToParamsOpt: default, + _localScopeDepth, + _diagnostics); + } + + return null; + } + + public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node) + { + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + GetAwaitableInstancePlaceholders(placeholders, node.AwaitableInfo, GetValEscape(node.Expression, _localScopeDepth)); + using var _ = new PlaceholderRegion(this, placeholders); + base.VisitAwaitExpression(node); + return null; + } + + private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, BoundAwaitableInfo awaitableInfo, uint valEscapeScope) + { + if (awaitableInfo.AwaitableInstancePlaceholder is { } placeholder) + { + placeholders.Add((placeholder, valEscapeScope)); + } + } + + // Based on NullableWalker.VisitDeconstructionAssignmentOperator(). + public override BoundNode? VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node) + { + base.VisitDeconstructionAssignmentOperator(node); + + var left = node.Left; + var right = node.Right; + var variables = GetDeconstructionAssignmentVariables(left); + VisitDeconstructionArguments(variables, right.Syntax, right.Conversion, right.Operand); + variables.FreeAll(v => v.NestedVariables); + return null; + } + + private void VisitDeconstructionArguments(ArrayBuilder variables, SyntaxNode syntax, Conversion conversion, BoundExpression right) + { + Debug.Assert(conversion.Kind == ConversionKind.Deconstruction); + + // We only need to visit the right side when deconstruction uses a Deconstruct() method call + // (when !DeconstructionInfo.IsDefault), not when the right side is a tuple, because ref structs + // cannot be used as tuple type arguments. + if (conversion.DeconstructionInfo.IsDefault) + { + return; + } + + var invocation = conversion.DeconstructionInfo.Invocation as BoundCall; + if (invocation is null) + { + return; + } + + var deconstructMethod = invocation.Method; + if (deconstructMethod is null) + { + return; + } + + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + placeholders.Add((conversion.DeconstructionInfo.InputPlaceholder, GetValEscape(right, _localScopeDepth))); + + var parameters = deconstructMethod.Parameters; + int n = variables.Count; + int offset = invocation.InvokedAsExtensionMethod ? 1 : 0; + Debug.Assert(parameters.Length - offset == n); + + for (int i = 0; i < n; i++) + { + var variable = variables[i]; + var nestedVariables = variable.NestedVariables; + var arg = (BoundDeconstructValuePlaceholder)invocation.Arguments[i + offset]; + uint valEscape = nestedVariables is null + ? GetValEscape(variable.Expression, _localScopeDepth) + : _localScopeDepth; + placeholders.Add((arg, valEscape)); + } + + using var _ = new PlaceholderRegion(this, placeholders); + + CheckInvocationArgMixing( + syntax, + deconstructMethod, + invocation.ReceiverOpt, + parameters, + invocation.Arguments, + invocation.ArgumentRefKindsOpt, + invocation.ArgsToParamsOpt, + _localScopeDepth, + _diagnostics); + + for (int i = 0; i < n; i++) + { + var variable = variables[i]; + var nestedVariables = variable.NestedVariables; + if (nestedVariables != null) + { + var (placeholder, placeholderConversion) = conversion.DeconstructConversionInfo[i]; + var underlyingConversion = BoundNode.GetConversion(placeholderConversion, placeholder); + VisitDeconstructionArguments(nestedVariables, syntax, underlyingConversion, right: invocation.Arguments[i + offset]); + } + } + } + + private readonly struct DeconstructionVariable + { + internal readonly BoundExpression Expression; + internal readonly uint ValEscape; + internal readonly ArrayBuilder? NestedVariables; + + internal DeconstructionVariable(BoundExpression expression, uint valEscape, ArrayBuilder? nestedVariables) + { + Expression = expression; + ValEscape = valEscape; + NestedVariables = nestedVariables; + } + } + + private ArrayBuilder GetDeconstructionAssignmentVariables(BoundTupleExpression tuple) + { + var arguments = tuple.Arguments; + var builder = ArrayBuilder.GetInstance(arguments.Length); + foreach (var arg in arguments) + { + builder.Add(getDeconstructionAssignmentVariable(arg)); + } + return builder; + + DeconstructionVariable getDeconstructionAssignmentVariable(BoundExpression expr) + { + return expr is BoundTupleExpression tuple + ? new DeconstructionVariable(expr, valEscape: uint.MaxValue, GetDeconstructionAssignmentVariables(tuple)) + : new DeconstructionVariable(expr, GetValEscape(expr, _localScopeDepth), null); + } + } + + private static ImmutableArray GetDeconstructionRightParts(BoundExpression expr) + { + switch (expr) + { + case BoundTupleExpression tuple: + return tuple.Arguments; + case BoundConversion conv: + switch (conv.ConversionKind) + { + case ConversionKind.Identity: + case ConversionKind.ImplicitTupleLiteral: + return GetDeconstructionRightParts(conv.Operand); + } + break; + } + + throw ExceptionUtilities.Unreachable(); + } + + public override BoundNode? VisitForEachStatement(BoundForEachStatement node) + { + uint collectionEscape = GetValEscape(node.Expression, _localScopeDepth); + using var _ = new LocalScope(this, ImmutableArray.Empty); + + foreach (var local in node.IterationVariables) + { + AddLocalScopes(local, refEscapeScope: local.RefKind == RefKind.None ? _localScopeDepth : collectionEscape, valEscapeScope: collectionEscape); + } + + var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance(); + if (node.DeconstructionOpt?.TargetPlaceholder is { } targetPlaceholder) + { + placeholders.Add((targetPlaceholder, collectionEscape)); + } + if (node.AwaitOpt is { } awaitableInfo) + { + GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, collectionEscape); + } + + using var region = new PlaceholderRegion(this, placeholders); + base.VisitForEachStatement(node); + + foreach (var local in node.IterationVariables) + { + RemoveLocalScopes(local); + } + + return null; + } + + private static void Error(BindingDiagnosticBag diagnostics, ErrorCode code, SyntaxNodeOrToken syntax, params object[] args) + { + var location = syntax.GetLocation(); + RoslynDebug.Assert(location is object); + Error(diagnostics, code, location, args); + } + + private static void Error(BindingDiagnosticBag diagnostics, ErrorCode code, Location location, params object[] args) + { + diagnostics.Add(new CSDiagnostic(new CSDiagnosticInfo(code, args), location)); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs index 015e20d3a49cc..e2b4a95824146 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs @@ -46,8 +46,6 @@ protected BoundExpression SwitchGoverningExpression protected TypeSymbol SwitchGoverningType => SwitchGoverningExpression.Type; - protected uint SwitchGoverningValEscape => GetValEscape(SwitchGoverningExpression, LocalScopeDepth); - protected BindingDiagnosticBag SwitchGoverningDiagnostics { get @@ -222,7 +220,7 @@ private void BuildSwitchLabels(SyntaxList labelsSyntax, Binde // bind the pattern, to cause its pattern variables to be inferred if necessary var matchLabel = (CasePatternSwitchLabelSyntax)labelSyntax; _ = sectionBinder.BindPattern( - matchLabel.Pattern, SwitchGoverningType, SwitchGoverningValEscape, permitDesignations: true, labelSyntax.HasErrors, tempDiagnosticBag); + matchLabel.Pattern, SwitchGoverningType, permitDesignations: true, labelSyntax.HasErrors, tempDiagnosticBag); break; default: diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs index a6c0217c40cf0..a70f367e3446b 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder_Patterns.cs @@ -270,7 +270,7 @@ private BoundSwitchLabel BindSwitchSectionLabel( MessageID.IDS_FeaturePatternMatching.CheckFeatureAvailability(diagnostics, node, node.Keyword.GetLocation()); BoundPattern pattern = sectionBinder.BindPattern( - matchLabelSyntax.Pattern, SwitchGoverningType, SwitchGoverningValEscape, permitDesignations: true, node.HasErrors, diagnostics); + matchLabelSyntax.Pattern, SwitchGoverningType, permitDesignations: true, node.HasErrors, diagnostics); if (matchLabelSyntax.Pattern is ConstantPatternSyntax p) reportIfConstantNamedUnderscore(pattern, p.Expression); diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionArmBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionArmBinder.cs index 4e7400bc67a43..62406a5a774bf 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionArmBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionArmBinder.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// Binder for one of the arms of a switch expression. For example, in the one-armed switch expression /// "e switch { p when c => v }", this could be the binder for the arm "p when c => v". /// - internal class SwitchExpressionArmBinder : Binder + internal sealed class SwitchExpressionArmBinder : Binder { private readonly SwitchExpressionArmSyntax _arm; private readonly ExpressionVariableBinder _armScopeBinder; @@ -29,17 +29,17 @@ public SwitchExpressionArmBinder(SwitchExpressionArmSyntax arm, ExpressionVariab internal BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, BindingDiagnosticBag diagnostics) { Debug.Assert(node == _arm); - (TypeSymbol inputType, uint valEscape) = _switchExpressionBinder.GetInputTypeAndValEscape(); - return BindSwitchExpressionArm(node, inputType, valEscape, diagnostics); + TypeSymbol inputType = _switchExpressionBinder.GetInputType(); + return BindSwitchExpressionArm(node, inputType, diagnostics); } - internal override BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, uint switchGoverningValEscape, BindingDiagnosticBag diagnostics) + internal override BoundSwitchExpressionArm BindSwitchExpressionArm(SwitchExpressionArmSyntax node, TypeSymbol switchGoverningType, BindingDiagnosticBag diagnostics) { Debug.Assert(node == _arm); Binder armBinder = this.GetRequiredBinder(node); bool hasErrors = switchGoverningType.IsErrorType(); ImmutableArray locals = _armScopeBinder.Locals; - BoundPattern pattern = armBinder.BindPattern(node.Pattern, switchGoverningType, switchGoverningValEscape, permitDesignations: true, hasErrors, diagnostics); + BoundPattern pattern = armBinder.BindPattern(node.Pattern, switchGoverningType, permitDesignations: true, hasErrors, diagnostics); BoundExpression? whenClause = node.WhenClause != null ? armBinder.BindBooleanExpression(node.WhenClause.Condition, diagnostics) : null; diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs index 54479b444ccf7..463109a589e10 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs @@ -170,23 +170,23 @@ ImmutableArray nonNullSuccessors(BoundDecisionDagNode n) private ImmutableArray BindSwitchExpressionArms(SwitchExpressionSyntax node, Binder originalBinder, BoundExpression inputExpression, BindingDiagnosticBag diagnostics) { var builder = ArrayBuilder.GetInstance(); - (TypeSymbol inputType, uint valEscape) = GetInputTypeAndValEscape(inputExpression); + TypeSymbol inputType = GetInputType(inputExpression); foreach (var arm in node.Arms) { var armBinder = originalBinder.GetRequiredBinder(arm); Debug.Assert(inputExpression.Type is not null); - var boundArm = armBinder.BindSwitchExpressionArm(arm, inputType, valEscape, diagnostics); + var boundArm = armBinder.BindSwitchExpressionArm(arm, inputType, diagnostics); builder.Add(boundArm); } return builder.ToImmutableAndFree(); } - internal (TypeSymbol GoverningType, uint GoverningValEscape) GetInputTypeAndValEscape(BoundExpression? inputExpression = null) + internal TypeSymbol GetInputType(BoundExpression? inputExpression = null) { inputExpression ??= BindSwitchGoverningExpression(BindingDiagnosticBag.Discarded); Debug.Assert(inputExpression.Type is not null); - return (inputExpression.Type, GetValEscape(inputExpression, LocalScopeDepth)); + return inputExpression.Type; } private BoundExpression BindSwitchGoverningExpression(BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index fff7a1825be72..e3b5721d490ec 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -150,7 +150,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo else { hasErrors |= ReportUseSite(awaitableTypeOpt, diagnostics, awaitKeyword); - var placeholder = new BoundAwaitableValuePlaceholder(syntax, valEscape: originalBinder.LocalScopeDepth, awaitableTypeOpt).MakeCompilerGenerated(); + var placeholder = new BoundAwaitableValuePlaceholder(syntax, awaitableTypeOpt).MakeCompilerGenerated(); awaitOpt = originalBinder.BindAwaitInfo(placeholder, syntax, diagnostics, ref hasErrors); } } diff --git a/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs index 9e096bbe4529c..1a0aeb05d04de 100644 --- a/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/WithExternAliasesBinder.cs @@ -69,8 +69,6 @@ internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo resu return null; } - internal sealed override uint LocalScopeDepth => Binder.CallingMethodScope; - internal static WithExternAliasesBinder Create(SourceNamespaceSymbol declaringSymbol, CSharpSyntaxNode declarationSyntax, Binder next) { return new FromSyntax(declaringSymbol, declarationSyntax, next); diff --git a/src/Compilers/CSharp/Portable/Binder/WithLambdaParametersBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithLambdaParametersBinder.cs index 05400c70996e4..c31a0543f343a 100644 --- a/src/Compilers/CSharp/Portable/Binder/WithLambdaParametersBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/WithLambdaParametersBinder.cs @@ -176,7 +176,5 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable(); } - - internal override uint LocalScopeDepth => Binder.CurrentMethodScope; } } diff --git a/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs b/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs index 65ec9dfd2d5cf..6dca9506b42e6 100644 --- a/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/WithUsingNamespacesAndTypesBinder.cs @@ -225,8 +225,6 @@ internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo resu return null; } - internal override uint LocalScopeDepth => Binder.CallingMethodScope; - internal override ImportChain? ImportChain { get diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 1ee4b8bd5558a..bb18785eef8cd 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -114,7 +114,6 @@ - @@ -132,7 +131,6 @@ --> - - - @@ -175,7 +171,6 @@ - @@ -978,6 +973,7 @@ + - @@ -2384,13 +2379,12 @@ - + - diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index 51b200b121f41..975aff78f5f4e 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -595,7 +595,7 @@ public BoundGotoStatement(SyntaxNode syntax, LabelSymbol label, bool hasErrors = internal partial class BoundBlock { - public BoundBlock(SyntaxNode syntax, ImmutableArray locals, ImmutableArray statements, bool hasErrors = false) : this(syntax, locals, ImmutableArray.Empty, statements, hasErrors) + public BoundBlock(SyntaxNode syntax, ImmutableArray locals, ImmutableArray statements, bool hasErrors = false) : this(syntax, locals, ImmutableArray.Empty, hasUnsafeModifier: false, statements, hasErrors) { } diff --git a/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs b/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs index 2d4ed85c5b7a9..4bd5f83599a20 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/InterpolatedStringHandlerData.cs @@ -14,11 +14,6 @@ internal readonly struct InterpolatedStringHandlerData public readonly BoundExpression Construction; public readonly bool UsesBoolReturns; /// - /// The scope of the expression that contained the interpolated string during initial binding. This is used to determine the SafeToEscape rules - /// for the builder during lowering. - /// - public readonly uint ScopeOfContainingExpression; - /// /// The placeholders that are used for . /// public readonly ImmutableArray ArgumentPlaceholders; @@ -35,7 +30,6 @@ public InterpolatedStringHandlerData( TypeSymbol builderType, BoundExpression construction, bool usesBoolReturns, - uint scopeOfContainingExpression, ImmutableArray placeholders, ImmutableArray> positionInfo, BoundInterpolatedStringHandlerPlaceholder receiverPlaceholder) @@ -48,7 +42,6 @@ public InterpolatedStringHandlerData( BuilderType = builderType; Construction = construction; UsesBoolReturns = usesBoolReturns; - ScopeOfContainingExpression = scopeOfContainingExpression; ArgumentPlaceholders = placeholders; PositionInfo = positionInfo; ReceiverPlaceholder = receiverPlaceholder; diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs index 718e6488c17db..0cf0546f9c14d 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -15,7 +15,7 @@ public BoundDeconstructValuePlaceholder SetInferredTypeWithAnnotations(TypeWithA { Debug.Assert(Placeholder is null); - Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol: VariableSymbol, ValEscape, isDiscardExpression: IsDiscardExpression, type.Type, hasErrors: this.HasErrors || !success); + Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol: VariableSymbol, isDiscardExpression: IsDiscardExpression, type.Type, hasErrors: this.HasErrors || !success); return Placeholder; } diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index f310e54dd8226..1ac388c96050d 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -2279,18 +2279,6 @@ public override RefKind RefKind get { return RefKind.None; } } - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable(); - - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal override uint RefEscapeScope => throw ExceptionUtilities.Unreachable(); - internal override DeclarationScope Scope => DeclarationScope.Unscoped; } } diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 82baaf55ba500..cb94747a8a175 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1037,6 +1037,8 @@ private void CompileMethod( { analyzedInitializers = InitializerRewriter.RewriteConstructor(processedInitializers.BoundInitializers, methodSymbol); processedInitializers.HasErrors = processedInitializers.HasErrors || analyzedInitializers.HasAnyErrors; + + RefSafetyAnalysis.Analyze(_compilation, methodSymbol, processedInitializers.BoundInitializers, diagsForCurrentMethod); } body = BindMethodBody( @@ -1082,7 +1084,7 @@ private void CompileMethod( { insertAt = 1; } - body = body.Update(body.Locals, body.LocalFunctions, body.Statements.Insert(insertAt, analyzedInitializers)); + body = body.Update(body.Locals, body.LocalFunctions, body.HasUnsafeModifier, body.Statements.Insert(insertAt, analyzedInitializers)); includeNonEmptyInitializersInBody = false; analyzedInitializers = null; } @@ -1826,12 +1828,15 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && finalNullableState: out _); } } + forSemanticModel = new MethodBodySemanticModel.InitialState(syntaxNode, methodBodyForSemanticModel, bodyBinder, snapshotManager, remappedSymbols); #if DEBUG Debug.Assert(IsEmptyRewritePossible(methodBody)); #endif + RefSafetyAnalysis.Analyze(compilation, method, methodBody, diagnostics); + switch (methodBody.Kind) { case BoundKind.ConstructorMethodBody: diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs index 1f2f312f6bd72..e20abe8db1c22 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs @@ -135,7 +135,7 @@ private static BoundBlock PrependImplicitInitializations(BoundBlock body, Method } var initializations = F.HiddenSequencePoint(F.Block(builder.ToImmutableAndFree())); - return body.Update(body.Locals, body.LocalFunctions, body.Statements.Insert(index: 0, initializations)); + return body.Update(body.Locals, body.LocalFunctions, body.HasUnsafeModifier, body.Statements.Insert(index: 0, initializations)); } private static BoundBlock AppendImplicitReturn(BoundBlock body, MethodSymbol method, bool originalBodyNested) @@ -149,7 +149,7 @@ private static BoundBlock AppendImplicitReturn(BoundBlock body, MethodSymbol met builder.AddRange(statements, n - 1); builder.Add(AppendImplicitReturn((BoundBlock)statements[n - 1], method)); - return body.Update(body.Locals, ImmutableArray.Empty, builder.ToImmutableAndFree()); + return body.Update(body.Locals, ImmutableArray.Empty, body.HasUnsafeModifier, builder.ToImmutableAndFree()); } else { @@ -177,7 +177,7 @@ internal static BoundBlock AppendImplicitReturn(BoundBlock body, MethodSymbol me ? (BoundStatement)BoundYieldBreakStatement.Synthesized(syntax) : BoundReturnStatement.Synthesized(syntax, RefKind.None, null); - return body.Update(body.Locals, body.LocalFunctions, body.Statements.Add(ret)); + return body.Update(body.Locals, body.LocalFunctions, body.HasUnsafeModifier, body.Statements.Add(ret)); } private static bool Analyze( diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs index 262c1713b142b..d0d5c67523e42 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs @@ -61,8 +61,6 @@ public override bool Equals(Symbol obj, TypeCompareKind compareKind) internal override ImmutableBindingDiagnostic GetConstantValueDiagnostics(BoundExpression boundInitValue) => ImmutableBindingDiagnostic.Empty; internal override SyntaxNode GetDeclaratorSyntax() => throw ExceptionUtilities.Unreachable(); internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) => throw ExceptionUtilities.Unreachable(); - internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable(); - internal override uint RefEscapeScope => throw ExceptionUtilities.Unreachable(); internal override DeclarationScope Scope => DeclarationScope.Unscoped; } } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 58a33122f23d6..9ca49dbcadfae 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -510,41 +510,38 @@ public BoundCapturedReceiverPlaceholder Update(BoundExpression receiver, uint lo internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase { - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type, bool hasErrors) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, bool isDiscardExpression, TypeSymbol type, bool hasErrors) : base(BoundKind.DeconstructValuePlaceholder, syntax, type, hasErrors) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.VariableSymbol = variableSymbol; - this.ValEscape = valEscape; this.IsDiscardExpression = isDiscardExpression; } - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, Symbol? variableSymbol, bool isDiscardExpression, TypeSymbol type) : base(BoundKind.DeconstructValuePlaceholder, syntax, type) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.VariableSymbol = variableSymbol; - this.ValEscape = valEscape; this.IsDiscardExpression = isDiscardExpression; } public new TypeSymbol Type => base.Type!; public Symbol? VariableSymbol { get; } - public uint ValEscape { get; } public bool IsDiscardExpression { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDeconstructValuePlaceholder(this); - public BoundDeconstructValuePlaceholder Update(Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, TypeSymbol type) + public BoundDeconstructValuePlaceholder Update(Symbol? variableSymbol, bool isDiscardExpression, TypeSymbol type) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape || isDiscardExpression != this.IsDiscardExpression || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || isDiscardExpression != this.IsDiscardExpression || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol, valEscape, isDiscardExpression, type, this.HasErrors); + var result = new BoundDeconstructValuePlaceholder(this.Syntax, variableSymbol, isDiscardExpression, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -589,29 +586,26 @@ public BoundTupleOperandPlaceholder Update(TypeSymbol type) internal sealed partial class BoundAwaitableValuePlaceholder : BoundValuePlaceholderBase { - public BoundAwaitableValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol? type, bool hasErrors) + public BoundAwaitableValuePlaceholder(SyntaxNode syntax, TypeSymbol? type, bool hasErrors) : base(BoundKind.AwaitableValuePlaceholder, syntax, type, hasErrors) { - this.ValEscape = valEscape; } - public BoundAwaitableValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol? type) + public BoundAwaitableValuePlaceholder(SyntaxNode syntax, TypeSymbol? type) : base(BoundKind.AwaitableValuePlaceholder, syntax, type) { - this.ValEscape = valEscape; } public new TypeSymbol? Type => base.Type; - public uint ValEscape { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitAwaitableValuePlaceholder(this); - public BoundAwaitableValuePlaceholder Update(uint valEscape, TypeSymbol? type) + public BoundAwaitableValuePlaceholder Update(TypeSymbol? type) { - if (valEscape != this.ValEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundAwaitableValuePlaceholder(this.Syntax, valEscape, type, this.HasErrors); + var result = new BoundAwaitableValuePlaceholder(this.Syntax, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -729,38 +723,35 @@ public BoundImplicitIndexerValuePlaceholder Update(TypeSymbol type) internal sealed partial class BoundImplicitIndexerReceiverPlaceholder : BoundValuePlaceholderBase { - public BoundImplicitIndexerReceiverPlaceholder(SyntaxNode syntax, uint valEscape, bool isEquivalentToThisReference, TypeSymbol type, bool hasErrors) + public BoundImplicitIndexerReceiverPlaceholder(SyntaxNode syntax, bool isEquivalentToThisReference, TypeSymbol type, bool hasErrors) : base(BoundKind.ImplicitIndexerReceiverPlaceholder, syntax, type, hasErrors) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; this.IsEquivalentToThisReference = isEquivalentToThisReference; } - public BoundImplicitIndexerReceiverPlaceholder(SyntaxNode syntax, uint valEscape, bool isEquivalentToThisReference, TypeSymbol type) + public BoundImplicitIndexerReceiverPlaceholder(SyntaxNode syntax, bool isEquivalentToThisReference, TypeSymbol type) : base(BoundKind.ImplicitIndexerReceiverPlaceholder, syntax, type) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; this.IsEquivalentToThisReference = isEquivalentToThisReference; } public new TypeSymbol Type => base.Type!; - public uint ValEscape { get; } public override bool IsEquivalentToThisReference { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitImplicitIndexerReceiverPlaceholder(this); - public BoundImplicitIndexerReceiverPlaceholder Update(uint valEscape, bool isEquivalentToThisReference, TypeSymbol type) + public BoundImplicitIndexerReceiverPlaceholder Update(bool isEquivalentToThisReference, TypeSymbol type) { - if (valEscape != this.ValEscape || isEquivalentToThisReference != this.IsEquivalentToThisReference || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (isEquivalentToThisReference != this.IsEquivalentToThisReference || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundImplicitIndexerReceiverPlaceholder(this.Syntax, valEscape, isEquivalentToThisReference, type, this.HasErrors); + var result = new BoundImplicitIndexerReceiverPlaceholder(this.Syntax, isEquivalentToThisReference, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -770,35 +761,32 @@ public BoundImplicitIndexerReceiverPlaceholder Update(uint valEscape, bool isEqu internal sealed partial class BoundListPatternReceiverPlaceholder : BoundEarlyValuePlaceholderBase { - public BoundListPatternReceiverPlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type, bool hasErrors) + public BoundListPatternReceiverPlaceholder(SyntaxNode syntax, TypeSymbol type, bool hasErrors) : base(BoundKind.ListPatternReceiverPlaceholder, syntax, type, hasErrors) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; } - public BoundListPatternReceiverPlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type) + public BoundListPatternReceiverPlaceholder(SyntaxNode syntax, TypeSymbol type) : base(BoundKind.ListPatternReceiverPlaceholder, syntax, type) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; } public new TypeSymbol Type => base.Type!; - public uint ValEscape { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitListPatternReceiverPlaceholder(this); - public BoundListPatternReceiverPlaceholder Update(uint valEscape, TypeSymbol type) + public BoundListPatternReceiverPlaceholder Update(TypeSymbol type) { - if (valEscape != this.ValEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundListPatternReceiverPlaceholder(this.Syntax, valEscape, type, this.HasErrors); + var result = new BoundListPatternReceiverPlaceholder(this.Syntax, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -843,35 +831,32 @@ public BoundListPatternIndexPlaceholder Update(TypeSymbol type) internal sealed partial class BoundSlicePatternReceiverPlaceholder : BoundEarlyValuePlaceholderBase { - public BoundSlicePatternReceiverPlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type, bool hasErrors) + public BoundSlicePatternReceiverPlaceholder(SyntaxNode syntax, TypeSymbol type, bool hasErrors) : base(BoundKind.SlicePatternReceiverPlaceholder, syntax, type, hasErrors) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; } - public BoundSlicePatternReceiverPlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type) + public BoundSlicePatternReceiverPlaceholder(SyntaxNode syntax, TypeSymbol type) : base(BoundKind.SlicePatternReceiverPlaceholder, syntax, type) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); - this.ValEscape = valEscape; } public new TypeSymbol Type => base.Type!; - public uint ValEscape { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitSlicePatternReceiverPlaceholder(this); - public BoundSlicePatternReceiverPlaceholder Update(uint valEscape, TypeSymbol type) + public BoundSlicePatternReceiverPlaceholder Update(TypeSymbol type) { - if (valEscape != this.ValEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundSlicePatternReceiverPlaceholder(this.Syntax, valEscape, type, this.HasErrors); + var result = new BoundSlicePatternReceiverPlaceholder(this.Syntax, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -3046,7 +3031,7 @@ public BoundStepThroughSequencePoint Update(TextSpan span) internal sealed partial class BoundBlock : BoundStatementList { - public BoundBlock(SyntaxNode syntax, ImmutableArray locals, ImmutableArray localFunctions, ImmutableArray statements, bool hasErrors = false) + public BoundBlock(SyntaxNode syntax, ImmutableArray locals, ImmutableArray localFunctions, bool hasUnsafeModifier, ImmutableArray statements, bool hasErrors = false) : base(BoundKind.Block, syntax, statements, hasErrors || statements.HasErrors()) { @@ -3056,19 +3041,21 @@ public BoundBlock(SyntaxNode syntax, ImmutableArray locals, Immutab this.Locals = locals; this.LocalFunctions = localFunctions; + this.HasUnsafeModifier = hasUnsafeModifier; } public ImmutableArray Locals { get; } public ImmutableArray LocalFunctions { get; } + public bool HasUnsafeModifier { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitBlock(this); - public BoundBlock Update(ImmutableArray locals, ImmutableArray localFunctions, ImmutableArray statements) + public BoundBlock Update(ImmutableArray locals, ImmutableArray localFunctions, bool hasUnsafeModifier, ImmutableArray statements) { - if (locals != this.Locals || localFunctions != this.LocalFunctions || statements != this.Statements) + if (locals != this.Locals || localFunctions != this.LocalFunctions || hasUnsafeModifier != this.HasUnsafeModifier || statements != this.Statements) { - var result = new BoundBlock(this.Syntax, locals, localFunctions, statements, this.HasErrors); + var result = new BoundBlock(this.Syntax, locals, localFunctions, hasUnsafeModifier, statements, this.HasErrors); result.CopyAttributes(this); return result; } @@ -7401,38 +7388,35 @@ public BoundInterpolatedStringHandlerPlaceholder Update(TypeSymbol? type) internal sealed partial class BoundInterpolatedStringArgumentPlaceholder : BoundValuePlaceholderBase { - public BoundInterpolatedStringArgumentPlaceholder(SyntaxNode syntax, int argumentIndex, uint valSafeToEscape, TypeSymbol type, bool hasErrors) + public BoundInterpolatedStringArgumentPlaceholder(SyntaxNode syntax, int argumentIndex, TypeSymbol type, bool hasErrors) : base(BoundKind.InterpolatedStringArgumentPlaceholder, syntax, type, hasErrors) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.ArgumentIndex = argumentIndex; - this.ValSafeToEscape = valSafeToEscape; } - public BoundInterpolatedStringArgumentPlaceholder(SyntaxNode syntax, int argumentIndex, uint valSafeToEscape, TypeSymbol type) + public BoundInterpolatedStringArgumentPlaceholder(SyntaxNode syntax, int argumentIndex, TypeSymbol type) : base(BoundKind.InterpolatedStringArgumentPlaceholder, syntax, type) { RoslynDebug.Assert(type is object, "Field 'type' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.ArgumentIndex = argumentIndex; - this.ValSafeToEscape = valSafeToEscape; } public new TypeSymbol Type => base.Type!; public int ArgumentIndex { get; } - public uint ValSafeToEscape { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitInterpolatedStringArgumentPlaceholder(this); - public BoundInterpolatedStringArgumentPlaceholder Update(int argumentIndex, uint valSafeToEscape, TypeSymbol type) + public BoundInterpolatedStringArgumentPlaceholder Update(int argumentIndex, TypeSymbol type) { - if (argumentIndex != this.ArgumentIndex || valSafeToEscape != this.ValSafeToEscape || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (argumentIndex != this.ArgumentIndex || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundInterpolatedStringArgumentPlaceholder(this.Syntax, argumentIndex, valSafeToEscape, type, this.HasErrors); + var result = new BoundInterpolatedStringArgumentPlaceholder(this.Syntax, argumentIndex, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -8183,35 +8167,32 @@ public DeconstructionVariablePendingInference Update(Symbol variableSymbol, Boun internal sealed partial class OutDeconstructVarPendingInference : BoundExpression { - public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression, bool hasErrors) + public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, bool isDiscardExpression, bool hasErrors) : base(BoundKind.OutDeconstructVarPendingInference, syntax, null, hasErrors) { this.VariableSymbol = variableSymbol; - this.ValEscape = valEscape; this.IsDiscardExpression = isDiscardExpression; } - public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, uint valEscape, bool isDiscardExpression) + public OutDeconstructVarPendingInference(SyntaxNode syntax, Symbol? variableSymbol, bool isDiscardExpression) : base(BoundKind.OutDeconstructVarPendingInference, syntax, null) { this.VariableSymbol = variableSymbol; - this.ValEscape = valEscape; this.IsDiscardExpression = isDiscardExpression; } public new TypeSymbol? Type => base.Type; public Symbol? VariableSymbol { get; } - public uint ValEscape { get; } public bool IsDiscardExpression { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitOutDeconstructVarPendingInference(this); - public OutDeconstructVarPendingInference Update(Symbol? variableSymbol, uint valEscape, bool isDiscardExpression) + public OutDeconstructVarPendingInference Update(Symbol? variableSymbol, bool isDiscardExpression) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || valEscape != this.ValEscape || isDiscardExpression != this.IsDiscardExpression) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(variableSymbol, this.VariableSymbol) || isDiscardExpression != this.IsDiscardExpression) { - var result = new OutDeconstructVarPendingInference(this.Syntax, variableSymbol, valEscape, isDiscardExpression, this.HasErrors); + var result = new OutDeconstructVarPendingInference(this.Syntax, variableSymbol, isDiscardExpression, this.HasErrors); result.CopyAttributes(this); return result; } @@ -10273,7 +10254,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.VariableSymbol, node.ValEscape, node.IsDiscardExpression, type); + return node.Update(node.VariableSymbol, node.IsDiscardExpression, type); } public override BoundNode? VisitTupleOperandPlaceholder(BoundTupleOperandPlaceholder node) { @@ -10283,7 +10264,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ValEscape, type); + return node.Update(type); } public override BoundNode? VisitDisposableValuePlaceholder(BoundDisposableValuePlaceholder node) { @@ -10303,12 +10284,12 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitImplicitIndexerReceiverPlaceholder(BoundImplicitIndexerReceiverPlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ValEscape, node.IsEquivalentToThisReference, type); + return node.Update(node.IsEquivalentToThisReference, type); } public override BoundNode? VisitListPatternReceiverPlaceholder(BoundListPatternReceiverPlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ValEscape, type); + return node.Update(type); } public override BoundNode? VisitListPatternIndexPlaceholder(BoundListPatternIndexPlaceholder node) { @@ -10318,7 +10299,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitSlicePatternReceiverPlaceholder(BoundSlicePatternReceiverPlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ValEscape, type); + return node.Update(type); } public override BoundNode? VisitSlicePatternRangePlaceholder(BoundSlicePatternRangePlaceholder node) { @@ -10689,7 +10670,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitBlock(BoundBlock node) { ImmutableArray statements = this.VisitList(node.Statements); - return node.Update(node.Locals, node.LocalFunctions, statements); + return node.Update(node.Locals, node.LocalFunctions, node.HasUnsafeModifier, statements); } public override BoundNode? VisitScope(BoundScope node) { @@ -11398,7 +11379,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitInterpolatedStringArgumentPlaceholder(BoundInterpolatedStringArgumentPlaceholder node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.ArgumentIndex, node.ValSafeToEscape, type); + return node.Update(node.ArgumentIndex, type); } public override BoundNode? VisitStringInsert(BoundStringInsert node) { @@ -11548,7 +11529,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitOutDeconstructVarPendingInference(OutDeconstructVarPendingInference node) { TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.VariableSymbol, node.ValEscape, node.IsDiscardExpression); + return node.Update(node.VariableSymbol, node.IsDiscardExpression); } public override BoundNode? VisitNonConstructorMethodBody(BoundNonConstructorMethodBody node) { @@ -11651,12 +11632,12 @@ public NullabilityRewriter(ImmutableDictionary locals = GetUpdatedArray(node, node.Locals); ImmutableArray localFunctions = GetUpdatedArray(node, node.LocalFunctions); ImmutableArray statements = this.VisitList(node.Statements); - return node.Update(locals, localFunctions, statements); + return node.Update(locals, localFunctions, node.HasUnsafeModifier, statements); } public override BoundNode? VisitScope(BoundScope node) @@ -13870,7 +13851,7 @@ public NullabilityRewriter(ImmutableDictionary new TreeDumperNode("deconstructValuePlaceholder", null, new TreeDumperNode[] { new TreeDumperNode("variableSymbol", node.VariableSymbol, null), - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("isDiscardExpression", node.IsDiscardExpression, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), @@ -14232,7 +14212,6 @@ private BoundTreeDumperNodeProducer() ); public override TreeDumperNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node, object? arg) => new TreeDumperNode("awaitableValuePlaceholder", null, new TreeDumperNode[] { - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) @@ -14262,7 +14241,6 @@ private BoundTreeDumperNodeProducer() ); public override TreeDumperNode VisitImplicitIndexerReceiverPlaceholder(BoundImplicitIndexerReceiverPlaceholder node, object? arg) => new TreeDumperNode("implicitIndexerReceiverPlaceholder", null, new TreeDumperNode[] { - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("isEquivalentToThisReference", node.IsEquivalentToThisReference, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), @@ -14271,7 +14249,6 @@ private BoundTreeDumperNodeProducer() ); public override TreeDumperNode VisitListPatternReceiverPlaceholder(BoundListPatternReceiverPlaceholder node, object? arg) => new TreeDumperNode("listPatternReceiverPlaceholder", null, new TreeDumperNode[] { - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) @@ -14286,7 +14263,6 @@ private BoundTreeDumperNodeProducer() ); public override TreeDumperNode VisitSlicePatternReceiverPlaceholder(BoundSlicePatternReceiverPlaceholder node, object? arg) => new TreeDumperNode("slicePatternReceiverPlaceholder", null, new TreeDumperNode[] { - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) @@ -14862,6 +14838,7 @@ private BoundTreeDumperNodeProducer() { new TreeDumperNode("locals", node.Locals, null), new TreeDumperNode("localFunctions", node.LocalFunctions, null), + new TreeDumperNode("hasUnsafeModifier", node.HasUnsafeModifier, null), new TreeDumperNode("statements", null, from x in node.Statements select Visit(x, null)), new TreeDumperNode("hasErrors", node.HasErrors, null) } @@ -15966,7 +15943,6 @@ private BoundTreeDumperNodeProducer() public override TreeDumperNode VisitInterpolatedStringArgumentPlaceholder(BoundInterpolatedStringArgumentPlaceholder node, object? arg) => new TreeDumperNode("interpolatedStringArgumentPlaceholder", null, new TreeDumperNode[] { new TreeDumperNode("argumentIndex", node.ArgumentIndex, null), - new TreeDumperNode("valSafeToEscape", node.ValSafeToEscape, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), new TreeDumperNode("hasErrors", node.HasErrors, null) @@ -16169,7 +16145,6 @@ private BoundTreeDumperNodeProducer() public override TreeDumperNode VisitOutDeconstructVarPendingInference(OutDeconstructVarPendingInference node, object? arg) => new TreeDumperNode("outDeconstructVarPendingInference", null, new TreeDumperNode[] { new TreeDumperNode("variableSymbol", node.VariableSymbol, null), - new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("isDiscardExpression", node.IsDiscardExpression, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null), diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ClosureConversion.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ClosureConversion.cs index b4f591c07eb0c..60e5904d86ae1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ClosureConversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/ClosureConversion.cs @@ -1146,7 +1146,7 @@ private BoundBlock RewriteBlock(BoundBlock node, ArrayBuilder p } // TODO: we may not need to update if there was nothing to rewrite. - return node.Update(newLocals.ToImmutableAndFree(), node.LocalFunctions, newStatements.ToImmutableAndFree()); + return node.Update(newLocals.ToImmutableAndFree(), node.LocalFunctions, node.HasUnsafeModifier, newStatements.ToImmutableAndFree()); } public override BoundNode VisitScope(BoundScope node) diff --git a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs index 8a8be3e52ade2..ed08422012974 100644 --- a/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs +++ b/src/Compilers/CSharp/Portable/Lowering/Instrumentation/DebugInfoInjector.cs @@ -75,7 +75,7 @@ public override BoundStatement InstrumentFieldOrPropertyInitializer(BoundStateme if (rewritten.Kind == BoundKind.Block) { var block = (BoundBlock)rewritten; - return block.Update(block.Locals, block.LocalFunctions, ImmutableArray.Create(InstrumentFieldOrPropertyInitializer(block.Statements.Single(), syntax))); + return block.Update(block.Locals, block.LocalFunctions, block.HasUnsafeModifier, ImmutableArray.Create(InstrumentFieldOrPropertyInitializer(block.Statements.Single(), syntax))); } return InstrumentFieldOrPropertyInitializer(rewritten, syntax); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 3366d933e167f..63b3899c7ebcd 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -606,7 +606,7 @@ public override BoundNode VisitTypeOrInstanceInitializers(BoundTypeOrInstanceIni var block = (BoundBlock)initializer; var statement = RewriteExpressionStatement((BoundExpressionStatement)block.Statements.Single(), suppressInstrumentation: true); Debug.Assert(statement is { }); - statements.Add(block.Update(block.Locals, block.LocalFunctions, ImmutableArray.Create(statement))); + statements.Add(block.Update(block.Locals, block.LocalFunctions, block.HasUnsafeModifier, ImmutableArray.Create(statement))); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs index 44d4337973e06..a79417ba65866 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Block.cs @@ -2,13 +2,9 @@ // 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.Collections.Immutable; -using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp { @@ -21,7 +17,7 @@ public override BoundNode VisitBlock(BoundBlock node) if (!this.Instrument || (node != _rootStatement && (node.WasCompilerGenerated || node.Syntax.Kind() != SyntaxKind.Block))) { - return node.Update(node.Locals, node.LocalFunctions, builder.ToImmutableAndFree()); + return node.Update(node.Locals, node.LocalFunctions, node.HasUnsafeModifier, builder.ToImmutableAndFree()); } LocalSymbol? synthesizedLocal; @@ -41,7 +37,7 @@ public override BoundNode VisitBlock(BoundBlock node) builder.Add(epilogue); } - return new BoundBlock(node.Syntax, synthesizedLocal == null ? node.Locals : node.Locals.Add(synthesizedLocal), node.LocalFunctions, builder.ToImmutableAndFree(), node.HasErrors); + return new BoundBlock(node.Syntax, synthesizedLocal == null ? node.Locals : node.Locals.Add(synthesizedLocal), node.LocalFunctions, node.HasUnsafeModifier, builder.ToImmutableAndFree(), node.HasErrors); } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs index 803ddfcb42236..3066aa674fd89 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs @@ -75,7 +75,7 @@ private BoundExpression MakeImplicitConversionForInterpolatedString(BoundExpress private InterpolationHandlerResult RewriteToInterpolatedStringHandlerPattern(InterpolatedStringHandlerData data, ImmutableArray parts, SyntaxNode syntax) { Debug.Assert(parts.All(static p => p is BoundCall or BoundDynamicInvocation)); - var builderTempSymbol = _factory.InterpolatedStringHandlerLocal(data.BuilderType, data.ScopeOfContainingExpression, syntax); + var builderTempSymbol = _factory.InterpolatedStringHandlerLocal(data.BuilderType, syntax); BoundLocal builderTemp = _factory.Local(builderTempSymbol); // var handler = new HandlerType(baseStringLength, numFormatHoles, ...InterpolatedStringHandlerArgumentAttribute parameters, out bool appendShouldProceed); diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index bcf1bbbab422b..2fe77e03150de 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -150,7 +150,7 @@ public override BoundNode VisitBlock(BoundBlock node) var newLocals = RewriteLocals(node.Locals); var newLocalFunctions = node.LocalFunctions; var newStatements = VisitList(node.Statements); - return node.Update(newLocals, newLocalFunctions, newStatements); + return node.Update(newLocals, newLocalFunctions, node.HasUnsafeModifier, newStatements); } public abstract override BoundNode VisitScope(BoundScope node); @@ -417,7 +417,7 @@ public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node) return node; } - var rewrittenPlaceholder = awaitablePlaceholder.Update(awaitablePlaceholder.ValEscape, VisitType(awaitablePlaceholder.Type)); + var rewrittenPlaceholder = awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type)); _placeholderMap.Add(awaitablePlaceholder, rewrittenPlaceholder); var getAwaiter = (BoundExpression?)this.Visit(node.GetAwaiter); diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 63cd04fbbb7cd..85346eef714e6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -466,7 +466,7 @@ public BoundBlock Block(ImmutableArray locals, ImmutableArray locals, ImmutableArray localFunctions, ImmutableArray statements) { - return new BoundBlock(Syntax, locals, localFunctions, statements) { WasCompilerGenerated = true }; + return new BoundBlock(Syntax, locals, localFunctions, hasUnsafeModifier: false, statements) { WasCompilerGenerated = true }; } public BoundExtractedFinallyBlock ExtractedFinallyBlock(BoundBlock finallyBlock) @@ -551,7 +551,6 @@ public LocalSymbol SynthesizedLocal( public LocalSymbol InterpolatedStringHandlerLocal( TypeSymbol type, - uint valEscapeScope, SyntaxNode syntax #if DEBUG , @@ -560,11 +559,10 @@ SyntaxNode syntax #endif ) { - return new SynthesizedLocalWithValEscape( + return new SynthesizedLocal( CurrentFunction, TypeWithAnnotations.Create(type), SynthesizedLocalKind.LoweringTemp, - valEscapeScope, syntax #if DEBUG , createdAtLineNumber: createdAtLineNumber, createdAtFilePath: createdAtFilePath diff --git a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs index 0c19932d63b6b..7404e170e0c37 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs @@ -354,18 +354,6 @@ public abstract RefKind RefKind get; } - /// - /// Returns the scope to which a local can "escape" ref assignments or other form of aliasing - /// Makes sense only for locals with formal scopes - i.e. source locals - /// - internal abstract uint RefEscapeScope { get; } - - /// - /// Returns the scope to which values of a local can "escape" via ordinary assignments - /// Makes sense only for ref-like locals with formal scopes - i.e. source locals - /// - internal abstract uint ValEscapeScope { get; } - /// /// When a local variable's type is inferred, it may not be used in the /// expression that computes its value (and type). This property returns diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index 03078c7b27815..84da92f01f2f3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -35,18 +35,6 @@ internal class SourceLocalSymbol : LocalSymbol private TypeWithAnnotations.Boxed _type; - /// - /// Scope to which the local can "escape" via aliasing/ref assignment. - /// Not readonly because we can only know escape values after binding the initializer. - /// - protected uint _refEscapeScope; - - /// - /// Scope to which the local's values can "escape" via ordinary assignments. - /// Not readonly because we can only know escape values after binding the initializer. - /// - protected uint _valEscapeScope; - private SourceLocalSymbol( Symbol containingSymbol, Binder scopeBinder, @@ -83,14 +71,6 @@ private SourceLocalSymbol( // create this eagerly as it will always be needed for the EnsureSingleDefinition _locations = ImmutableArray.Create(identifierToken.GetLocation()); - - _refEscapeScope = this._refKind == RefKind.None ? - scopeBinder.LocalScopeDepth : - Binder.CallingMethodScope; // default to returnable, unless there is initializer - - // we do not know the type yet. - // assume this is returnable in case we never get to know our type. - _valEscapeScope = Binder.CallingMethodScope; } /// @@ -106,45 +86,6 @@ internal override SyntaxNode ScopeDesignatorOpt get { return _scopeBinder.ScopeDesignator; } } - // From https://github.com/dotnet/csharplang/blob/main/csharp-11.0/proposals/low-level-struct-improvements.md: - // - // | Parameter or Local | ref-safe-to-escape | safe-to-escape | - // |------------------------|--------------------|----------------| - // | Span s | current method | calling method | - // | scoped Span s | current method | current method | - // | ref Span s | calling method | calling method | - // | scoped ref Span s | current method | calling method | - - internal sealed override uint RefEscapeScope - { - get - { - if (!_scopeBinder.UseUpdatedEscapeRules || - _scope == DeclarationScope.Unscoped) - { - return _refEscapeScope; - } - return _scope == DeclarationScope.RefScoped ? - _scopeBinder.LocalScopeDepth : - Binder.CurrentMethodScope; - } - } - - internal sealed override uint ValEscapeScope - { - get - { - if (!_scopeBinder.UseUpdatedEscapeRules || - _scope == DeclarationScope.Unscoped) - { - return _valEscapeScope; - } - return _scope == DeclarationScope.ValueScoped ? - _scopeBinder.LocalScopeDepth : - Binder.CallingMethodScope; - } - } - internal sealed override DeclarationScope Scope => _scope; /// @@ -308,21 +249,6 @@ internal sealed override bool IsKnownToReferToTempIfReferenceType get { return false; } } - internal virtual void SetRefEscape(uint value) - { - _refEscapeScope = value; - } - - internal virtual void SetValEscape(uint value) - { - // either we should be setting the val escape for the first time, - // or not contradicting what was set before. - Debug.Assert( - _valEscapeScope == Binder.CallingMethodScope - || _valEscapeScope == value); - _valEscapeScope = value; - } - public override Symbol ContainingSymbol { get { return _containingSymbol; } @@ -604,10 +530,6 @@ public LocalWithInitializer( _initializer = initializer; _initializerBinder = initializerBinder; - - // default to the current scope in case we need to handle self-referential error cases. - _refEscapeScope = _scopeBinder.LocalScopeDepth; - _valEscapeScope = _scopeBinder.LocalScopeDepth; } protected override TypeWithAnnotations InferTypeOfVarVariable(BindingDiagnosticBag diagnostics) @@ -666,20 +588,6 @@ internal override ImmutableBindingDiagnostic GetConstantValueDia MakeConstantTuple(inProgress: null, boundInitValue: boundInitValue); return _constantTuple == null ? ImmutableBindingDiagnostic.Empty : _constantTuple.Diagnostics; } - - internal override void SetRefEscape(uint value) - { - Debug.Assert(!_scopeBinder.UseUpdatedEscapeRules || _scope == DeclarationScope.Unscoped); - Debug.Assert(value <= _refEscapeScope); - _refEscapeScope = value; - } - - internal override void SetValEscape(uint value) - { - Debug.Assert(!_scopeBinder.UseUpdatedEscapeRules || _scope == DeclarationScope.Unscoped); - Debug.Assert(value <= _valEscapeScope); - _valEscapeScope = value; - } } /// @@ -725,7 +633,7 @@ protected override TypeWithAnnotations InferTypeOfVarVariable(BindingDiagnosticB /// Symbol for a deconstruction local that might require type inference. /// For instance, local x in var (x, y) = ... or (var x, int y) = .... /// - private class DeconstructionLocalSymbol : SourceLocalSymbol + private sealed class DeconstructionLocalSymbol : SourceLocalSymbol { private readonly SyntaxNode _deconstruction; private readonly Binder _nodeBinder; @@ -788,7 +696,7 @@ internal override SyntaxNode ForbiddenZone } } - private class LocalSymbolWithEnclosingContext : SourceLocalSymbol + private sealed class LocalSymbolWithEnclosingContext : SourceLocalSymbol { private readonly SyntaxNode _forbiddenZone; private readonly Binder _nodeBinder; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index f38024f394628..53f32fa6e0638 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -832,6 +832,8 @@ internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo HasFlag(DeclarationModifiers.File); + internal bool IsUnsafe => HasFlag(DeclarationModifiers.Unsafe); + internal SyntaxTree AssociatedSyntaxTree => declaration.Declarations[0].Location.SourceTree; internal sealed override FileIdentifier? AssociatedFileIdentifier diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/InterpolatedStringBuilderLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/InterpolatedStringBuilderLocalSymbol.cs deleted file mode 100644 index edd88804af4b0..0000000000000 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/InterpolatedStringBuilderLocalSymbol.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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.Runtime.CompilerServices; -using System.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp.Symbols -{ - /// - /// A synthesized local variable with a val escape scope. - /// - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal sealed class SynthesizedLocalWithValEscape : SynthesizedLocal - { - public SynthesizedLocalWithValEscape( - MethodSymbol? containingMethod, - TypeWithAnnotations typeWithAnnotations, - SynthesizedLocalKind kind, - uint valEscapeScope, - SyntaxNode? syntaxOpt = null, - bool isPinned = false, - RefKind refKind = RefKind.None -#if DEBUG - , - [CallerLineNumber] int createdAtLineNumber = 0, - [CallerFilePath] string? createdAtFilePath = null -#endif - ) : base(containingMethod, typeWithAnnotations, kind, syntaxOpt, isPinned, - isKnownToReferToTempIfReferenceType: false, refKind -#if DEBUG - , createdAtLineNumber, createdAtFilePath -#endif - ) - { - ValEscapeScope = valEscapeScope; - } - - internal override uint ValEscapeScope { get; } - } -} diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs index 62a8805780e20..439e2276b6f2b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedLocal.cs @@ -9,7 +9,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -17,7 +16,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// A synthesized local variable. /// [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] - internal class SynthesizedLocal : LocalSymbol + internal sealed class SynthesizedLocal : LocalSymbol { private readonly MethodSymbol _containingMethodOpt; private readonly TypeWithAnnotations _type; @@ -170,18 +169,6 @@ internal sealed override bool IsCompilerGenerated get { return true; } } - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable(); - - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal sealed override uint RefEscapeScope => throw ExceptionUtilities.Unreachable(); - internal sealed override DeclarationScope Scope => DeclarationScope.Unscoped; internal sealed override ConstantValue GetConstantValue(SyntaxNode node, LocalSymbol inProgress, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs index 671a4d4ce63f6..dd9b90976bbd1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/TypeSubstitutedLocalSymbol.cs @@ -103,18 +103,6 @@ public override RefKind RefKind get { return _originalVariable.RefKind; } } - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable(); - - /// - /// Compiler should always be synthesizing locals with correct escape semantics. - /// Checking escape scopes is not valid here. - /// - internal override uint RefEscapeScope => throw ExceptionUtilities.Unreachable(); - /// /// Compiler should always be synthesizing locals with correct escape semantics. /// Checking escape scopes is not valid here. diff --git a/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs b/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs index 747f5e17be18e..be895d4400fa8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs +++ b/src/Compilers/CSharp/Portable/Symbols/UpdatedContainingSymbolLocal.cs @@ -88,8 +88,6 @@ public override bool Equals(Symbol other, TypeCompareKind compareKind) internal override bool IsPinned => _underlyingLocal.IsPinned; internal override bool IsKnownToReferToTempIfReferenceType => _underlyingLocal.IsKnownToReferToTempIfReferenceType; internal override bool IsCompilerGenerated => _underlyingLocal.IsCompilerGenerated; - internal override uint RefEscapeScope => _underlyingLocal.RefEscapeScope; - internal override uint ValEscapeScope => _underlyingLocal.ValEscapeScope; internal override DeclarationScope Scope => _underlyingLocal.Scope; internal override ConstantValue GetConstantValue(SyntaxNode node, LocalSymbol inProgress, BindingDiagnosticBag? diagnostics = null) => _underlyingLocal.GetConstantValue(node, inProgress, diagnostics); diff --git a/src/Compilers/CSharp/Test/Emit2/Diagnostics/OperationAnalyzerTests.cs b/src/Compilers/CSharp/Test/Emit2/Diagnostics/OperationAnalyzerTests.cs index ac5b39f318832..fa344fb23657a 100644 --- a/src/Compilers/CSharp/Test/Emit2/Diagnostics/OperationAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Diagnostics/OperationAnalyzerTests.cs @@ -1322,13 +1322,14 @@ public void LongArithmeticExpressionCSharp() return builder.ToString(); }; // This code will cause OperationWalker to throw `InsufficientExecutionStackException` + var expr = buildSequenceOfBinaryExpressions(8192); var source = @" class Test { public static long Calculate1(long[] f) { long x; -" + $" x = {buildSequenceOfBinaryExpressions(8192)};" + @" +" + $" x = {expr};" + @" return x; } }"; @@ -1336,8 +1337,8 @@ public static long Calculate1(long[] f) CreateCompilationWithMscorlib45(source) .VerifyDiagnostics() .VerifyAnalyzerDiagnostics(new DiagnosticAnalyzer[] { new AssignmentOperationSyntaxTestAnalyzer() }, null, null, - Diagnostic(AssignmentOperationSyntaxTestAnalyzer.AssignmentOperationDescriptor.Id, $"x = {buildSequenceOfBinaryExpressions(8192)}").WithLocation(7, 9), - Diagnostic(AssignmentOperationSyntaxTestAnalyzer.AssignmentSyntaxDescriptor.Id, $"x = {buildSequenceOfBinaryExpressions(8192)}").WithLocation(7, 9)); + Diagnostic(AssignmentOperationSyntaxTestAnalyzer.AssignmentOperationDescriptor.Id, $"x = {expr}").WithLocation(7, 9), + Diagnostic(AssignmentOperationSyntaxTestAnalyzer.AssignmentSyntaxDescriptor.Id, $"x = {expr}").WithLocation(7, 9)); } [WorkItem(9020, "https://github.com/dotnet/roslyn/issues/9020")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 7aaf5c7830dfe..5ca1517d87baa 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -14924,6 +14924,160 @@ static CustomHandler F4() comp.VerifyDiagnostics(); } + [Fact] + public void RefEscape_14() + { + string source = """ + using System.Runtime.CompilerServices; + [InterpolatedStringHandler] + struct CustomHandler + { + public CustomHandler(int literalLength, int formattedCount) { } + } + ref struct R { } + class Program + { + static R F1() + { + R r = F2($""); + return r; + } + static R F2(ref CustomHandler handler) + { + return default; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (13,16): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // return r; + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(13, 16)); + } + + [Fact] + public void RefEscape_15() + { + string source = """ + using System.Runtime.CompilerServices; + [InterpolatedStringHandler] + ref struct CustomHandler + { + public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + public void AppendFormatted(int i) { } + } + ref struct R + { + public CustomHandler Handler; + public object this[[InterpolatedStringHandlerArgument("")] CustomHandler handler] => null; + } + class Program + { + static R F() + { + R r = new R(); + _ = r[$"{1}"]; + return r; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope + // public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (18,13): error CS1620: Argument 3 must be passed with the 'ref' keyword + // _ = r[$"{1}"]; + Diagnostic(ErrorCode.ERR_BadArgRef, "r").WithArguments("3", "ref").WithLocation(18, 13)); + } + + [Fact] + public void RefEscape_16() + { + string source = """ + using System.Runtime.CompilerServices; + [InterpolatedStringHandler] + ref struct CustomHandler + { + public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + public void AppendFormatted(int i) { } + } + ref struct R + { + public CustomHandler Handler; + public object this[ref R r, [InterpolatedStringHandlerArgument("r")] CustomHandler handler] => null; + } + class Program + { + static R F() + { + R r = new R(); + _ = r[ref r, $"{1}"]; + return r; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope + // public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (11,24): error CS0631: ref and out are not valid in this context + // public object this[ref R r, [InterpolatedStringHandlerArgument("r")] CustomHandler handler] => null; + Diagnostic(ErrorCode.ERR_IllegalRefParam, "ref").WithLocation(11, 24), + // (18,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // _ = r[ref r, $"{1}"]; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "r").WithLocation(18, 19), + // (18,19): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // _ = r[ref r, $"{1}"]; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "r").WithLocation(18, 19), + // (18,22): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope + // _ = r[ref r, $"{1}"]; + Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{1}""").WithArguments("CustomHandler.CustomHandler(int, int, ref R)", "r").WithLocation(18, 22), + // (18,22): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope + // _ = r[ref r, $"{1}"]; + Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{1}""").WithArguments("CustomHandler.CustomHandler(int, int, ref R)", "r").WithLocation(18, 22)); + } + + [Fact] + public void RefEscape_17() + { + string source = """ + using System.Runtime.CompilerServices; + [InterpolatedStringHandler] + ref struct CustomHandler + { + public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + public void AppendFormatted(int i) { } + } + ref struct R + { + public CustomHandler Handler; + public R(ref R r, [InterpolatedStringHandlerArgument("r")] CustomHandler handler) { } + } + class Program + { + static R F() + { + R x = new R(); + R y = new R(ref x, $"{1}"); + return x; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (5,97): error CS8352: Cannot use variable 'out CustomHandler this' in this context because it may expose referenced variables outside of their declaration scope + // public CustomHandler(int literalLength, int formattedCount, ref R r) : this() { r.Handler = this; } + Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler this").WithLocation(5, 97), + // (18,25): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // R y = new R(ref x, $"{1}"); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "x").WithLocation(18, 25), + // (18,28): error CS8347: Cannot use a result of 'CustomHandler.CustomHandler(int, int, ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope + // R y = new R(ref x, $"{1}"); + Diagnostic(ErrorCode.ERR_EscapeCall, @"$""{1}""").WithArguments("CustomHandler.CustomHandler(int, int, ref R)", "r").WithLocation(18, 28)); + } + [Theory, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")] [InlineData(@"$""{{ {i} }}""")] [InlineData(@"$""{{ "" + $""{i}"" + $"" }}""")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index af28e95a5480d..5772651e52706 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -1238,10 +1238,7 @@ class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "r").WithArguments("R", "R").WithLocation(13, 24), // (15,24): error CS8168: Cannot return local 'r' by reference because it is not a ref local // return ref r!; // 2 - Diagnostic(ErrorCode.ERR_RefReturnLocal, "r").WithArguments("r").WithLocation(15, 24), - // (15,24): warning CS8619: Nullability of reference types in value of type 'R' doesn't match target type 'R'. - // return ref r!; // 2 - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "r").WithArguments("R", "R").WithLocation(15, 24)); + Diagnostic(ErrorCode.ERR_RefReturnLocal, "r").WithArguments("r").WithLocation(15, 24)); } [WorkItem(31297, "https://github.com/dotnet/roslyn/issues/31297")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 5493a32bae53f..a2161cd73539b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -176,17 +176,25 @@ public void RefStructEscapeInIterator(LanguageVersion languageVersion) using System.Collections; class C { - IEnumerable Gen() + IEnumerable F1() { - Span s = stackalloc int[10]; - yield return s; + Span s1 = stackalloc int[10]; + yield return s1; + } + IEnumerable F2() + { + Span s2 = default; + yield return s2; } }", parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); // Note: an escape analysis error is not given here because we already gave a conversion error. comp.VerifyDiagnostics( // (9,22): error CS0029: Cannot implicitly convert type 'System.Span' to 'object' - // yield return s; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "s").WithArguments("System.Span", "object").WithLocation(9, 22)); + // yield return s1; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "s1").WithArguments("System.Span", "object").WithLocation(9, 22), + // (14,22): error CS0029: Cannot implicitly convert type 'System.Span' to 'object' + // yield return s2; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "s2").WithArguments("System.Span", "object").WithLocation(14, 22)); } [Theory] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index 325c36422de6b..bf509206e29eb 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -5348,6 +5348,41 @@ static void M4(T t4) Diagnostic(ErrorCode.WRN_RefReturnParameter, "t4").WithArguments("t4").WithLocation(41, 26)); } + [Fact] + public void Constructors_02() + { + var source = """ + ref struct R + { + public R(ref int i) { } + public int this[R r] { get { return 0; } set { } } + } + class Program + { + static R F() + { + int i = 0; + R x = new R(ref i); + R y = default; + y = new R { [x] = 1 }; // 1 + y[x] = 1; // 2 + return y; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (13,22): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope + // y = new R { [x] = 1 }; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(13, 22), + // (14,9): error CS8350: This combination of arguments to 'R.this[R]' is disallowed because it may expose variables referenced by parameter 'r' outside of their declaration scope + // y[x] = 1; // 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, "y[x]").WithArguments("R.this[R]", "r").WithLocation(14, 9), + // (14,11): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope + // y[x] = 1; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(14, 11)); + } + [Fact] public void DefiniteAssignment_01() { @@ -8825,6 +8860,7 @@ static void Main() (x, (y, z1)) = s; R2 z2 = default; (x, (y, z2)) = s; // 1 + (z2, (y, z1)) = s; // 2 } static void Deconstruct(this Span s, out R2 x, out R1 y) => throw null; }"; @@ -8835,7 +8871,13 @@ static void Main() Diagnostic(ErrorCode.ERR_EscapeVariable, "(x, (y, z2)) = s").WithArguments("(x, (y, z2)) = s").WithLocation(20, 9), // (20,24): error CS8350: This combination of arguments to 'R1.Deconstruct(out R2, out R2)' is disallowed because it may expose variables referenced by parameter 'this' outside of their declaration scope // (x, (y, z2)) = s; // 1 - Diagnostic(ErrorCode.ERR_CallArgMixing, "s").WithArguments("R1.Deconstruct(out R2, out R2)", "this").WithLocation(20, 24)); + Diagnostic(ErrorCode.ERR_CallArgMixing, "s").WithArguments("R1.Deconstruct(out R2, out R2)", "this").WithLocation(20, 24), + // (21,9): error CS8352: Cannot use variable '(z2, (y, z1)) = s' in this context because it may expose referenced variables outside of their declaration scope + // (z2, (y, z1)) = s; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "(z2, (y, z1)) = s").WithArguments("(z2, (y, z1)) = s").WithLocation(21, 9), + // (21,25): error CS8350: This combination of arguments to 'Program.Deconstruct(Span, out R2, out R1)' is disallowed because it may expose variables referenced by parameter 's' outside of their declaration scope + // (z2, (y, z1)) = s; // 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, "s").WithArguments("Program.Deconstruct(System.Span, out R2, out R1)", "s").WithLocation(21, 25)); } [Theory] @@ -14250,7 +14292,6 @@ class Enumerator2 // foreach ((scoped ref readonly var r51, scoped ref readonly R _) in new Enumerable2(ref r)) break; Diagnostic(ErrorCode.ERR_DeconstructVariableCannotBeByRef, "ref").WithLocation(13, 55) ); - verify(comp); comp = CreateCompilation(source); @@ -15998,7 +16039,10 @@ void M2(D2 d2) { } // (13,9): error CS0121: The call is ambiguous between the following methods or properties: 'C.M2(D1)' and 'C.M2(D2)' // M2(r => r); // 1 Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments("C.M2(D1)", "C.M2(D2)").WithLocation(13, 9), - // (14,28): error CS8352: Cannot use variable 'scoped R r' in this context because it may expose referenced variables outside of their declaration scope + // (14,9): error CS0121: The call is ambiguous between the following methods or properties: 'C.M2(D1)' and 'C.M2(D2)' + // M2((scoped R r) => r); // 2 + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments("C.M2(D1)", "C.M2(D2)").WithLocation(14, 9), + // (14,28): error CS8352: Cannot use variable 'scoped R' in this context because it may expose referenced variables outside of their declaration scope // M2((scoped R r) => r); // 2 Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("scoped R r").WithLocation(14, 28), // (15,9): error CS0121: The call is ambiguous between the following methods or properties: 'C.M2(D1)' and 'C.M2(D2)' @@ -16297,12 +16341,88 @@ static void M(D2 d2) { } // (18,36): error CS8352: Cannot use variable 'scoped R r8' in this context because it may expose referenced variables outside of their declaration scope // D1 d1_3 = (scoped R r8) => r8; // 7 Diagnostic(ErrorCode.ERR_EscapeVariable, "r8").WithArguments("scoped R r8").WithLocation(18, 36), + // (19,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M(D1)' and 'Program.M(D2)' + // M((scoped R r9) => r9); // 8 + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("Program.M(D1)", "Program.M(D2)").WithLocation(19, 9), // (19,28): error CS8352: Cannot use variable 'scoped R r9' in this context because it may expose referenced variables outside of their declaration scope // M((scoped R r9) => r9); // 8 Diagnostic(ErrorCode.ERR_EscapeVariable, "r9").WithArguments("scoped R r9").WithLocation(19, 28) ); } + [Fact] + public void DelegateConversions_13() + { + var source = """ + ref struct R { } + + delegate R D1(R r); + delegate object D2(object o); + + class Program + { + static void M(D1 d1) { } + static void M(D2 d2) { } + + static R F(R r, ref int i) => r; + static object F(object o, ref int i) => o; + + static void Main() + { + M(x => + { + int i = 0; + return F(x, ref i); + }); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (16,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M(D1)' and 'Program.M(D2)' + // M(x => + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("Program.M(D1)", "Program.M(D2)").WithLocation(16, 9)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void DelegateConversions_14(LanguageVersion languageVersion) + { + var source = """ + using System; + + ref struct R { } + + delegate R D1(R r); + delegate object D2(object o); + + class Program + { + static void M(D1 d1) { } + static void M(D2 d2) { } + + static void F(ref R x, ref Span y) { } + static void F(ref object x, ref Span y) { } + + static void Main() + { + M(x => + { + Span y = stackalloc int[1]; + F(ref x, ref y); + return x; + }); + } + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics( + // (18,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M(D1)' and 'Program.M(D2)' + // M(x => + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("Program.M(D1)", "Program.M(D2)").WithLocation(18, 9)); + } + [Fact] public void FunctionPointerConversions() { @@ -26469,6 +26589,37 @@ public RefByteContainer Create(ref ByteContainer bc) comp.VerifyDiagnostics(); } + [Fact, WorkItem(63526, "https://github.com/dotnet/roslyn/issues/63526")] + public void ReturnOnlyScope_UnsafeStatement() + { + var source = """ + #pragma warning disable 8321 // unused local function + static void M1(scoped ref S p1, ref S p2, ref S p3) + { + unsafe + { + p2.refField = ref p1.field; // 1 + } + { + p3.refField = ref p1.field; // 2 + } + } + ref struct S + { + public int field; + public ref int refField; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70, options: TestOptions.UnsafeReleaseExe); + comp.VerifyDiagnostics( + // (6,9): warning CS9085: This ref-assigns 'p1.field' to 'refField' but 'p1.field' has a narrower escape scope than 'refField'. + // p2.refField = ref p1.field; // 1 + Diagnostic(ErrorCode.WRN_RefAssignNarrower, "p2.refField = ref p1.field").WithArguments("refField", "p1.field").WithLocation(6, 9), + // (9,9): error CS8374: Cannot ref-assign 'p1.field' to 'refField' because 'p1.field' has a narrower escape scope than 'refField'. + // p3.refField = ref p1.field; // 1 + Diagnostic(ErrorCode.ERR_RefAssignNarrower, "p3.refField = ref p1.field").WithArguments("refField", "p1.field").WithLocation(9, 9)); + } + /// /// Validate that this is properly represented as an out parameter in a constructor and /// can capture ref as ref. @@ -27720,5 +27871,456 @@ static unsafe void F2(out R r1, int i2) // f(out r1, i2); // 1 Diagnostic(ErrorCode.WRN_RefReturnParameter, "i2").WithArguments("i2").WithLocation(18, 19)); } + + [Fact] + public void PatternIndex_01() + { + string source = """ + using System; + using System.Diagnostics.CodeAnalysis; + ref struct R + { + public int Length => 0; + [UnscopedRef] public ref int this[int i] => throw null; + } + class Program + { + static ref int F1(ref R r1) + { + ref int i1 = ref r1[^1]; + return ref i1; + } + static ref int F2(ref R r2, Index i) + { + ref int i2 = ref r2[i]; + return ref i2; + } + static ref int F3() + { + R r3 = new R(); + ref int i3 = ref r3[^3]; + return ref i3; // 1 + } + static ref int F4(Index i) + { + R r4 = new R(); + ref int i4 = ref r4[i]; + return ref i4; // 2 + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (24,20): error CS8157: Cannot return 'i3' by reference because it was initialized to a value that cannot be returned by reference + // return ref i3; // 1 + Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "i3").WithArguments("i3").WithLocation(24, 20), + // (30,20): error CS8157: Cannot return 'i4' by reference because it was initialized to a value that cannot be returned by reference + // return ref i4; // 2 + Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "i4").WithArguments("i4").WithLocation(30, 20)); + } + + [Fact] + public void PatternIndex_02() + { + string source = """ + ref struct R + { + public R(ref int i) { } + public int Length => 0; + public R this[int i] => throw null; + } + class Program + { + static R F1() + { + R r1 = new R(); + if (r1 is [.., var r]) return r; + return r1; + } + static R F2() + { + int i2 = 2; + R r2 = new R(ref i2); + if (r2 is [.., var r]) return r; // 1 + return r2; // 2 + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (19,39): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // if (r2 is [.., var r]) return r; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(19, 39), + // (20,16): error CS8352: Cannot use variable 'r2' in this context because it may expose referenced variables outside of their declaration scope + // return r2; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r2").WithArguments("r2").WithLocation(20, 16)); + } + + [Fact] + public void PatternIndex_03() + { + string source = """ + ref struct R + { + public R(ref int i) { } + public int Length => 0; + public int this[int i] => 0; + public R Slice(int x, int y) => this; + } + class Program + { + static R F1() + { + R r1 = new R(); + if (r1 is [.. [> 0] r]) return r; + return r1; + } + static R F2() + { + int i2 = 2; + R r2 = new R(ref i2); + if (r2 is [.. [> 0] r]) return r; // 1 + return r2; // 2 + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (20,40): error CS8352: Cannot use variable 'r' in this context because it may expose referenced variables outside of their declaration scope + // if (r2 is [.. [> 0] r]) return r; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r").WithArguments("r").WithLocation(20, 40), + // (21,16): error CS8352: Cannot use variable 'r2' in this context because it may expose referenced variables outside of their declaration scope + // return r2; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, "r2").WithArguments("r2").WithLocation(21, 16)); + } + + [Fact] + public void TopLevelStatementLocal() + { + var source = """ + int i = 0; + ref int r = ref i; + class C + { + static void F1() { F2(ref r); } + static ref int F2(ref int r) => ref r; + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,31): error CS8801: Cannot use local variable or local function 'r' declared in a top-level statement in this context. + // static void F1() { F2(ref r); } + Diagnostic(ErrorCode.ERR_SimpleProgramLocalIsReferencedOutsideOfTopLevelStatement, "r").WithArguments("r").WithLocation(5, 31), + // (5,31): error CS0165: Use of unassigned local variable 'r' + // static void F1() { F2(ref r); } + Diagnostic(ErrorCode.ERR_UseDefViolation, "r").WithArguments("r").WithLocation(5, 31)); + } + + [Fact] + public void Discard_01() + { + var source = """ + class Program + { + static ref int F1() + { + return ref F2(out _); + } + static ref int F2(out int i) + { + i = 0; + return ref i; + } + } + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics( + // (5,20): error CS8347: Cannot use a result of 'Program.F2(out int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return ref F2(out _); + Diagnostic(ErrorCode.ERR_EscapeCall, "F2(out _)").WithArguments("Program.F2(out int)", "i").WithLocation(5, 20), + // (5,27): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return ref F2(out _); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "_").WithLocation(5, 27)); + } + + [Fact] + public void Discard_02() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + class Program + { + static ref int F1() + { + return ref F2(out _); + } + static ref int F2([UnscopedRef] out int i) + { + i = 0; + return ref i; + } + } + """; + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (6,20): error CS8347: Cannot use a result of 'Program.F2(out int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // return ref F2(out _); + Diagnostic(ErrorCode.ERR_EscapeCall, "F2(out _)").WithArguments("Program.F2(out int)", "i").WithLocation(6, 20), + // (6,27): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // return ref F2(out _); + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "_").WithLocation(6, 27)); + } + + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.CSharp11)] + public void Discard_03(LanguageVersion languageVersion) + { + var source = """ + ref struct R + { + public void Deconstruct(out int x, out R y) + { + x = 0; + y = default; + } + } + class Program + { + static R F1() + { + R r1 = default; + int i; + (i, _) = r1; + return r1; + } + static R F2() + { + R r2 = default; + int i; + r2.Deconstruct(out i, out _); + return r2; + } + } + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + comp.VerifyDiagnostics(); + } + + [Fact] + public void SwitchExpression_Assignment() + { + var source = """ + using System; + class Program + { + static void M() + { + Span s; + Span outer = stackalloc int[100]; + s = outer switch + { + Span inner => inner + }; + } + } + """; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics( + // (10,32): error CS8352: Cannot use variable 'inner' in this context because it may expose referenced variables outside of their declaration scope + // Span inner => inner + Diagnostic(ErrorCode.ERR_EscapeVariable, "inner").WithArguments("inner").WithLocation(10, 32)); + } + + [Fact] + public void SwitchExpression_Argument() + { + var source = """ + using System; + class Program + { + static Span F(Span x, Span y) + { + return x; + } + static void M() + { + Span x = default; + Span y = stackalloc int[100]; + x = F(x, y switch { Span inner => inner }); + } + } + """; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics( + // (12,13): error CS8347: Cannot use a result of 'Program.F(Span, Span)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // x = F(x, y switch { Span inner => inner }); + Diagnostic(ErrorCode.ERR_EscapeCall, "F(x, y switch { Span inner => inner })").WithArguments("Program.F(System.Span, System.Span)", "y").WithLocation(12, 13), + // (12,48): error CS8352: Cannot use variable 'inner' in this context because it may expose referenced variables outside of their declaration scope + // x = F(x, y switch { Span inner => inner }); + Diagnostic(ErrorCode.ERR_EscapeVariable, "inner").WithArguments("inner").WithLocation(12, 48)); + } + + [Fact] + public void SwitchExpression_Return() + { + var source = """ + using System; + class Program + { + static Span M() + { + Span outer = stackalloc int[100]; + return outer switch + { + Span inner => inner + }; + } + } + """; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics( + // (9,32): error CS8352: Cannot use variable 'inner' in this context because it may expose referenced variables outside of their declaration scope + // Span inner => inner + Diagnostic(ErrorCode.ERR_EscapeVariable, "inner").WithArguments("inner").WithLocation(9, 32)); + } + + [Fact] + public void SwitchExpression_YieldReturn() + { + var source = """ + using System; + using System.Collections.Generic; + class Program + { + static IEnumerable M() + { + Span outer = stackalloc int[100]; + yield return (outer switch + { + Span inner => inner + })[0]; + } + } + """; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void SwitchExpression_FieldInitializer() + { + var source = """ + using System; + class Program + { + static int F = (new Span() switch + { + Span inner => inner + })[0]; + } + """; + var comp = CreateCompilationWithSpan(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void UnsafeContext_LocalFunction_01() + { + var source = """ + #pragma warning disable 8321 + class Program + { + static ref int F0(ref int x, ref int y) + { + return ref x; + } + static void F1() + { + static ref int Local1(ref int x1, int y1) + { + return ref F0(ref x1, ref y1); + } + unsafe static ref int Local2(ref int x2, int y2) + { + return ref F0(ref x2, ref y2); + } + unsafe + { + static ref int Local3(ref int x3, int y3) + { + return ref F0(ref x3, ref y3); + } + unsafe static ref int Local4(ref int x4, int y4) + { + return ref F0(ref x4, ref y4); + } + } + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (12,24): error CS8347: Cannot use a result of 'Program.F0(ref int, ref int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // return ref F0(ref x1, ref y1); + Diagnostic(ErrorCode.ERR_EscapeCall, "F0(ref x1, ref y1)").WithArguments("Program.F0(ref int, ref int)", "y").WithLocation(12, 24), + // (12,39): error CS8166: Cannot return a parameter by reference 'y1' because it is not a ref parameter + // return ref F0(ref x1, ref y1); + Diagnostic(ErrorCode.ERR_RefReturnParameter, "y1").WithArguments("y1").WithLocation(12, 39), + // (16,39): warning CS9087: This returns a parameter by reference 'y2' but it is not a ref parameter + // return ref F0(ref x2, ref y2); + Diagnostic(ErrorCode.WRN_RefReturnParameter, "y2").WithArguments("y2").WithLocation(16, 39), + // (22,43): warning CS9087: This returns a parameter by reference 'y3' but it is not a ref parameter + // return ref F0(ref x3, ref y3); + Diagnostic(ErrorCode.WRN_RefReturnParameter, "y3").WithArguments("y3").WithLocation(22, 43), + // (26,43): warning CS9087: This returns a parameter by reference 'y4' but it is not a ref parameter + // return ref F0(ref x4, ref y4); + Diagnostic(ErrorCode.WRN_RefReturnParameter, "y4").WithArguments("y4").WithLocation(26, 43)); + } + + [Fact] + public void UnsafeContext_LocalFunction_02() + { + var source = """ + #pragma warning disable 8321 + class A + { + internal static ref int F0(ref int x, ref int y) + { + return ref x; + } + } + class B1 : A + { + unsafe static void F1() + { + static ref int Local1(ref int x1, int y1) + { + return ref F0(ref x1, ref y1); + } + } + unsafe class B2 : A + { + static void F2() + { + static ref int Local2(ref int x2, int y2) + { + return ref F0(ref x2, ref y2); + } + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeReleaseDll); + comp.VerifyDiagnostics( + // (15,39): warning CS9087: This returns a parameter by reference 'y1' but it is not a ref parameter + // return ref F0(ref x1, ref y1); + Diagnostic(ErrorCode.WRN_RefReturnParameter, "y1").WithArguments("y1").WithLocation(15, 39), + // (24,39): warning CS9087: This returns a parameter by reference 'y2' but it is not a ref parameter + // return ref F0(ref x2, ref y2); + Diagnostic(ErrorCode.WRN_RefReturnParameter, "y2").WithArguments("y2").WithLocation(24, 39), + // (27,2): error CS1513: } expected + // } + Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(27, 2)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs index cb677d03b5b2f..73ca5a23f2b90 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocInitializerTests.cs @@ -657,12 +657,21 @@ static void Method1(int[] array) // (7,37): error CS0306: The type 'Span' may not be used as a type argument // var q1 = from item in array select stackalloc int[3] { 1, 2, 3 }; Diagnostic(ErrorCode.ERR_BadTypeArgument, "select stackalloc int[3] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(7, 37), + // (7,44): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // var q1 = from item in array select stackalloc int[3] { 1, 2, 3 }; + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[3] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(7, 44), // (8,37): error CS0306: The type 'Span' may not be used as a type argument // var q2 = from item in array select stackalloc int[ ] { 1, 2, 3 }; Diagnostic(ErrorCode.ERR_BadTypeArgument, "select stackalloc int[ ] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(8, 37), + // (8,44): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // var q2 = from item in array select stackalloc int[ ] { 1, 2, 3 }; + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[ ] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(8, 44), // (9,37): error CS0306: The type 'Span' may not be used as a type argument // var q3 = from item in array select stackalloc [ ] { 1, 2, 3 }; - Diagnostic(ErrorCode.ERR_BadTypeArgument, "select stackalloc [ ] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(9, 37) + Diagnostic(ErrorCode.ERR_BadTypeArgument, "select stackalloc [ ] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(9, 37), + // (9,44): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method + // var q3 = from item in array select stackalloc [ ] { 1, 2, 3 }; + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc [ ] { 1, 2, 3 }").WithArguments("System.Span").WithLocation(9, 44) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 6fd294b8f8934..260794f7b6585 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -3752,8 +3752,10 @@ public E2() { } Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "Field = stackalloc byte[512]").WithArguments("System.Span").WithLocation(9, 57)); } - [Fact] - public void FieldInitializer_EscapeAnalysis_04() + [Theory] + [InlineData(LanguageVersion.CSharp10)] + [InlineData(LanguageVersion.Latest)] + public void FieldInitializer_EscapeAnalysis_04(LanguageVersion languageVersion) { var source = @"using System; @@ -3765,19 +3767,7 @@ ref struct Example public Example() {} static Span F(D d) => d(); }"; - var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular10); - comp.VerifyDiagnostics( - // (5,31): error CS0188: The 'this' object cannot be used before all of its fields have been assigned. Consider updating to language version '11.0' to auto-default the unassigned fields. - // public Span Field = F(() => stackalloc byte[512]); - Diagnostic(ErrorCode.ERR_UseDefViolationThisUnsupportedVersion, "F").WithArguments("11.0").WithLocation(5, 31), - // (5,39): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // public Span Field = F(() => stackalloc byte[512]); - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc byte[512]").WithArguments("System.Span").WithLocation(5, 39), - // (6,51): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method - // public Span Property { get; } = F(() => stackalloc byte[512]); - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc byte[512]").WithArguments("System.Span").WithLocation(6, 51)); - - comp = CreateCompilationWithSpan(source); + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); comp.VerifyDiagnostics( // (5,39): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public Span Field = F(() => stackalloc byte[512]); diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs index 8390abf31e13d..0bcaadd943ada 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Rewriters/CapturedVariableRewriter.cs @@ -48,7 +48,7 @@ public override BoundNode VisitBlock(BoundBlock node) var rewrittenLocals = node.Locals.WhereAsArray((local, rewriter) => local.IsCompilerGenerated || local.Name == null || rewriter.GetVariable(local.Name) == null, this); var rewrittenLocalFunctions = node.LocalFunctions; var rewrittenStatements = VisitList(node.Statements); - return node.Update(rewrittenLocals, rewrittenLocalFunctions, rewrittenStatements); + return node.Update(rewrittenLocals, rewrittenLocalFunctions, node.HasUnsafeModifier, rewrittenStatements); } public override BoundNode VisitLocal(BoundLocal node) diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs index b43cf816751bb..58e44a4f71c26 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EELocalSymbolBase.cs @@ -84,18 +84,6 @@ internal sealed override UseSiteInfo GetUseSiteInfo() return result; } - /// - /// EE Symbols have no source symbols associated with them. - /// They should be safe to escape for evaluation purposes. - /// - internal override uint ValEscapeScope => Binder.CurrentMethodScope; - - /// - /// EE Symbols have no source symbols associated with them. - /// They should be safe to escape for evaluation purposes. - /// - internal override uint RefEscapeScope => Binder.CurrentMethodScope; - internal override DeclarationScope Scope => DeclarationScope.Unscoped; } } diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs index 827504ab30293..f1754a7398c0f 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs @@ -666,7 +666,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, localBuilder.Add(local); } - body = block.Update(localBuilder.ToImmutableAndFree(), block.LocalFunctions, block.Statements); + body = block.Update(localBuilder.ToImmutableAndFree(), block.LocalFunctions, block.HasUnsafeModifier, block.Statements); TypeParameterChecker.Check(body, _allTypeParameters); compilationState.AddSynthesizedMethod(this, body); } From 5391dc165e304d1195c5a19c1af1e2a6281fe3c8 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:41:14 +0000 Subject: [PATCH 139/274] Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20221221.3 (#66093) [main] Update dependencies from dotnet/roslyn-analyzers --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index cb75f5b24a50a..da0010fbc6e0c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -26,9 +26,9 @@ https://github.com/dotnet/arcade 57ba56de330e50f9012493b8ba24818e24ec7817 - + https://github.com/dotnet/roslyn-analyzers - 10632e6dfeb051173788f4e5cd616abeba6d9ff0 + 43348f5b4ca847a3d45f33f229356617f53fbfbf diff --git a/eng/Versions.props b/eng/Versions.props index 0be36054ee7ed..d3b03e7a034d3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,7 +20,7 @@ 3.3.4-beta1.22579.2 - 8.0.0-preview1.22619.1 + 8.0.0-preview1.22621.3 1.1.2-beta1.22512.1 0.1.149-beta From 9535591a00e2f4e2852619f2c9ab4f063ee93b84 Mon Sep 17 00:00:00 2001 From: Jason Imison Date: Wed, 21 Dec 2022 17:21:19 +0000 Subject: [PATCH 140/274] Fix typo in error message --- .../FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs index b1510a0680cb9..e8f4b149a2929 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfoCacheService.cs @@ -177,7 +177,7 @@ private async Task UpdateSourceSymbolTreeInfoAsync(Project project, Cancellation project, checksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum must match our checksum."); // Mark that we're up to date with this project. Future calls with the same semantic-version or // checksum can bail out immediately. @@ -199,7 +199,7 @@ private async Task UpdateReferenceAsync( project.Solution, reference, checksum, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(info); - Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum much match our checksum."); + Contract.ThrowIfTrue(info.Checksum != checksum, "If we computed a SymbolTreeInfo, then its checksum must match our checksum."); metadataInfo = new MetadataInfo(info, metadataInfo.ReferencingProjects ?? new HashSet()); _peReferenceToInfo[reference] = metadataInfo; From f86f36b3b50292f2f3105a2a3de523fd570d773f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 21 Dec 2022 21:10:11 -0600 Subject: [PATCH 141/274] Simplifies implementation of GlobalOptionService.SetOptions and SetGlobalOptions and clarifies the difference. (#66016) Next, we replace OptionKey with OptionKey2 in all Roslyn code that does not deal with OptionSet or other public APIs that expose options. Eventually we'll be able to ban OptionKey internally. We also push all translations between internal and public code style option values into SolutionOptionSet and remove them from GlobalOptionService. The former exposes the values through public API while the latter does not. After this change global options only operate with internal representations. A lot of mechanical changes are needed for this change - the most interesting files to review are: OptionSet.cs, SolutionOptionSet.cs, GlobalOptionService.cs, OptionsHelpers.cs, OptionKeyTest.cs and GlobalOptionServiceTests.cs. Adds a new integration test that enumerates all global option definitions in Roslyn assemblies and validates that each of these options can be updated in VS. This addresses test hole where we did not validate reading options from VS settings/registry. Moves code that updates options of the current workspace solution to LegacyWorkspaceOptionService as it only implements legacy behavior and only affects code reading from Solution.Options, which shouldn't be done in Roslyn. There is a slight semantic change: the GlobalOptionsService.OptionChanged event is now triggered before the workspaces registered to LegacyWorkspaceOptionService get their current solutions updated. This is ok since the event is an internal event, not observable by public consumers of Workspace APIs and Roslyn code does not read global options from Solution.Options anymore. Removes UnitTestingOptionChangedEventArgsWrapper - not used by Unit Testing anymore. --- .../CSharpTest/AddUsing/AddUsingNuGetTests.cs | 4 +- .../CloseBlockCommentTests.cs | 2 +- ...arpCompleteStatementCommandHandlerTests.cs | 2 +- .../LoadDirectiveCompletionProviderTests.cs | 2 +- .../PartialMethodCompletionProviderTests.cs | 4 +- ...ferenceDirectiveCompletionProviderTests.cs | 2 +- ...olCompletionProviderTests_NoInteractive.cs | 2 +- .../Completion/CompletionServiceTests.cs | 4 +- .../ConvertNamespaceCommandHandlerTests.cs | 2 +- .../EncapsulateFieldTestState.cs | 4 +- .../CSharpTest/Formatting/CodeCleanupTests.cs | 6 +- .../RazorLineFormattingOptionsTests.cs | 4 +- .../InlineDiagnosticsTaggerProviderTests.cs | 2 +- .../SplitStringLiteralCommandHandlerTests.cs | 2 +- .../Squiggles/ErrorSquiggleProducerTests.cs | 26 +- .../SuggestionTagProducerTests.cs | 3 +- .../WorkspaceTests_EditorFeatures.cs | 31 +- .../InlineHintsKeyProcessorProvider.cs | 2 +- .../UI/Adornment/RenameFlyoutViewModel.cs | 12 +- .../UI/Dashboard/RenameDashboardViewModel.cs | 10 +- .../AbstractAddImportsPasteCommandHandler.cs | 4 +- .../NamingStyles/SourceTextExtensions.cs | 4 +- .../Api/VSTypeScriptGlobalOptions.cs | 8 +- .../LegacyGlobalOptionsWorkspaceService.cs | 10 +- .../CodeActions/CodeFixVerifierHelper.cs | 18 +- .../CodeActions/SharedVerifierState.cs | 31 +- ...AbstractSplitCommentCommandHandlerTests.cs | 4 +- .../Test/Completion/CompletionServiceTests.cs | 4 +- .../DiagnosticAnalyzerServiceTests.cs | 22 +- ...osticsClassificationTaggerProviderTests.cs | 2 +- .../DiagnosticsSquiggleTaggerProviderTests.cs | 9 +- .../IDEDiagnosticIDConfigurationTests.cs | 4 +- .../Data/CodeStyleSettingsTest.cs | 9 - .../Test/Options/GlobalOptionsTests.cs | 97 ++----- .../SolutionCrawler/WorkCoordinatorTests.cs | 8 +- .../Test/Structure/StructureTaggerTests.cs | 32 +-- .../Diagnostics/DiagnosticProviderTests.vb | 2 +- .../Diagnostics/DiagnosticServiceTests.vb | 2 +- .../InlineHints/AbstractInlineHintsTests.vb | 2 +- .../CSharpCompletionCommandHandlerTests.vb | 206 ++++++-------- .../CSharpSignatureHelpCommandHandlerTests.vb | 2 +- .../IntelliSense/CompletionServiceTests.vb | 4 +- .../CompletionServiceTests_Exclusivitiy.vb | 2 +- ...isualBasicCompletionCommandHandlerTests.vb | 18 +- ...alBasicSignatureHelpCommandHandlerTests.vb | 2 +- .../AbstractKeywordHighlightingTests.vb | 2 +- .../AbstractReferenceHighlightingTests.vb | 2 +- .../Test2/Rename/InlineRenameTests.vb | 10 +- .../Test2/Rename/RenameCommandHandlerTests.vb | 2 +- .../Test2/Rename/RenameViewModelTests.vb | 10 +- .../AbstractCompletionProviderTests.cs | 2 +- .../Diagnostics/DiagnosticTaggerWrapper.cs | 4 +- .../Formatting/CoreFormatterTestsBase.cs | 2 +- .../TaskList/AbstractTaskListTests.cs | 2 +- .../Intellisense/TestStateFactory.vb | 10 +- .../AddImport/AddImportTests_NuGet.vb | 4 +- .../EndConstructCommandHandlerTests.vb | 3 +- .../EndConstructTestingHelpers.vb | 6 +- .../Formatting/CodeCleanUpTests.vb | 4 +- .../Indentation/SmartIndentProviderTests.vb | 2 +- .../ImplementInterfaceCommandHandlerTests.vb | 2 +- .../CommitOnMiscellaneousCommandsTests.vb | 6 +- .../Squiggles/ErrorSquiggleProducerTests.vb | 3 +- .../Configuration/ConfigurationUpdater.cs | 10 +- ...ConfigureCodeStyleOptionCodeFixProvider.cs | 2 +- .../Portable/Completion/CompletionContext.cs | 4 +- .../Portable/Completion/CompletionService.cs | 2 +- .../VSCode/API/VSCodeAnalyzerLoader.cs | 2 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 1 - .../Options/InternalDiagnosticsOptions.cs | 12 + .../Options/QuickInfoOptionsStorage.cs | 2 +- .../AbstractPullDiagnosticHandler.cs | 16 +- .../Completion/CompletionTests.cs | 7 +- .../AbstractPullDiagnosticTestsBase.cs | 12 +- .../Diagnostics/PullDiagnosticTests.cs | 8 +- .../VSTypeScriptHandlerTests.cs | 2 +- .../IncrementalAnalyzerRunner.cs | 4 +- .../FSharp/FSharpGlobalOptions.cs | 4 +- .../Razor/RazorGlobalOptions.cs | 22 +- .../Test/Options/OptionViewModelTests.cs | 2 +- .../ColorSchemeApplier.Settings.cs | 4 +- .../Pythia/PythiaGlobalOptions.cs | 4 +- .../StreamingFindUsagesPresenter.cs | 2 +- .../KeybindingResetDetector.cs | 18 +- .../Core/Def/Options/FeatureFlagPersister.cs | 4 +- .../LocalUserRegistryOptionPersister.cs | 4 +- .../Def/Options/PackageSettingsPersister.cs | 8 +- .../VisualStudioSettingsOptionPersister.cs | 30 +- ...alStudioSettingsOptionPersisterProvider.cs | 4 +- .../Impl/Options/AbstractAutomationObject.cs | 6 +- .../Core/Impl/Options/AbstractOptionPage.cs | 8 +- .../Core/Impl/Options/OptionStore.cs | 4 +- .../Core/Test.Next/Options/AllOptionsTests.cs | 17 ++ ...alStudioDiagnosticAnalyzerExecutorTests.cs | 6 +- .../CSharpCompletionSnippetNoteTests.vb | 4 +- .../ExternalDiagnosticUpdateSourceTests.vb | 2 +- .../BasicEditorConfigGeneratorTests.vb | 2 +- .../CSharpEditorConfigGeneratorTests.vb | 2 +- .../SnippetCompletionProviderTests.vb | 6 +- .../Core/Test/Snippets/SnippetTestState.vb | 4 +- .../CSharp/CSharpAddMissingUsingsOnPaste.cs | 6 +- .../CSharp/CSharpArgumentProvider.cs | 4 +- .../CSharp/CSharpGoToDefinition.cs | 4 +- .../CSharp/CSharpNavigationBar.cs | 4 +- .../CSharp/CSharpSourceGenerators.cs | 2 +- .../InProcess/InheritanceMarginInProcess.cs | 10 +- .../InProcess/StateResetInProcess.cs | 6 +- .../InProcess/WorkspaceInProcess.cs | 13 +- ...nguageServices.New.IntegrationTests.csproj | 2 +- .../Options/GlobalOptionsTest.cs | 68 +++++ .../VisualBasic/BasicArgumentProvider.cs | 4 +- .../VisualBasic/BasicGoToDefinition.cs | 6 +- .../VisualBasic/BasicLineCommit.cs | 2 +- .../VisualBasic/BasicNavigationBar.cs | 4 +- .../InProcess/VisualStudioWorkspace_InProc.cs | 15 +- .../TestUtilities/WellKnownGlobalOptions.cs | 10 +- ...estingIncrementalAnalyzerImplementation.cs | 3 - ...nitTestingOptionChangedEventArgsWrapper.cs | 18 -- .../Portable/Options/DocumentOptionSet.cs | 9 - .../EditorConfig/EditorConfigFileGenerator.cs | 3 +- .../Core/Portable/Options/EmptyOptionSet.cs | 23 ++ .../Portable/Options/GlobalOptionService.cs | 146 +++------- .../Portable/Options/IGlobalOptionService.cs | 41 ++- .../Options/ILegacyWorkspaceOptionService.cs | 15 +- .../Core/Portable/Options/IOptionPersister.cs | 4 +- .../Options/LegacyWorkspaceOptionService.cs | 123 ++++++++ .../Options/OptionChangedEventArgs.cs | 4 +- .../Core/Portable/Options/OptionKey.cs | 12 +- .../Portable/Options/OptionServiceFactory.cs | 33 --- .../OptionSet+AnalyzerConfigOptionsImpl.cs | 6 +- .../Core/Portable/Options/OptionSet.cs | 60 +--- .../Core/Portable/Options/OptionValueSet.cs | 29 -- .../Core/Portable/Options/OptionsHelpers.cs | 24 +- .../Portable/Options/SolutionOptionSet.cs | 39 ++- .../Core/Portable/Workspace/Workspace.cs | 13 +- .../CoreTest/Formatter/FormatterTests.cs | 4 +- .../CoreTest/Options/OptionKeyTests.cs | 14 + .../CoreTest/SolutionTests/SolutionTests.cs | 4 +- .../GlobalOptionServiceTests.cs | 266 ++++++++---------- .../CoreTestUtilities/Fakes/TestOptionSet.cs | 15 +- ...eAnalysis.Workspaces.Test.Utilities.csproj | 1 + .../Options/OptionsTestHelpers.cs | 62 +++- .../Options/OptionsTestInfo.cs | 140 +++++++++ .../CoreTestUtilities/Options/TestOption.cs | 19 ++ .../CoreTestUtilities/OptionsCollection.cs | 6 +- .../Formatting/CSharpFormattingOptions2.cs | 24 +- .../Core/CodeStyle/CodeStyleOption2`1.cs | 6 +- .../Options/ClientSettingsStorageLocation.cs | 6 +- .../EditorConfigStorageLocation`1.cs | 19 +- .../Compiler/Core/Options/OptionKey2.cs | 8 + 150 files changed, 1198 insertions(+), 1120 deletions(-) create mode 100644 src/VisualStudio/Core/Test.Next/Options/AllOptionsTests.cs create mode 100644 src/VisualStudio/IntegrationTest/New.IntegrationTests/Options/GlobalOptionsTest.cs delete mode 100644 src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingOptionChangedEventArgsWrapper.cs create mode 100644 src/Workspaces/Core/Portable/Options/EmptyOptionSet.cs create mode 100644 src/Workspaces/Core/Portable/Options/LegacyWorkspaceOptionService.cs delete mode 100644 src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs delete mode 100644 src/Workspaces/Core/Portable/Options/OptionValueSet.cs rename src/Workspaces/{CoreTest => CoreTestUtilities}/Options/OptionsTestHelpers.cs (68%) create mode 100644 src/Workspaces/CoreTestUtilities/Options/OptionsTestInfo.cs create mode 100644 src/Workspaces/CoreTestUtilities/Options/TestOption.cs diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs index 7e344a013b079..527c0ba53eed3 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingNuGetTests.cs @@ -36,8 +36,8 @@ public class AddUsingNuGetTests : AbstractAddUsingTests protected override void InitializeWorkspace(TestWorkspace workspace, TestParameters parameters) { - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SymbolSearchOptionsStorage.SearchNuGetPackages, LanguageNames.CSharp), true); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SymbolSearchOptionsStorage.SearchReferenceAssemblies, LanguageNames.CSharp), true); + workspace.GlobalOptions.SetGlobalOption(SymbolSearchOptionsStorage.SearchNuGetPackages, LanguageNames.CSharp, true); + workspace.GlobalOptions.SetGlobalOption(SymbolSearchOptionsStorage.SearchReferenceAssemblies, LanguageNames.CSharp, true); } internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer( diff --git a/src/EditorFeatures/CSharpTest/BlockCommentEditing/CloseBlockCommentTests.cs b/src/EditorFeatures/CSharpTest/BlockCommentEditing/CloseBlockCommentTests.cs index 2bfb574c1140d..931e28e391270 100644 --- a/src/EditorFeatures/CSharpTest/BlockCommentEditing/CloseBlockCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/BlockCommentEditing/CloseBlockCommentTests.cs @@ -285,7 +285,7 @@ public void NotClosedAfterAsteriskSpaceWithOptionOff() Verify(code, expected, workspace => { var globalOptions = workspace.GetService(); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AutoInsertBlockCommentStartString, LanguageNames.CSharp), false); + globalOptions.SetGlobalOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString, LanguageNames.CSharp, false); }); } diff --git a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs index 6ac2dcb966888..2fe3cbd45b84d 100644 --- a/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/CompleteStatement/CSharpCompleteStatementCommandHandlerTests.cs @@ -4240,7 +4240,7 @@ public int XValue setOptionsOpt: workspace => { var globalOptions = workspace.GetService(); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon), false); + globalOptions.SetGlobalOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, false); }); } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs index ae5391dc6fad0..36ca14e0a5b58 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/LoadDirectiveCompletionProviderTests.cs @@ -80,7 +80,7 @@ public void ShouldTriggerCompletion(string textWithPositionMarker, bool expected using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); var provider = workspace.ExportProvider.GetExports().Single(p => p.Metadata.Language == LanguageNames.CSharp && p.Metadata.Name == nameof(LoadDirectiveCompletionProvider)).Value; var languageServices = workspace.Services.GetLanguageServices(LanguageNames.CSharp); - Assert.Equal(expectedResult, provider.ShouldTriggerCompletion(languageServices.LanguageServices, SourceText.From(text), position, trigger: default, CompletionOptions.Default, OptionValueSet.Empty)); + Assert.Equal(expectedResult, provider.ShouldTriggerCompletion(languageServices.LanguageServices, SourceText.From(text), position, trigger: default, CompletionOptions.Default, OptionSet.Empty)); } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs index b44ab27d12f2d..cf6f2fadb3e96 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/PartialMethodCompletionProviderTests.cs @@ -781,7 +781,7 @@ public async Task ExpressionBodyMethod() var workspace = workspaceFixture.Target.GetWorkspace(GetComposition()); workspace.GlobalOptions.SetGlobalOption( - new OptionKey(CSharpCodeStyleOptions.PreferExpressionBodiedMethods), + CSharpCodeStyleOptions.PreferExpressionBodiedMethods, new CodeStyleOption2(ExpressionBodyPreference.WhenPossible, NotificationOption2.Silent)); var text = @"using System; @@ -812,7 +812,7 @@ public async Task ExpressionBodyMethodExtended() var workspace = workspaceFixture.Target.GetWorkspace(GetComposition()); workspace.GlobalOptions.SetGlobalOption( - new OptionKey(CSharpCodeStyleOptions.PreferExpressionBodiedMethods), + CSharpCodeStyleOptions.PreferExpressionBodiedMethods, new CodeStyleOption2(ExpressionBodyPreference.WhenPossible, NotificationOption2.Silent)); var text = @"using System; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs index 1da0bb07f6c39..29293e5b106d5 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ReferenceDirectiveCompletionProviderTests.cs @@ -117,7 +117,7 @@ public void ShouldTriggerCompletion(string textWithPositionMarker, bool expected using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); var provider = workspace.ExportProvider.GetExports().Single(p => p.Metadata.Language == LanguageNames.CSharp && p.Metadata.Name == nameof(ReferenceDirectiveCompletionProvider)).Value; var languageServices = workspace.Services.GetLanguageServices(LanguageNames.CSharp); - Assert.Equal(expectedResult, provider.ShouldTriggerCompletion(languageServices.LanguageServices, SourceText.From(text), position, trigger: default, CompletionOptions.Default, OptionValueSet.Empty)); + Assert.Equal(expectedResult, provider.ShouldTriggerCompletion(languageServices.LanguageServices, SourceText.From(text), position, trigger: default, CompletionOptions.Default, OptionSet.Empty)); } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs index dad3e5f510ac3..2f72384adc9d2 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs @@ -337,7 +337,7 @@ string Property var service = CompletionService.GetService(document); var options = CompletionOptions.Default; var displayOptions = SymbolDescriptionOptions.Default; - var completions = await service.GetCompletionsAsync(document, position, options, OptionValueSet.Empty); + var completions = await service.GetCompletionsAsync(document, position, options, OptionSet.Empty); var item = completions.ItemsList.First(i => i.DisplayText == "Beep"); var edit = testDocument.GetTextBuffer().CreateEdit(); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs index 29432880c9997..cf3b94c5d7761 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionServiceTests.cs @@ -112,7 +112,7 @@ public async Task PassThroughOptions1() .AddDocument("TestDocument.cs", text); var service = CompletionService.GetService(document); - var options = new OptionValueSet(ImmutableDictionary.Empty.Add(new OptionKey(ThirdPartyOption.Instance, LanguageNames.CSharp), 1)); + var options = new TestOptionSet(ImmutableDictionary.Empty.Add(new OptionKey(ThirdPartyOption.Instance, LanguageNames.CSharp), 1)); service.ShouldTriggerCompletion(text, 1, CompletionTrigger.Invoke, options: options); #pragma warning disable RS0030 // Do not used banned APIs @@ -187,7 +187,7 @@ public class C1 // We want to make sure import completion providers are also participating. var options = CompletionOptions.Default with { ShowItemsFromUnimportedNamespaces = true }; - var completionList = await completionService.GetCompletionsAsync(document, position.Value, options, OptionValueSet.Empty); + var completionList = await completionService.GetCompletionsAsync(document, position.Value, options, OptionSet.Empty); // We expect completion to run on frozen partial semantic, which won't run source generator. Assert.Equal(0, generatorRanCount); diff --git a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs index f475a3f686e5d..f70eaf4367484 100644 --- a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs @@ -85,7 +85,7 @@ class C } }"); - testState.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon), false); + testState.Workspace.GlobalOptions.SetGlobalOption(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, false); testState.SendTypeChar(';'); testState.AssertCodeIs( diff --git a/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldTestState.cs b/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldTestState.cs index 265554d58dc92..34650b34dd584 100644 --- a/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldTestState.cs +++ b/src/EditorFeatures/CSharpTest/EncapsulateField/EncapsulateFieldTestState.cs @@ -43,8 +43,8 @@ public static EncapsulateFieldTestState Create(string markup) { var workspace = TestWorkspace.CreateCSharp(markup, composition: EditorTestCompositions.EditorFeatures); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors), CSharpCodeStyleOptions.NeverWithSilentEnforcement); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(CSharpCodeStyleOptions.PreferExpressionBodiedProperties), CSharpCodeStyleOptions.NeverWithSilentEnforcement); + workspace.GlobalOptions.SetGlobalOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithSilentEnforcement); + workspace.GlobalOptions.SetGlobalOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithSilentEnforcement); return new EncapsulateFieldTestState(workspace); } diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index 1627fcd262374..01c7d93117042 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -816,9 +816,9 @@ private protected static async Task AssertCodeCleanupResult(string expected, str // must set global options since incremental analyzer infra reads from global options var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.CSharp), separateUsingGroups); - globalOptions.SetGlobalOption(new OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp), systemUsingsFirst); - globalOptions.SetGlobalOption(new OptionKey(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement), preferredImportPlacement); + globalOptions.SetGlobalOption(GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.CSharp, separateUsingGroups); + globalOptions.SetGlobalOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp, systemUsingsFirst); + globalOptions.SetGlobalOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement, preferredImportPlacement); var solution = workspace.CurrentSolution.WithAnalyzerReferences(new[] { diff --git a/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs b/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs index cd5f5e8c6ef8c..1e317dc4b7e04 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/RazorLineFormattingOptionsTests.cs @@ -41,8 +41,8 @@ public async Task FormatAsync() using var workspace = new AdhocWorkspace(hostServices); var globalOptions = ((IMefHostExportProvider)hostServices).GetExportedValue(); - globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.UseTabs), true); - globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.TabSize), 10); + globalOptions.SetGlobalOption(RazorLineFormattingOptionsStorage.UseTabs, true); + globalOptions.SetGlobalOption(RazorLineFormattingOptionsStorage.TabSize, 10); var project = workspace.AddProject("Test", LanguageNames.CSharp); diff --git a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs index 0b7921bae4d60..0f5a23e5520e4 100644 --- a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs @@ -57,7 +57,7 @@ private static async Task>> GetTag private static async Task>> GetTagSpansAsync(TestWorkspace workspace) { - workspace.GlobalOptions.SetGlobalOption(new OptionKey(InlineDiagnosticsOptions.EnableInlineDiagnostics, LanguageNames.CSharp), true); + workspace.GlobalOptions.SetGlobalOption(InlineDiagnosticsOptions.EnableInlineDiagnostics, LanguageNames.CSharp, true); return (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; } } diff --git a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs index 6d0f14c70e24d..f8a64853b53ac 100644 --- a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs @@ -67,7 +67,7 @@ private static void TestWorker( options.SetOptionValue(DefaultOptions.IndentStyleId, indentStyle.ToEditorIndentStyle()); // Remove once https://github.com/dotnet/roslyn/issues/62204 is fixed: - workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, document.Project.Language), indentStyle); + workspace.GlobalOptions.SetGlobalOption(IndentationOptionsStorage.SmartIndent, document.Project.Language, indentStyle); var originalSnapshot = textBuffer.CurrentSnapshot; var originalSelections = document.SelectedSpans; diff --git a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs index fb45cf7efa924..2c35211e39d15 100644 --- a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs +++ b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs @@ -81,8 +81,7 @@ void Test() "; using var workspace = TestWorkspace.Create(workspaceXml); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); var spans = (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; @@ -118,11 +117,10 @@ void Test() using var workspace = TestWorkspace.Create(workspaceXml, composition: SquiggleUtilities.CompositionWithSolutionCrawler); var language = workspace.Projects.Single().Language; - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); workspace.GlobalOptions.SetGlobalOption( - new OptionKey(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, language), + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, language, new CodeStyleOption2(value: true, notification: NotificationOption2.Error)); var analyzerMap = new Dictionary> @@ -214,8 +212,7 @@ public async Task SemanticErrorReported(bool pull) { using var workspace = TestWorkspace.CreateCSharp("class C : Bar { }", composition: SquiggleUtilities.CompositionWithSolutionCrawler); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); var spans = await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace); @@ -240,8 +237,7 @@ public async Task SemanticErrorReported(bool pull) public async Task TestNoErrorsAfterDocumentRemoved(bool pull) { using var workspace = TestWorkspace.CreateCSharp("class"); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); using var wrapper = new DiagnosticTaggerWrapper(workspace); @@ -271,8 +267,7 @@ public async Task TestNoErrorsAfterDocumentRemoved(bool pull) public async Task TestNoErrorsAfterProjectRemoved(bool pull) { using var workspace = TestWorkspace.CreateCSharp("class"); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); using var wrapper = new DiagnosticTaggerWrapper(workspace); @@ -318,8 +313,7 @@ class Test "; using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); var document = workspace.Documents.First(); @@ -363,8 +357,7 @@ class Test "; using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); var document = workspace.Documents.First(); @@ -408,8 +401,7 @@ private static async Task>> GetTagSpansInSour private static async Task>> GetTagSpansAsync(TestWorkspace workspace, bool pull) { - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); return (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; } diff --git a/src/EditorFeatures/CSharpTest/SuggestionTags/SuggestionTagProducerTests.cs b/src/EditorFeatures/CSharpTest/SuggestionTags/SuggestionTagProducerTests.cs index 2fba35f95ec46..3612562abf6de 100644 --- a/src/EditorFeatures/CSharpTest/SuggestionTags/SuggestionTagProducerTests.cs +++ b/src/EditorFeatures/CSharpTest/SuggestionTags/SuggestionTagProducerTests.cs @@ -44,8 +44,7 @@ void M() { string content, bool pull) { using var workspace = TestWorkspace.CreateCSharp(content); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); var analyzerMap = new Dictionary>() { diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 6513a6bedeabd..73464533e726e 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -1391,7 +1391,7 @@ public void TestSolutionWithOptions() using var workspace1 = CreateWorkspace(); var solution = workspace1.CurrentSolution; - var optionKey = new OptionKey2(FormattingOptions2.SmartIndent, LanguageNames.CSharp); + var optionKey = new OptionKey(FormattingOptions2.SmartIndent, LanguageNames.CSharp); var defaultValue = solution.Options.GetOption(optionKey); var changedValue = FormattingOptions.IndentStyle.Block; Assert.NotEqual(defaultValue, changedValue); @@ -1429,7 +1429,7 @@ public void TestOptionChangedHandlerInvokedAfterCurrentSolutionChanged() var beforeSolutionForPrimaryWorkspace = primaryWorkspace.CurrentSolution; var beforeSolutionForSecondaryWorkspace = secondaryWorkspace.CurrentSolution; - var optionKey = new OptionKey2(FormattingOptions2.SmartIndent, LanguageNames.CSharp); + var optionKey = new OptionKey(FormattingOptions2.SmartIndent, LanguageNames.CSharp); Assert.Equal(FormattingOptions2.IndentStyle.Smart, primaryWorkspace.Options.GetOption(optionKey)); Assert.Equal(FormattingOptions2.IndentStyle.Smart, secondaryWorkspace.Options.GetOption(optionKey)); @@ -1440,29 +1440,24 @@ public void TestOptionChangedHandlerInvokedAfterCurrentSolutionChanged() primaryWorkspace.Options = primaryWorkspace.Options.WithChangedOption(optionKey, FormattingOptions2.IndentStyle.Block); // Verify current solution and option change for both workspaces. - VerifyCurrentSolutionAndOptionChange(primaryWorkspace, beforeSolutionForPrimaryWorkspace); - VerifyCurrentSolutionAndOptionChange(secondaryWorkspace, beforeSolutionForSecondaryWorkspace); + Assert.NotEqual(beforeSolutionForPrimaryWorkspace, primaryWorkspace.CurrentSolution); + Assert.NotEqual(beforeSolutionForSecondaryWorkspace, secondaryWorkspace.CurrentSolution); + + Assert.Equal(FormattingOptions2.IndentStyle.Block, primaryWorkspace.Options.GetOption(optionKey)); + Assert.Equal(FormattingOptions2.IndentStyle.Block, secondaryWorkspace.Options.GetOption(optionKey)); primaryWorkspace.GlobalOptions.OptionChanged -= OptionService_OptionChanged; return; void OptionService_OptionChanged(object sender, OptionChangedEventArgs e) { - // Verify current solution and option change for both workspaces. - VerifyCurrentSolutionAndOptionChange(primaryWorkspace, beforeSolutionForPrimaryWorkspace); - VerifyCurrentSolutionAndOptionChange(secondaryWorkspace, beforeSolutionForSecondaryWorkspace); - } + // CurrentSolution has been updated when the event fires. - static void VerifyCurrentSolutionAndOptionChange(Workspace workspace, Solution beforeOptionChangedSolution) - { - // Verify that workspace.CurrentSolution has been updated with a new solution instance with changed option. - var currentSolution = workspace.CurrentSolution; - Assert.NotEqual(beforeOptionChangedSolution, currentSolution); - - // Verify workspace.CurrentSolution has changed option. - var optionKey = new OptionKey2(FormattingOptions2.SmartIndent, LanguageNames.CSharp); - Assert.Equal(FormattingOptions2.IndentStyle.Smart, beforeOptionChangedSolution.Options.GetOption(optionKey)); - Assert.Equal(FormattingOptions2.IndentStyle.Block, currentSolution.Options.GetOption(optionKey)); + Assert.NotSame(beforeSolutionForPrimaryWorkspace, primaryWorkspace.CurrentSolution); + Assert.NotSame(beforeSolutionForSecondaryWorkspace, secondaryWorkspace.CurrentSolution); + + Assert.Equal(FormattingOptions2.IndentStyle.Block, primaryWorkspace.Options.GetOption(optionKey)); + Assert.Equal(FormattingOptions2.IndentStyle.Block, secondaryWorkspace.Options.GetOption(optionKey)); } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsKeyProcessorProvider.cs b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsKeyProcessorProvider.cs index 6829d8a1c7a21..23cffcfdf0fc2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsKeyProcessorProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineHints/InlineHintsKeyProcessorProvider.cs @@ -116,7 +116,7 @@ private void Toggle(bool on) // We can only enter the on-state if the user has the chord feature enabled. We can always enter the // off state though. on = on && _globalOptions.GetOption(InlineHintsViewOptions.DisplayAllHintsWhilePressingAltF1); - _globalOptions.RefreshOption(new OptionKey(InlineHintsGlobalStateOption.DisplayAllOverride), on); + _globalOptions.RefreshOption(new OptionKey2(InlineHintsGlobalStateOption.DisplayAllOverride), on); } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyoutViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyoutViewModel.cs index fa7f66223e9a6..6b569adc84d5e 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyoutViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyoutViewModel.cs @@ -113,7 +113,7 @@ public bool RenameInCommentsFlag get => _session.Options.RenameInComments; set { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameInComments), value); + _globalOptionService.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameInComments = value }); } } @@ -123,7 +123,7 @@ public bool RenameInStringsFlag get => _session.Options.RenameInStrings; set { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameInStrings), value); + _globalOptionService.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameInStrings = value }); } } @@ -133,7 +133,7 @@ public bool RenameFileFlag get => _session.Options.RenameFile; set { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameFile), value); + _globalOptionService.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameFile = value }); } } @@ -143,7 +143,7 @@ public bool PreviewChangesFlag get => _session.PreviewChanges; set { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.PreviewChanges), value); + _globalOptionService.SetGlobalOption(InlineRenameSessionOptionsStorage.PreviewChanges, value); _session.SetPreviewChanges(value); } } @@ -153,7 +153,7 @@ public bool RenameOverloadsFlag get => _session.Options.RenameOverloads; set { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameOverloads), value); + _globalOptionService.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameOverloads, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameOverloads = value }); } } @@ -165,7 +165,7 @@ public bool IsCollapsed { if (value != IsCollapsed) { - _globalOptionService.SetGlobalOption(new OptionKey(InlineRenameUIOptions.CollapseUI), value); + _globalOptionService.SetGlobalOption(InlineRenameUIOptions.CollapseUI, value); NotifyPropertyChanged(nameof(IsCollapsed)); NotifyPropertyChanged(nameof(IsExpanded)); } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboardViewModel.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboardViewModel.cs index 67bbcc63386c9..68ff29813023a 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboardViewModel.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboardViewModel.cs @@ -241,7 +241,7 @@ public bool DefaultRenameOverloadFlag { if (IsRenameOverloadsEditable) { - _session.RenameService.GlobalOptions.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameOverloads), value); + _session.RenameService.GlobalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameOverloads, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameOverloads = value }); } } @@ -253,7 +253,7 @@ public bool DefaultRenameInStringsFlag set { - _session.RenameService.GlobalOptions.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameInStrings), value); + _session.RenameService.GlobalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameInStrings = value }); } } @@ -264,7 +264,7 @@ public bool DefaultRenameInCommentsFlag set { - _session.RenameService.GlobalOptions.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameInComments), value); + _session.RenameService.GlobalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameInComments = value }); } } @@ -274,7 +274,7 @@ public bool DefaultRenameFileFlag get => _session.Options.RenameFile; set { - _session.RenameService.GlobalOptions.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.RenameFile), value); + _session.RenameService.GlobalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, value); _session.RefreshRenameSessionWithOptionsChanged(_session.Options with { RenameFile = value }); } } @@ -285,7 +285,7 @@ public bool DefaultPreviewChangesFlag set { - _session.RenameService.GlobalOptions.SetGlobalOption(new OptionKey(InlineRenameSessionOptionsStorage.PreviewChanges), value); + _session.RenameService.GlobalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.PreviewChanges, value); _session.SetPreviewChanges(value); } } diff --git a/src/EditorFeatures/Core/AddImports/AbstractAddImportsPasteCommandHandler.cs b/src/EditorFeatures/Core/AddImports/AbstractAddImportsPasteCommandHandler.cs index 271cf6e61ab14..7212f9f4dc28d 100644 --- a/src/EditorFeatures/Core/AddImports/AbstractAddImportsPasteCommandHandler.cs +++ b/src/EditorFeatures/Core/AddImports/AbstractAddImportsPasteCommandHandler.cs @@ -56,8 +56,10 @@ public CommandState GetCommandState(PasteCommandArgs args, Func ne public void ExecuteCommand(PasteCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) { + var language = args.SubjectBuffer.GetLanguageName(); + // If the feature is not explicitly enabled we can exit early - if (!_globalOptions.GetOption(FeatureOnOffOptions.AddImportsOnPaste, args.SubjectBuffer.GetLanguageName())) + if (language is null || !_globalOptions.GetOption(FeatureOnOffOptions.AddImportsOnPaste, language)) { nextCommandHandler(); return; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Updater/NamingStyles/SourceTextExtensions.cs b/src/EditorFeatures/Core/EditorConfigSettings/Updater/NamingStyles/SourceTextExtensions.cs index f259cfd32e0b7..cf376ce90da21 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Updater/NamingStyles/SourceTextExtensions.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Updater/NamingStyles/SourceTextExtensions.cs @@ -81,8 +81,8 @@ static SourceText WithChanges(SourceText sourceText, TextSpan span, string newTe private static (IEnumerable Common, IEnumerable CSharp, IEnumerable VisualBasic) GetPreferencesForAllLanguages(OptionSet options) { - var csharpNamingStylePreferences = options.GetOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp); - var vbNamingStylePreferences = options.GetOption(NamingStyleOptions.NamingPreferences, LanguageNames.VisualBasic); + var csharpNamingStylePreferences = options.GetOption((PerLanguageOption)NamingStyleOptions.NamingPreferences, LanguageNames.CSharp); + var vbNamingStylePreferences = options.GetOption((PerLanguageOption)NamingStyleOptions.NamingPreferences, LanguageNames.VisualBasic); var commonOptions = GetCommonOptions(csharpNamingStylePreferences, vbNamingStylePreferences); var csharpOnlyOptions = GetOptionsUniqueOptions(csharpNamingStylePreferences, commonOptions); diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs index be1927b8062de..2e07016985570 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/Api/VSTypeScriptGlobalOptions.cs @@ -26,17 +26,15 @@ public VSTypeScriptGlobalOptions(IGlobalOptionService globalOptions) public bool BlockForCompletionItems { get => _globalOptions.GetOption(CompletionViewOptions.BlockForCompletionItems, InternalLanguageNames.TypeScript); - set => _globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.BlockForCompletionItems, InternalLanguageNames.TypeScript), value); + set => _globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, InternalLanguageNames.TypeScript, value); } public void SetBackgroundAnalysisScope(bool openFilesOnly) { - _globalOptions.SetGlobalOption( - new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), + _globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript, openFilesOnly ? BackgroundAnalysisScope.OpenFiles : BackgroundAnalysisScope.FullSolution); - _globalOptions.SetGlobalOption( - new OptionKey(SolutionCrawlerOptionsStorage.RemoveDocumentDiagnosticsOnDocumentClose, InternalLanguageNames.TypeScript), + _globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.RemoveDocumentDiagnosticsOnDocumentClose, InternalLanguageNames.TypeScript, openFilesOnly); } diff --git a/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs b/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs index ebd98a223530e..73ba8d51761cc 100644 --- a/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs +++ b/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs @@ -60,7 +60,7 @@ public LegacyGlobalOptionsWorkspaceService(IGlobalOptionService globalOptions) public bool GenerateOverrides { get => _globalOptions.GetOption(s_generateOverridesOption); - set => _globalOptions.SetGlobalOption(new OptionKey(s_generateOverridesOption), value); + set => _globalOptions.SetGlobalOption(s_generateOverridesOption, value); } public bool RazorUseTabs @@ -73,7 +73,7 @@ public int RazorTabSize public bool InlineHintsOptionsDisplayAllOverride { get => _globalOptions.GetOption(InlineHintsGlobalStateOption.DisplayAllOverride); - set => _globalOptions.SetGlobalOption(new OptionKey(InlineHintsGlobalStateOption.DisplayAllOverride), value); + set => _globalOptions.SetGlobalOption(InlineHintsGlobalStateOption.DisplayAllOverride, value); } public CleanCodeGenerationOptionsProvider CleanCodeGenerationOptionsProvider @@ -83,18 +83,18 @@ public bool GetGenerateEqualsAndGetHashCodeFromMembersGenerateOperators(string l => _globalOptions.GetOption(s_implementIEquatable, language); public void SetGenerateEqualsAndGetHashCodeFromMembersGenerateOperators(string language, bool value) - => _globalOptions.SetGlobalOption(new OptionKey(s_generateOperators, language), value); + => _globalOptions.SetGlobalOption(s_generateOperators, language, value); public bool GetGenerateEqualsAndGetHashCodeFromMembersImplementIEquatable(string language) => _globalOptions.GetOption(s_implementIEquatable, language); public void SetGenerateEqualsAndGetHashCodeFromMembersImplementIEquatable(string language, bool value) - => _globalOptions.SetGlobalOption(new OptionKey(s_implementIEquatable, language), value); + => _globalOptions.SetGlobalOption(s_implementIEquatable, language, value); public bool GetGenerateConstructorFromMembersOptionsAddNullChecks(string language) => _globalOptions.GetOption(s_addNullChecks, language); public void SetGenerateConstructorFromMembersOptionsAddNullChecks(string language, bool value) - => _globalOptions.SetGlobalOption(new OptionKey(s_addNullChecks, language), value); + => _globalOptions.SetGlobalOption(s_addNullChecks, language, value); } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CodeFixVerifierHelper.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CodeFixVerifierHelper.cs index 26c9a0d630ca9..e115fcbed6b4d 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CodeFixVerifierHelper.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/CodeFixVerifierHelper.cs @@ -96,20 +96,17 @@ private static void VerifyMessageHelpLinkUri(DiagnosticAnalyzer analyzer) public static string? GetEditorConfigText(this OptionsCollection options) { - var (text, _) = ConvertOptionsToAnalyzerConfig(options.DefaultExtension, explicitEditorConfig: string.Empty, options); + var text = ConvertOptionsToAnalyzerConfig(options.DefaultExtension, explicitEditorConfig: string.Empty, options); return text?.ToString(); } - public static (SourceText? analyzerConfig, IEnumerable> options) ConvertOptionsToAnalyzerConfig(string defaultFileExtension, string? explicitEditorConfig, OptionsCollection options) + public static SourceText? ConvertOptionsToAnalyzerConfig(string defaultFileExtension, string? explicitEditorConfig, OptionsCollection options) { if (options.Count == 0) { - var result = explicitEditorConfig is object ? SourceText.From(explicitEditorConfig, Encoding.UTF8) : null; - return (result, options); + return explicitEditorConfig is object ? SourceText.From(explicitEditorConfig, Encoding.UTF8) : null; } - var remainingOptions = new List>(); - var analyzerConfig = new StringBuilder(); if (explicitEditorConfig is object) { @@ -131,18 +128,13 @@ public static (SourceText? analyzerConfig, IEnumerable().FirstOrDefault(); - if (editorConfigStorageLocation is null) - { - remainingOptions.Add(KeyValuePairUtil.Create(optionKey, value)); - continue; - } + var editorConfigStorageLocation = optionKey.Option.StorageLocations.OfType().Single(); var line = editorConfigStorageLocation.GetEditorConfigString(value); analyzerConfig.AppendLine(line); } - return (SourceText.From(analyzerConfig.ToString(), Encoding.UTF8), remainingOptions); + return SourceText.From(analyzerConfig.ToString(), Encoding.UTF8); } } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs index 4a9d69196d525..e70dc8060dcb1 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/CodeActions/SharedVerifierState.cs @@ -56,7 +56,7 @@ internal IdeAnalyzerOptions GetIdeAnalyzerOptions(Project project) #endif internal void Apply() { - var (analyzerConfigSource, remainingOptions) = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(_defaultFileExt, EditorConfig, Options); + var analyzerConfigSource = CodeFixVerifierHelper.ConvertOptionsToAnalyzerConfig(_defaultFileExt, EditorConfig, Options); if (analyzerConfigSource is not null) { if (_analyzerConfigIndex is null) @@ -76,34 +76,7 @@ internal void Apply() } var solutionTransformIndex = _remainingOptionsSolutionTransform is not null ? _test.SolutionTransforms.IndexOf(_remainingOptionsSolutionTransform) : -1; - if (remainingOptions is not null) - { - // Generate a new solution transform - _remainingOptionsSolutionTransform = (solution, projectId) => - { -#if !CODE_STYLE - var options = solution.Options; - foreach (var (key, value) in remainingOptions) - { - options = options.WithChangedOption(key, value); - } - - solution = solution.WithOptions(options); -#endif - - return solution; - }; - - if (solutionTransformIndex < 0) - { - _test.SolutionTransforms.Add(_remainingOptionsSolutionTransform); - } - else - { - _test.SolutionTransforms[solutionTransformIndex] = _remainingOptionsSolutionTransform; - } - } - else if (_remainingOptionsSolutionTransform is not null) + if (_remainingOptionsSolutionTransform is not null) { _test.SolutionTransforms.Remove(_remainingOptionsSolutionTransform); _remainingOptionsSolutionTransform = null; diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs b/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs index eb72a8da16e09..db6086559579e 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs @@ -55,8 +55,8 @@ private void TestWorker( var globalOptions = workspace.GlobalOptions; var language = workspace.Projects.Single().Language; - globalOptions.SetGlobalOption(new OptionKey(SplitCommentOptions.Enabled, language), enabled); - globalOptions.SetGlobalOption(new OptionKey(FormattingOptions2.UseTabs, language), useTabs); + globalOptions.SetGlobalOption(SplitCommentOptions.Enabled, language, enabled); + globalOptions.SetGlobalOption(FormattingOptions2.UseTabs, language, useTabs); var document = workspace.Documents.Single(); var view = document.GetTextView(); diff --git a/src/EditorFeatures/Test/Completion/CompletionServiceTests.cs b/src/EditorFeatures/Test/Completion/CompletionServiceTests.cs index c6b92f3d7317a..09c6dcf78df0d 100644 --- a/src/EditorFeatures/Test/Completion/CompletionServiceTests.cs +++ b/src/EditorFeatures/Test/Completion/CompletionServiceTests.cs @@ -46,7 +46,7 @@ void Method() { var document = project.Documents.Single(); var caretPosition = workspace.DocumentWithCursor.CursorPosition ?? throw new InvalidOperationException(); - var completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionValueSet.Empty); + var completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionSet.Empty); // NuGet providers are not included until it's loaded and cached, this is to avoid potential delays, especially on UI thread. Assert.Empty(completions.ItemsList); @@ -55,7 +55,7 @@ void Method() { var waiter = workspace.ExportProvider.GetExportedValue().GetWaiter(FeatureAttribute.CompletionSet); await waiter.ExpeditedWaitAsync(); - completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionValueSet.Empty); + completions = await completionService.GetCompletionsAsync(document, caretPosition, CompletionOptions.Default, OptionSet.Empty); Assert.NotEmpty(completions.ItemsList); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index b47a4064b3672..82a5eb7b74b49 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -102,7 +102,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalseFSAOn() var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new Analyzer())); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); var document = GetDocumentFromIncompleteProject(workspace); @@ -151,7 +151,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalseWithCompilerAnalyzerFSAOn() var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new CSharpCompilerDiagnosticAnalyzer())); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); @@ -170,7 +170,7 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(new DisabledByDefaultAnalyzer())); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); @@ -469,7 +469,7 @@ public async Task TestHostAnalyzerErrorNotLeaking() new LeakDocumentAnalyzer(), new LeakProjectAnalyzer())); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); workspace.TryApplyChanges(solution.WithAnalyzerReferences(new[] { analyzerReference })); @@ -564,7 +564,7 @@ private static AdhocWorkspace CreateWorkspaceWithProjectAndAnalyzer(DiagnosticAn var workspace = CreateWorkspace(); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); var projectId = ProjectId.CreateNewId(); var solution = workspace.CurrentSolution; @@ -627,7 +627,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool using var workspace = CreateWorkspace(); var globalOptions = GetGlobalOptions(workspace); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), analysisScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "CSharpProject", "CSharpProject", LanguageNames.CSharp); var project = workspace.AddProject(projectInfo); @@ -751,7 +751,7 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS using var workspace = TestWorkspace.CreateCSharp("class A {}", composition: s_editorFeaturesCompositionWithMockDiagnosticUpdateSourceRegistrationService.AddParts(typeof(TestDocumentTrackingService))); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), analysisScope); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); @@ -876,11 +876,11 @@ void M() using var workspace = new TestWorkspace(composition); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), analysisScope); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles), isSourceGenerated); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, isSourceGenerated); var compilerDiagnosticsScope = analysisScope.ToEquivalentCompilerDiagnosticsScope(); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp), compilerDiagnosticsScope); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp, compilerDiagnosticsScope); workspace.InitializeDocuments(TestWorkspace.CreateWorkspaceElement(LanguageNames.CSharp, files: files, sourceGeneratedFiles: sourceGeneratedFiles), openDocuments: false); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); @@ -1152,7 +1152,7 @@ internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis) if (fullSolutionAnalysis) { - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); } else { diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticsClassificationTaggerProviderTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticsClassificationTaggerProviderTests.cs index fada59b76e859..0e5013cef7257 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticsClassificationTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticsClassificationTaggerProviderTests.cs @@ -134,7 +134,7 @@ public async Task Test_FadingOptions(string diagnosticId, bool fadingOptionValue // Set fading option var fadingOption = GetFadingOptionForDiagnostic(diagnosticId); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(fadingOption, LanguageNames.CSharp), fadingOptionValue); + workspace.GlobalOptions.SetGlobalOption(fadingOption, LanguageNames.CSharp, fadingOptionValue); // Add mapping from diagnostic ID to fading option IDEDiagnosticIdToOptionMappingHelper.AddFadingOptionMapping(diagnosticId, fadingOption); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs index 64c82ca8f9cf0..055fee910a8e5 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs @@ -46,8 +46,7 @@ public async Task Test_TagSourceDiffer(bool pull) }; using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A { }", "class E { }" }, parseOptions: CSharpParseOptions.Default); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); using var wrapper = new DiagnosticTaggerWrapper(workspace, analyzerMap); @@ -79,8 +78,7 @@ public async Task Test_TagSourceDiffer(bool pull) public async Task MultipleTaggersAndDispose(bool pull) { using var workspace = TestWorkspace.CreateCSharp(new string[] { "class A {" }, parseOptions: CSharpParseOptions.Default); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); using var wrapper = new DiagnosticTaggerWrapper(workspace); @@ -104,8 +102,7 @@ public async Task MultipleTaggersAndDispose(bool pull) public async Task TaggerProviderCreatedAfterInitialDiagnosticsReported(bool pull) { using var workspace = TestWorkspace.CreateCSharp(new string[] { "class C {" }, parseOptions: CSharpParseOptions.Default); - workspace.GlobalOptions.SetGlobalOption( - new OptionKey(DiagnosticTaggingOptions.PullDiagnosticTagging), pull); + workspace.GlobalOptions.SetGlobalOption(DiagnosticTaggingOptions.PullDiagnosticTagging, pull); using var wrapper = new DiagnosticTaggerWrapper(workspace, analyzerMap: null, createTaggerProvider: false); // First, make sure all diagnostics have been reported. diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 0e85703a11b2d..a39afc72e2450 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -664,7 +664,6 @@ public void VisualBasic_VerifyIDEDiagnosticSeveritiesAreConfigurable() private static void VerifyConfigureCodeStyleOptionsCore(string expected, string languageName) { using var workspace = new TestWorkspace(); - var optionSet = workspace.Options; var diagnosticIdAndOptions = GetIDEDiagnosticIdsAndOptions(languageName); var expectedMap = GetExpectedMap(expected, out var expectedLines); @@ -681,8 +680,7 @@ private static void VerifyConfigureCodeStyleOptionsCore(string expected, string continue; } - var optionKey = new OptionKey(option, option.IsPerLanguage ? languageName : null); - var editorConfigString = editorConfigLocation.GetEditorConfigString(optionKey, optionSet); + var editorConfigString = editorConfigLocation.GetEditorConfigString(option.DefaultValue); ProcessDiagnosticIdAndOption(diagnosticId, option, editorConfigString); hasEditorConfigCodeStyleOptions = true; diff --git a/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs b/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs index e71541af7164a..3abf0b4002fae 100644 --- a/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs +++ b/src/EditorFeatures/Test/EditorConfigSettings/Data/CodeStyleSettingsTest.cs @@ -97,14 +97,5 @@ public TestAnalyzerConfigOptions((string, string)[]? options = null) public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => _dictionary.TryGetValue(key, out value); } - - private class TestOptionSet : OptionSet - { - private readonly object? _value; - public TestOptionSet(CodeStyleOption2 value) => _value = value; - public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) => this; - internal override IEnumerable GetChangedOptions(OptionSet optionSet) => Array.Empty(); - private protected override object? GetOptionCore(OptionKey optionKey) => _value; - } } } diff --git a/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs b/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs index 76ab16db3ae76..e9903edb29fd5 100644 --- a/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs +++ b/src/EditorFeatures/Test/Options/GlobalOptionsTests.cs @@ -43,7 +43,7 @@ public class GlobalOptionsTests [Export(typeof(IGlobalOptionService)), Shared, PartNotDiscoverable] internal class TestGlobalOptions : IGlobalOptionService { - public readonly List AccessedOptionKeys = new(); + public readonly List AccessedOptionKeys = new(); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -51,112 +51,51 @@ public TestGlobalOptions() { } - private void OnOptionAccessed(OptionKey key) + private void OnOptionAccessed(OptionKey2 key) { AccessedOptionKeys.Add(key); } public T GetOption(Option2 option) { - OnOptionAccessed(new OptionKey(option)); - return (T)GetNonEqualValue(typeof(T), option.DefaultValue); + OnOptionAccessed(new OptionKey2(option)); + return (T)OptionsTestHelpers.GetDifferentValue(typeof(T), option.DefaultValue)!; } - public T GetOption(PerLanguageOption2 option, string? languageName) + public T GetOption(PerLanguageOption2 option, string languageName) { - OnOptionAccessed(new OptionKey(option, languageName)); - return (T)GetNonEqualValue(typeof(T), option.DefaultValue); + OnOptionAccessed(new OptionKey2(option, languageName)); + return (T)OptionsTestHelpers.GetDifferentValue(typeof(T), option.DefaultValue)!; } - public object? GetOption(OptionKey optionKey) + public T GetOption(OptionKey2 optionKey) => throw new NotImplementedException(); #region Unused - public void RegisterWorkspace(Workspace workspace) - { - } - - public void UnregisterWorkspace(Workspace workspace) - { - } - #pragma warning disable CS0067 public event EventHandler? OptionChanged; #pragma warning restore - public ImmutableArray GetOptions(ImmutableArray optionKeys) + public ImmutableArray GetOptions(ImmutableArray optionKeys) => throw new NotImplementedException(); - public void RefreshOption(OptionKey optionKey, object? newValue) + public bool RefreshOption(OptionKey2 optionKey, object? newValue) => throw new NotImplementedException(); - public void SetGlobalOption(OptionKey optionKey, object? value) + public void SetGlobalOption(Option2 option, T value) => throw new NotImplementedException(); - public void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values) + public void SetGlobalOption(PerLanguageOption2 option, string language, T value) => throw new NotImplementedException(); - public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) + public void SetGlobalOption(OptionKey2 optionKey, object? value) => throw new NotImplementedException(); - #endregion - } - - /// - /// True if the type is a type of an option value. - /// - private static bool IsOptionValueType(Type type) - { - type = GetNonNullableType(type); - - return - type == typeof(bool) || - type == typeof(int) || - type == typeof(string) || - type.IsEnum || - type == typeof(NamingStylePreferences) || - typeof(ICodeStyleOption).IsAssignableFrom(type); - } - - private static Type GetNonNullableType(Type type) - => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ? type.GetGenericArguments()[0] : type; - - /// - /// Returns another value of the same type that's not equal to the specified . - /// - private static object GetNonEqualValue(Type type, object? value) - { - Assert.True(IsOptionValueType(type)); - - switch (value) - { - case bool b: - return !b; - - case int i: - return i == 0 ? 1 : 0; - - case string s: - return "!" + s; - - case ICodeStyleOption codeStyle: - return codeStyle - .WithValue(GetNonEqualValue(codeStyle.GetType().GetGenericArguments()[0], codeStyle.Value)) - .WithNotification((codeStyle.Notification == NotificationOption2.Error) ? NotificationOption2.Warning : NotificationOption2.Error); - - case NamingStylePreferences naming: - return naming.IsEmpty ? NamingStylePreferences.Default : NamingStylePreferences.Empty; - - default: - if (value != null && type.IsEnum) - { - var zero = Enum.ToObject(type, 0); - return value.Equals(zero) ? Enum.ToObject(type, 1) : zero; - } + public bool SetGlobalOptions(ImmutableArray> options) + => throw new NotImplementedException(); - throw TestExceptionUtilities.UnexpectedValue(value); - } + #endregion } private static void VerifyDataMembersHaveNonDefaultValues(object options, object defaultOptions, string language) @@ -176,7 +115,7 @@ static void Recurse(Type type, object options, object defaultOptions, string lan // default value for the option -- may be different then default(T): var defaultValue = property.GetValue(defaultOptions); - if (IsOptionValueType(property.PropertyType)) + if (OptionsTestHelpers.IsOptionValueType(property.PropertyType)) { if (IsStoredInGlobalOptions(property, language)) { @@ -185,7 +124,7 @@ static void Recurse(Type type, object options, object defaultOptions, string lan } else { - var propertyType = GetNonNullableType(property.PropertyType); + var propertyType = OptionsTestHelpers.GetNonNullableType(property.PropertyType); if (propertyType != property.PropertyType) { diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index f5f9a52cb11a7..fec3da960ee52 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -481,7 +481,7 @@ public async Task Test_NeedsReanalysisOnOptionChanged() workspace.OnSolutionAdded(solutionInfo); await WaitWaiterAsync(workspace.ExportProvider); - var worker = await ExecuteOperation(workspace, w => w.TryApplyChanges(w.CurrentSolution.WithOptions(w.CurrentSolution.Options.WithChangedOption(Analyzer.TestOption, false)))); + var worker = await ExecuteOperation(workspace, w => w.GlobalOptions.SetGlobalOption(Analyzer.TestOption, false)); Assert.Equal(10, worker.SyntaxDocumentIds.Count); Assert.Equal(10, worker.DocumentIds.Count); @@ -500,7 +500,7 @@ public async Task Test_BackgroundAnalysisScopeOptionChanged_OpenFiles() Assert.Equal(BackgroundAnalysisScope.ActiveFile, workspace.GlobalOptions.GetBackgroundAnalysisScope(LanguageNames.CSharp)); var newAnalysisScope = BackgroundAnalysisScope.OpenFiles; - var worker = await ExecuteOperation(workspace, w => w.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), newAnalysisScope)); + var worker = await ExecuteOperation(workspace, w => w.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, newAnalysisScope)); Assert.Equal(newAnalysisScope, workspace.GlobalOptions.GetBackgroundAnalysisScope(LanguageNames.CSharp)); Assert.Equal(10, worker.SyntaxDocumentIds.Count); @@ -519,7 +519,7 @@ public async Task Test_BackgroundAnalysisScopeOptionChanged_FullSolution() Assert.Equal(BackgroundAnalysisScope.ActiveFile, workspace.GlobalOptions.GetBackgroundAnalysisScope(LanguageNames.CSharp)); var newAnalysisScope = BackgroundAnalysisScope.FullSolution; - var worker = await ExecuteOperation(workspace, w => w.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), newAnalysisScope)); + var worker = await ExecuteOperation(workspace, w => w.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, newAnalysisScope)); Assert.Equal(newAnalysisScope, workspace.GlobalOptions.GetBackgroundAnalysisScope(LanguageNames.CSharp)); Assert.Equal(10, worker.SyntaxDocumentIds.Count); @@ -1704,7 +1704,7 @@ public static WorkCoordinatorWorkspace CreateWithAnalysisScope(BackgroundAnalysi var workspace = new WorkCoordinatorWorkspace(workspaceKind, disablePartialSolutions, incrementalAnalyzer); var globalOptions = workspace.Services.SolutionServices.ExportProvider.GetExportedValue(); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), analysisScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); return workspace; } diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index 2f84c615c20fa..69251dce7b485 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -56,9 +56,9 @@ static void Main(string[] args) using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp), collapseRegionsWhenCollapsingToDefinitions); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForDeclarationLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -122,9 +122,9 @@ public class Bar using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp), collapseRegionsWhenCollapsingToDefinitions); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForDeclarationLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -167,10 +167,10 @@ public class Bar using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp), collapseRegionsWhenCollapsingToDefinitions); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForDeclarationLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForCodeLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp), showBlockStructureGuidesForCommentsAndPreprocessorRegions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCommentsAndPreprocessorRegions, LanguageNames.CSharp, showBlockStructureGuidesForCommentsAndPreprocessorRegions); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -212,9 +212,9 @@ public class Bar using var workspace = TestWorkspace.CreateCSharp(code, composition: EditorTestCompositions.EditorFeaturesWpf); var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp), collapseRegionsWhenCollapsingToDefinitions); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForDeclarationLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp), showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.CSharp, collapseRegionsWhenCollapsingToDefinitions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForDeclarationLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.CSharp, showBlockStructureGuidesForCodeLevelConstructs); var tags = await GetTagsFromWorkspaceAsync(workspace); @@ -264,9 +264,9 @@ End Module using var workspace = TestWorkspace.CreateVisualBasic(code, composition: EditorTestCompositions.EditorFeaturesWpf); var globalOptions = workspace.GlobalOptions; - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic), collapseRegionsWhenCollapsingToDefinitions); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic), showBlockStructureGuidesForDeclarationLevelConstructs); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic), showBlockStructureGuidesForCodeLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseRegionsWhenCollapsingToDefinitions, LanguageNames.VisualBasic, collapseRegionsWhenCollapsingToDefinitions); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForDeclarationLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForDeclarationLevelConstructs); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.ShowBlockStructureGuidesForCodeLevelConstructs, LanguageNames.VisualBasic, showBlockStructureGuidesForCodeLevelConstructs); var tags = await GetTagsFromWorkspaceAsync(workspace); diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 4b7ae50ccfc70..8cdf29232fad2 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -260,7 +260,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_composition) ' Ensure that diagnostic service computes diagnostics for all open files, not just the active file (default mode) For Each language In workspace.Projects.Select(Function(p) p.Language).Distinct() - workspace.GlobalOptions.SetGlobalOption(New OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, language), BackgroundAnalysisScope.OpenFiles) + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, language, BackgroundAnalysisScope.OpenFiles) Next Dim registrationService = workspace.Services.GetService(Of ISolutionCrawlerRegistrationService)() diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 9e2c799397a16..83a17202847a2 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -2149,7 +2149,7 @@ class MyClass Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) - workspace.GlobalOptions.SetGlobalOption(New OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution) + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution) Dim solution = workspace.CurrentSolution Dim project = solution.Projects.Single() diff --git a/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb b/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb index 4cac10d5597da..4fd7459b32625 100644 --- a/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb +++ b/src/EditorFeatures/Test2/InlineHints/AbstractInlineHintsTests.vb @@ -77,7 +77,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints WpfTestRunner.RequireWpfFact($"{NameOf(AbstractInlineHintsTests)}.{NameOf(Me.VerifyTypeHints)} creates asynchronous taggers") Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(InlineHintsGlobalStateOption.DisplayAllOverride), ephemeral) + globalOptions.SetGlobalOption(InlineHintsGlobalStateOption.DisplayAllOverride, ephemeral) Dim options = New InlineTypeHintsOptions() With { diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index ff543c0efbd38..6c1dd1d61705a 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -112,8 +112,7 @@ namespace NS excludedTypes:=Nothing, extraExportedTypes:=Nothing, includeFormatCommandHandler:=False, workspaceKind:=Nothing) - State.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) + State.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp, showCompletionInArgumentLists) State.SendTypeChars("F") Await State.AssertCompletionItemsDoNotContainAny("FC") @@ -147,8 +146,7 @@ namespace NS excludedTypes:=Nothing, extraExportedTypes:=Nothing, includeFormatCommandHandler:=False, workspaceKind:=Nothing) - State.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) + State.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp, showCompletionInArgumentLists) State.SendTypeChars("F") Await State.AssertCompletionItemsDoNotContainAny("FC") @@ -895,8 +893,7 @@ class C , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendTypeChars("repl") state.SendTab() @@ -1146,7 +1143,7 @@ class Class1 }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.CSharp), EnterKeyRule.AfterFullyTypedWord) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.CSharp, EnterKeyRule.AfterFullyTypedWord) state.SendTypeChars("System.TimeSpan.FromMin") state.SendReturn() @@ -1174,8 +1171,7 @@ class Class1 }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.CSharp), EnterKeyRule.AfterFullyTypedWord) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.CSharp, EnterKeyRule.AfterFullyTypedWord) state.SendTypeChars("System.TimeSpan.FromMinutes") state.SendReturn() @@ -1518,8 +1514,7 @@ class Variable }]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertSelectedCompletionItem(displayText:="as", isSoftSelected:=True) @@ -1547,8 +1542,7 @@ class Variable }]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertSelectedCompletionItem(displayText:="as", isSoftSelected:=True) @@ -4105,8 +4099,7 @@ class$$ C }]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionSession() @@ -4135,8 +4128,7 @@ class Program }]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionSession() @@ -4829,8 +4821,7 @@ class Program ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) For Each c In "Offset" state.SendBackspace() @@ -4858,8 +4849,7 @@ class Program ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) For Each c In "Offset." state.SendBackspace() @@ -4984,8 +4974,7 @@ class C Dim completionService = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)() Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of BooleanTaskControlledCompletionProvider)().Single() - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, False) state.SendTypeChars("Sys.") Await state.AssertNoCompletionSession() @@ -5004,8 +4993,7 @@ class C extraExportedTypes:={GetType(CompletedTaskControlledCompletionProvider)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, False) state.SendTypeChars("Sys") Await state.AssertSelectedCompletionItem(displayText:="System") @@ -5037,8 +5025,7 @@ class C Dim completionService = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)() Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of BooleanTaskControlledCompletionProvider)().Single() - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, False) state.SendTypeChars("Sys") @@ -5115,7 +5102,7 @@ class C Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of BooleanTaskControlledCompletionProvider)().Single() Dim globalOptions = state.Workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), False) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, False) state.SendTypeChars("Sys") Dim task1 As Task = Nothing @@ -5206,7 +5193,7 @@ class C provider.Reset() ' Switch to the non-blocking mode - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), False) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, False) ' re-use of TestNoBlockOnCompletionItems1 state.SendTypeChars("Sys.") @@ -5226,7 +5213,7 @@ class C provider.Reset() ' Switch to the blocking mode - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), True) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, True) #Disable Warning BC42358 ' Because this call is not awaited, execution of the current method continues before the call is completed Task.Run(Function() @@ -5470,8 +5457,7 @@ class Program , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertSelectedCompletionItem(displayText:="Environment", isHardSelected:=True) @@ -5873,8 +5859,7 @@ class C } ) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendTypeChars("""") @@ -6121,8 +6106,7 @@ public class Program , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendInvokeCompletionList() state.SendBackspace() @@ -6146,8 +6130,7 @@ public class Program , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendInvokeCompletionList() state.SelectAndMoveCaret(-6) @@ -6195,8 +6178,7 @@ class C }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionItemsContainAll("WriteLine") @@ -6449,8 +6431,7 @@ class C , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionSession() @@ -6476,8 +6457,7 @@ class C , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionSession() @@ -6561,8 +6541,7 @@ class C Dim completionService = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)() Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of IntelliCodeMockProvider)().Single() - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertCompletionItemsContainAll("Normalize", "★ Normalize") @@ -6668,8 +6647,7 @@ class C Dim completionService = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)() Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of IntelliCodeMockProvider)().Single() - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendTypeChars(".nor") Await state.AssertCompletionItemsContainAll("Normalize", "★ Normalize") @@ -6719,8 +6697,7 @@ class C Dim completionService = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of CompletionService)() Dim provider = completionService.GetTestAccessor().GetImportedAndBuiltInProviders(ImmutableHashSet(Of String).Empty).OfType(Of IntelliCodeMockProvider)().Single() - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendTypeChars(".nor") Await state.AssertCompletionItemsContainAll("Normalize", "★ Normalize") @@ -6789,7 +6766,7 @@ namespace NS2 Dim completionService = document.GetLanguageService(Of CompletionService)() completionService.GetTestAccessor().SuppressPartialSemantics() - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Dim service = state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService(Of ITypeImportCompletionService)() @@ -6838,7 +6815,7 @@ namespace NS2 ) Dim document = state.Workspace.CurrentSolution.GetDocument(state.Workspace.Documents.Single(Function(d) d.Name = "C.cs").Id) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Dim completionService = document.GetLanguageService(Of CompletionService)() completionService.GetTestAccessor().SuppressPartialSemantics() @@ -6878,7 +6855,7 @@ namespace NS2 ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) ' trigger completion with import completion disabled Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -6937,8 +6914,8 @@ namespace NS2 ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) ' trigger completion with import completion enabled Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -6974,8 +6951,8 @@ namespace NS1 ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) ' trigger completion with import completion enabled Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7013,8 +6990,8 @@ public class AA }) state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7058,7 +7035,7 @@ public class AA } }) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7493,8 +7470,8 @@ namespace NS2 } " - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7559,8 +7536,8 @@ namespace NS2 } " - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7625,8 +7602,8 @@ namespace NS2 } " - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7691,8 +7668,8 @@ namespace NS2 } " - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7758,8 +7735,8 @@ namespace NS2 } " - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7794,8 +7771,8 @@ namespace OtherNS , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7883,8 +7860,8 @@ namespace NS2 , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) ' trigger completion with import completion disabled Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -7895,7 +7872,7 @@ namespace NS2 state.SendEscape() Await state.AssertNoCompletionSession() - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.SendTypeChars("mytask") Await state.WaitForAsynchronousOperationsAsync() @@ -7938,8 +7915,7 @@ namespace NS ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowNameSuggestions, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNameSuggestions, LanguageNames.CSharp, True) state.SendInvokeCompletionList() Await state.AssertCompletionItemsContainAll("foo123Bar", "foo123", "foo", "bar") @@ -7968,8 +7944,7 @@ namespace NS ]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowNameSuggestions, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNameSuggestions, LanguageNames.CSharp, True) state.SendInvokeCompletionList() Await state.AssertCompletionItemsContainAll("foo123", "foo") @@ -8442,8 +8417,8 @@ namespace B } } ) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.SendInvokeCompletionList() state.AssertItemsInOrder(New String() { @@ -8757,8 +8732,8 @@ public class AA }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Dim expectedText = $" using CC; @@ -8803,8 +8778,8 @@ public class AA } }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -8853,9 +8828,8 @@ public class AA }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -8907,9 +8881,8 @@ namespace Test , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.SendInvokeCompletionList() Await state.WaitForAsynchronousOperationsAsync() @@ -8958,9 +8931,8 @@ namespace MyNamespace , showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.SendInvokeCompletionList() Await state.WaitForAsynchronousOperationsAsync() @@ -9000,9 +8972,8 @@ public class AA }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -9039,9 +9010,8 @@ public class AA }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -9085,9 +9055,8 @@ namespace Bar1 }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -9131,9 +9100,8 @@ public unsafe class AA }, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) Await state.SendInvokeCompletionListAndWaitForUiRenderAsync() @@ -9494,8 +9462,7 @@ class C }]]>, showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendBackspace() Await state.AssertSelectedCompletionItem("xml", isSoftSelected:=True).ConfigureAwait(True) @@ -9549,8 +9516,7 @@ class Repro extraExportedTypes:={GetType(PreselectionProvider)}.ToList(), showCompletionInArgumentLists:=showCompletionInArgumentLists) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnDeletion, LanguageNames.CSharp, True) state.SendInvokeCompletionList() Await state.AssertCompletionItemsContainAll({"★ length", "length", "Length"}) @@ -10200,8 +10166,8 @@ class C Dim workspace = state.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, True) @@ -10255,8 +10221,8 @@ class C Dim workspace = state.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, True) @@ -10308,9 +10274,9 @@ class C Dim workspace = state.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), True) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation), True) + globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.CSharp, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ForceExpandedCompletionIndexCreation, True) state.TextView.Options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, True) @@ -10625,7 +10591,7 @@ class MyClass } ) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp), True) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp, True) state.SendTypeChars("if") Await state.AssertSelectedCompletionItem(displayText:="if", inlineDescription:=Nothing, isHardSelected:=True) state.SendDownKey() @@ -10699,7 +10665,7 @@ class MyClass } ) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp, False) state.SendInvokeCompletionList() ' We should still work normally w/o pythia recommender Await state.AssertCompletionItemsContainAll("argumentException", "exception") @@ -10722,7 +10688,7 @@ class MyClass , extraExportedTypes:={GetType(TestPythiaDeclarationNameRecommenderImplmentation)}.ToList()) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp, False) state.SendInvokeCompletionList() Dim computedItems = (Await state.GetCompletionSession()).GetComputedItems(CancellationToken.None) diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpSignatureHelpCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpSignatureHelpCommandHandlerTests.vb index 8b2105f977114..1739af2be3bad 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpSignatureHelpCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpSignatureHelpCommandHandlerTests.vb @@ -884,7 +884,7 @@ class C , showCompletionInArgumentLists:=showCompletionInArgumentLists) ' disable implicit sig help then type a trigger character -> no session should be available - state.Workspace.GetService(Of IGlobalOptionService).SetGlobalOption(New OptionKey(SignatureHelpViewOptionsStorage.ShowSignatureHelp, LanguageNames.CSharp), False) + state.Workspace.GetService(Of IGlobalOptionService).SetGlobalOption(SignatureHelpViewOptionsStorage.ShowSignatureHelp, LanguageNames.CSharp, False) state.SendTypeChars("(") Await state.AssertNoSignatureHelpSession() diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb index a1e7d40cb6028..7bc7451a3154f 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests.vb @@ -37,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim completionService = New TestCompletionService(workspace.Services.SolutionServices, workspace.GetService(Of IAsynchronousOperationListenerProvider)()) Dim list = Await completionService.GetCompletionsAsync( - document, caretPosition:=0, CompletionOptions.Default, OptionValueSet.Empty, CompletionTrigger.Invoke) + document, caretPosition:=0, CompletionOptions.Default, OptionSet.Empty, CompletionTrigger.Invoke) Assert.NotEmpty(list.ItemsList) Assert.True(list.ItemsList.Count = 1, "Completion list contained more than one item") @@ -102,7 +102,7 @@ $$ Dim completionService = document.GetRequiredLanguageService(Of CompletionService)() Dim list = Await completionService.GetCompletionsAsync( - document, caretPosition:=0, CompletionOptions.Default, OptionValueSet.Empty, CompletionTrigger.Invoke, + document, caretPosition:=0, CompletionOptions.Default, OptionSet.Empty, CompletionTrigger.Invoke, roles:=ImmutableHashSet.Create("MyTextViewRole")) Assert.True(list.ItemsList.Contains(MyRoleProvider.Item)) diff --git a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests_Exclusivitiy.vb b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests_Exclusivitiy.vb index b000ee52f8e39..89346e8091914 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests_Exclusivitiy.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CompletionServiceTests_Exclusivitiy.vb @@ -42,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim completionService = New TestCompletionService(workspace.Services.SolutionServices) Dim list = Await completionService.GetCompletionsAsync( - document, caretPosition:=0, CompletionOptions.Default, OptionValueSet.Empty, CompletionTrigger.Invoke) + document, caretPosition:=0, CompletionOptions.Default, OptionSet.Empty, CompletionTrigger.Invoke) Assert.NotEmpty(list.ItemsList) Assert.True(list.ItemsList.Count = 2, "Completion List does not contain exactly two items.") diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb index de4467351b3db..10eaf7ee4cd8d 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests.vb @@ -1837,8 +1837,7 @@ Class Class1 End Class ) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic), EnterKeyRule.AfterFullyTypedWord) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic, EnterKeyRule.AfterFullyTypedWord) state.SendTypeChars("System.TimeSpan.FromMin") state.SendReturn() @@ -1863,8 +1862,7 @@ Class Class1 End Class ) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic), EnterKeyRule.AfterFullyTypedWord) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.EnterKeyBehavior, LanguageNames.VisualBasic, EnterKeyRule.AfterFullyTypedWord) state.SendTypeChars("System.TimeSpan.FromMinutes") state.SendReturn() @@ -2167,8 +2165,7 @@ Class G End Class ]]>) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.TriggerOnTyping, LanguageNames.VisualBasic), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerOnTyping, LanguageNames.VisualBasic, False) state.SendBackspace() Await state.AssertNoCompletionSession() @@ -2656,8 +2653,7 @@ End Class }]]>, extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) state.SendTypeChars("Shortcu") Await state.AssertNoCompletionSession() @@ -2678,8 +2674,7 @@ End Class }]]>, extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) state.SendTypeChars("Shortcu") Await state.AssertNoCompletionSession() @@ -2701,8 +2696,7 @@ End Class }]]>, extraExportedTypes:={GetType(MockSnippetInfoService), GetType(SnippetCompletionProvider), GetType(StubVsEditorAdaptersFactoryService)}.ToList()) - state.Workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) state.SendInvokeCompletionList() Await state.AssertCompletionItemsContainAll("x", "Shortcut") diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicSignatureHelpCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicSignatureHelpCommandHandlerTests.vb index 898f60a893da0..075fb68fceb86 100644 --- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicSignatureHelpCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicSignatureHelpCommandHandlerTests.vb @@ -284,7 +284,7 @@ End Class ) ' disable implicit sig help then type a trigger character -> no session should be available - state.Workspace.GetService(Of IGlobalOptionService).SetGlobalOption(New OptionKey(SignatureHelpViewOptionsStorage.ShowSignatureHelp, LanguageNames.VisualBasic), False) + state.Workspace.GetService(Of IGlobalOptionService).SetGlobalOption(SignatureHelpViewOptionsStorage.ShowSignatureHelp, LanguageNames.VisualBasic, False) state.SendTypeChars("(") Await state.AssertNoSignatureHelpSession() diff --git a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb index a4b5f43111696..c7199c09b32db 100644 --- a/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb +++ b/src/EditorFeatures/Test2/KeywordHighlighting/AbstractKeywordHighlightingTests.vb @@ -28,7 +28,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.KeywordHighlighting Dim document As Document = workspace.CurrentSolution.Projects.First.Documents.First Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.KeywordHighlighting, document.Project.Language), optionIsEnabled) + globalOptions.SetGlobalOption(FeatureOnOffOptions.KeywordHighlighting, document.Project.Language, optionIsEnabled) WpfTestRunner.RequireWpfFact($"{NameOf(AbstractKeywordHighlightingTests)}.{NameOf(Me.VerifyHighlightsAsync)} creates asynchronous taggers") diff --git a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb index c05cf89d81d92..21892cc3b3029 100644 --- a/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb +++ b/src/EditorFeatures/Test2/ReferenceHighlighting/AbstractReferenceHighlightingTests.vb @@ -37,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting Dim caretPosition = hostDocument.CursorPosition.Value Dim snapshot = hostDocument.GetTextBuffer().CurrentSnapshot - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.ReferenceHighlighting, hostDocument.Project.Language), optionIsEnabled) + globalOptions.SetGlobalOption(FeatureOnOffOptions.ReferenceHighlighting, hostDocument.Project.Language, optionIsEnabled) Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id) Dim context = New TaggerContext(Of NavigableHighlightTag)( diff --git a/src/EditorFeatures/Test2/Rename/InlineRenameTests.vb b/src/EditorFeatures/Test2/Rename/InlineRenameTests.vb index 9f64c64f4cfb0..edde5551c41dc 100644 --- a/src/EditorFeatures/Test2/Rename/InlineRenameTests.vb +++ b/src/EditorFeatures/Test2/Rename/InlineRenameTests.vb @@ -126,7 +126,7 @@ class [|Test1$$|] , host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameFile), True) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, True) Dim session = StartSession(workspace) @@ -246,10 +246,10 @@ class Deconstructable Optional fileToRename As DocumentId = Nothing) As Task Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameOverloads), renameOverloads) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameInStrings), renameInStrings) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameInComments), renameInComments) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameFile), renameFile) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameOverloads, renameOverloads) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, renameInStrings) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, renameInComments) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, renameFile) Dim session = StartSession(workspace) diff --git a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb index 64517f397eeb3..107a269acd118 100644 --- a/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameCommandHandlerTests.vb @@ -235,7 +235,7 @@ End Class ' This test specifically matters for the case where a user is typing in the editor ' and is not intended to test the rename flyout tab behavior Dim optionsService = workspace.GetService(Of IGlobalOptionService)() - optionsService.SetGlobalOption(New OptionKey(InlineRenameUIOptions.UseInlineAdornment), False) + optionsService.SetGlobalOption(InlineRenameUIOptions.UseInlineAdornment, False) Dim view = workspace.Documents.Single().GetTextView() view.Caret.MoveTo(New SnapshotPoint(view.TextBuffer.CurrentSnapshot, workspace.Documents.Single(Function(d) d.CursorPosition.HasValue).CursorPosition.Value)) diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index c1b6c34549a3f..d1277e44211ce 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -552,10 +552,10 @@ class D : B Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameOverloads), renameOverloads) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameInStrings), renameInStrings) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameInComments), renameInComments) - globalOptions.SetGlobalOption(New OptionKey(InlineRenameSessionOptionsStorage.RenameFile), renameFile) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameOverloads, renameOverloads) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, renameInStrings) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, renameInComments) + globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, renameFile) Dim cursorDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) Dim cursorPosition = cursorDocument.CursorPosition.Value @@ -694,7 +694,7 @@ class D : B Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() - globalOptions.SetGlobalOption(New OptionKey(InlineRenameUIOptions.CollapseUI), False) + globalOptions.SetGlobalOption(InlineRenameUIOptions.CollapseUI, False) Dim cursorDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) Dim renameService = DirectCast(workspace.GetService(Of IInlineRenameService)(), InlineRenameService) diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index cd792e7eacacf..39ebc2164e02f 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -131,7 +131,7 @@ internal Task GetCompletionListAsync( int position, RoslynCompletion.CompletionTrigger triggerInfo, CompletionOptions options = null) - => service.GetCompletionsAsync(document, position, options ?? GetCompletionOptions(), OptionValueSet.Empty, triggerInfo, GetRoles(document)); + => service.GetCompletionsAsync(document, position, options ?? GetCompletionOptions(), TestOptionSet.Empty, triggerInfo, GetRoles(document)); private protected async Task CheckResultsAsync( Document document, int position, string expectedItemOrNull, diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs b/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs index 78d8b4b2d1667..50247ec4681e8 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/DiagnosticTaggerWrapper.cs @@ -47,8 +47,8 @@ public DiagnosticTaggerWrapper( // Change the background analysis scope to OpenFiles instead of ActiveFile (default), // so that every diagnostic tagger test does not need to mark test files as "active" file. - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.OpenFiles); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), BackgroundAnalysisScope.OpenFiles); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.OpenFiles); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, BackgroundAnalysisScope.OpenFiles); _workspace = workspace; diff --git a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs index 167ec650842d6..5048f7199cc38 100644 --- a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs +++ b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs @@ -85,7 +85,7 @@ internal void TestIndentation( editorOptions.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, !useTabs); // Remove once https://github.com/dotnet/roslyn/issues/62204 is fixed: - workspace.GlobalOptions.SetGlobalOption(new OptionKey(IndentationOptionsStorage.SmartIndent, document.Project.Language), indentStyle); + workspace.GlobalOptions.SetGlobalOption(IndentationOptionsStorage.SmartIndent, document.Project.Language, indentStyle); var snapshot = textBuffer.CurrentSnapshot; var bufferGraph = new Mock(MockBehavior.Strict); diff --git a/src/EditorFeatures/TestUtilities/TaskList/AbstractTaskListTests.cs b/src/EditorFeatures/TestUtilities/TaskList/AbstractTaskListTests.cs index bd45ba8c75d6f..bab78f3a94eae 100644 --- a/src/EditorFeatures/TestUtilities/TaskList/AbstractTaskListTests.cs +++ b/src/EditorFeatures/TestUtilities/TaskList/AbstractTaskListTests.cs @@ -31,7 +31,7 @@ protected async Task TestAsync(string codeWithMarker, TestHost host) using var workspace = CreateWorkspace(codeWithMarker, host); var descriptors = TaskListOptions.Default.Descriptors; - workspace.GlobalOptions.SetGlobalOption(new OptionKey(TaskListOptionsStorage.Descriptors), descriptors); + workspace.GlobalOptions.SetGlobalOption(TaskListOptionsStorage.Descriptors, descriptors); var hostDocument = workspace.Documents.First(); var initialTextSnapshot = hostDocument.GetTextBuffer().CurrentSnapshot; diff --git a/src/EditorFeatures/TestUtilities2/Intellisense/TestStateFactory.vb b/src/EditorFeatures/TestUtilities2/Intellisense/TestStateFactory.vb index bced396833bfb..5919d389d0e9a 100644 --- a/src/EditorFeatures/TestUtilities2/Intellisense/TestStateFactory.vb +++ b/src/EditorFeatures/TestUtilities2/Intellisense/TestStateFactory.vb @@ -25,8 +25,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense excludedTypes, extraExportedTypes, includeFormatCommandHandler, workspaceKind:=Nothing) - testState.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) - testState.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), False) + testState.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp, showCompletionInArgumentLists) + testState.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, False) Return testState End Function @@ -44,7 +44,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense excludedTypes:=Nothing, extraExportedTypes, includeFormatCommandHandler:=False, workspaceKind:=Nothing) - testState.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic), False) + testState.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, False) Return testState End Function @@ -57,8 +57,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense Dim testState = New TestState( workspaceElement, excludedTypes:=Nothing, extraExportedTypes, includeFormatCommandHandler:=False, workspaceKind) - testState.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp), showCompletionInArgumentLists) - testState.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic), False) + testState.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerInArgumentLists, LanguageNames.CSharp, showCompletionInArgumentLists) + testState.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, False) Return testState End Function diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb index a1acadaf083fe..bac46d1601ff6 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AddImport/AddImportTests_NuGet.vb @@ -26,8 +26,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeActions.AddImp ImmutableArray.Create(New PackageSource(PackageSourceHelper.NugetOrgSourceName, "http://nuget.org")) Protected Overrides Sub InitializeWorkspace(workspace As TestWorkspace, parameters As TestParameters) - workspace.GlobalOptions.SetGlobalOption(New OptionKey(SymbolSearchOptionsStorage.SearchNuGetPackages, LanguageNames.VisualBasic), True) - workspace.GlobalOptions.SetGlobalOption(New OptionKey(SymbolSearchOptionsStorage.SearchReferenceAssemblies, LanguageNames.VisualBasic), True) + workspace.GlobalOptions.SetGlobalOption(SymbolSearchOptionsStorage.SearchNuGetPackages, LanguageNames.VisualBasic, True) + workspace.GlobalOptions.SetGlobalOption(SymbolSearchOptionsStorage.SearchReferenceAssemblies, LanguageNames.VisualBasic, True) End Sub Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb index e3305c12b6684..332a1a8ae760a 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb @@ -13,11 +13,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera Public Class EndConstructCommandHandlerTests Private ReadOnly _endConstructServiceMock As New Mock(Of IEndConstructGenerationService)(MockBehavior.Strict) - Private ReadOnly _featureOptions As New Mock(Of ILegacyWorkspaceOptionService)(MockBehavior.Strict) Private ReadOnly _textViewMock As New Mock(Of ITextView)(MockBehavior.Strict) Private ReadOnly _textBufferMock As New Mock(Of ITextBuffer)(MockBehavior.Strict) #If False Then + Private ReadOnly _featureOptions As New Mock(Of ILegacyWorkspaceOptionService)(MockBehavior.Strict) + ' TODO(jasonmal): Figure out how to enable these tests. Public Sub ServiceNotCompletingShouldCallNextHandler() diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb index b4c8432906d74..4602fce37138d 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructTestingHelpers.vb @@ -36,7 +36,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera Dim beforeText = before.Replace("$$", "") Using workspace = TestWorkspace.CreateVisualBasic(beforeText, composition:=EditorTestCompositions.EditorFeatures) Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) Dim view = workspace.Documents.First().GetTextView() view.Caret.MoveTo(New SnapshotPoint(view.TextSnapshot, caretPos)) @@ -66,7 +66,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera afterCaret As Integer()) Using workspace = TestWorkspace.CreateVisualBasic(before, composition:=EditorTestCompositions.EditorFeatures) Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) Dim textView = workspace.Documents.First().GetTextView() Dim subjectBuffer = workspace.Documents.First().GetTextBuffer() @@ -210,7 +210,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera ' create separate composition Using workspace = TestWorkspace.CreateVisualBasic(before, composition:=EditorTestCompositions.EditorFeatures) Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) Dim view = workspace.Documents.First().GetTextView() view.Options.GlobalOptions.SetOptionValue(DefaultOptions.IndentStyleId, IndentingStyle.Smart) diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb index f25897b21ef8a..0f36b52054da5 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/CodeCleanUpTests.vb @@ -568,8 +568,8 @@ End Class ' must set global options since incremental analyzer infra reads from global options Dim globalOptions = workspace.GlobalOptions - globalOptions.SetGlobalOption(New OptionKey(GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.VisualBasic), separateImportsGroups) - globalOptions.SetGlobalOption(New OptionKey(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic), systemImportsFirst) + globalOptions.SetGlobalOption(GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.VisualBasic, separateImportsGroups) + globalOptions.SetGlobalOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic, systemImportsFirst) Dim solution = workspace.CurrentSolution.WithAnalyzerReferences( { diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndentProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndentProviderTests.vb index 138fe23151c81..9a3b3e02ed6db 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndentProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndentProviderTests.vb @@ -40,7 +40,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting.Indenta Public Sub GetSmartIndent3() Using workspace = TestWorkspace.CreateCSharp("") Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(InternalFeatureOnOffOptions.SmartIndenter), False) + globalOptions.SetGlobalOption(InternalFeatureOnOffOptions.SmartIndenter, False) Dim document = workspace.Projects.Single().Documents.Single() Dim provider = workspace.ExportProvider.GetExportedValues(Of ISmartIndentProvider)().OfType(Of SmartIndentProvider)().Single() diff --git a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb index 303c292e5848c..d0288ce16f35d 100644 --- a/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/ImplementInterface/ImplementInterfaceCommandHandlerTests.vb @@ -67,7 +67,7 @@ End Interface") Dim globalOptions = workspace.GetService(Of IGlobalOptionService) Dim commandHandler = MoveCaretAndCreateCommandHandler(workspace) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers, LanguageNames.VisualBasic, False) Dim nextHandlerCalled = False Dim view = workspace.Documents.Single().GetTextView() diff --git a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitOnMiscellaneousCommandsTests.vb b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitOnMiscellaneousCommandsTests.vb index a80a6c631b60e..b1f1e8e09e063 100644 --- a/src/EditorFeatures/VisualBasicTest/LineCommit/CommitOnMiscellaneousCommandsTests.vb +++ b/src/EditorFeatures/VisualBasicTest/LineCommit/CommitOnMiscellaneousCommandsTests.vb @@ -72,7 +72,7 @@ End Class Dim workspace = testData.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) testData.CommandHandler.ExecuteCommand(New PasteCommandArgs(testData.View, testData.Buffer), Sub() testData.EditorOperations.InsertText("Class Program" & vbCrLf & " Sub M(abc As Integer)" & vbCrLf & " Dim a = 7" & vbCrLf & " End Sub" & vbCrLf & "End Class"), @@ -131,7 +131,7 @@ End Class ) Dim workspace = testData.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) testData.Buffer.Insert(57, " ") testData.CommandHandler.ExecuteCommand(New SaveCommandArgs(testData.View, testData.Buffer), Sub() Exit Sub, TestCommandExecutionContext.Create()) @@ -180,7 +180,7 @@ End Module ' Turn off pretty listing Dim workspace = testData.Workspace Dim globalOptions = workspace.GetService(Of IGlobalOptionService) - globalOptions.SetGlobalOption(New OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), False) + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, False) testData.CommandHandler.ExecuteCommand(New FormatDocumentCommandArgs(testData.View, testData.Buffer), Sub() Exit Sub, TestCommandExecutionContext.Create()) Assert.Equal(" Sub Main()", testData.Buffer.CurrentSnapshot.GetLineFromLineNumber(1).GetText()) diff --git a/src/EditorFeatures/VisualBasicTest/Squiggles/ErrorSquiggleProducerTests.vb b/src/EditorFeatures/VisualBasicTest/Squiggles/ErrorSquiggleProducerTests.vb index 7b502cccfcd77..5535c6ea9e7b2 100644 --- a/src/EditorFeatures/VisualBasicTest/Squiggles/ErrorSquiggleProducerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Squiggles/ErrorSquiggleProducerTests.vb @@ -119,8 +119,7 @@ End Class" Dim language = workspace.Projects.Single().Language workspace.GlobalOptions.SetGlobalOption( - New OptionKey(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, language), - New CodeStyleOption2(Of Boolean)(value:=True, notification:=NotificationOption2.Error)) + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, language, New CodeStyleOption2(Of Boolean)(value:=True, notification:=NotificationOption2.Error)) Dim diagnosticsAndSpans = Await TestDiagnosticTagProducer(Of DiagnosticsSquiggleTaggerProvider, IErrorTag).GetDiagnosticsAndErrorSpans(workspace, analyzerMap) Dim spans = diagnosticsAndSpans.Item1.Zip(diagnosticsAndSpans.Item2, Function(diagnostic, span) (diagnostic, span)).OrderBy(Function(s) s.span.Span.Span.Start).ToImmutableArray() diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 7ad4935eac530..b0a90b03f1897 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -402,32 +402,32 @@ internal static bool TryGetEditorConfigStringParts(string editorConfigString, ou return false; } - internal static ImmutableArray<(OptionKey optionKey, IEditorConfigStorageLocation2 location)> GetCodeStyleOptionsForDiagnostic( + internal static ImmutableArray<(OptionKey2 optionKey, IEditorConfigStorageLocation2 location)> GetCodeStyleOptionsForDiagnostic( Diagnostic diagnostic, Project project) { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - using var _ = ArrayBuilder<(OptionKey, IEditorConfigStorageLocation2)>.GetInstance(out var builder); + using var _ = ArrayBuilder<(OptionKey2, IEditorConfigStorageLocation2)>.GetInstance(out var builder); foreach (var option in options.OrderBy(option => option.Name)) { var editorConfigLocation = option.StorageLocations.OfType().FirstOrDefault(); if (editorConfigLocation != null && option.DefaultValue is ICodeStyleOption codeStyleOption) { - var optionKey = new OptionKey(option, option.IsPerLanguage ? project.Language : null); + var optionKey = new OptionKey2(option, option.IsPerLanguage ? project.Language : null); builder.Add((optionKey, editorConfigLocation)); continue; } // Did not find a match. - return ImmutableArray<(OptionKey, IEditorConfigStorageLocation2)>.Empty; + return ImmutableArray<(OptionKey2, IEditorConfigStorageLocation2)>.Empty; } return builder.ToImmutable(); } - return ImmutableArray<(OptionKey, IEditorConfigStorageLocation2)>.Empty; + return ImmutableArray<(OptionKey2, IEditorConfigStorageLocation2)>.Empty; } private SourceText? GetNewAnalyzerConfigDocumentText(SourceText originalText, AnalyzerConfigDocument editorConfigDocument) diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs index 5178e9bad94bb..5f7d81d105772 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigureCodeStyle/ConfigureCodeStyleOptionCodeFixProvider.cs @@ -104,7 +104,7 @@ private static ImmutableArray GetConfigurations(Project project, IEnume // Local functions TopLevelConfigureCodeStyleOptionCodeAction GetCodeActionForCodeStyleOption( - OptionKey optionKey, + OptionKey2 optionKey, IEditorConfigStorageLocation2 editorConfigLocation, Diagnostic diagnostic, bool hasMultipleOptions) diff --git a/src/Features/Core/Portable/Completion/CompletionContext.cs b/src/Features/Core/Portable/Completion/CompletionContext.cs index bbd707306e25b..cc579d4ec7fc9 100644 --- a/src/Features/Core/Portable/Completion/CompletionContext.cs +++ b/src/Features/Core/Portable/Completion/CompletionContext.cs @@ -129,7 +129,7 @@ public CompletionContext( cancellationToken) { #pragma warning disable RS0030 // Do not used banned APIs - Options = options ?? OptionValueSet.Empty; + Options = options ?? OptionSet.Empty; #pragma warning restore } @@ -157,7 +157,7 @@ internal CompletionContext( SharedSyntaxContextsWithSpeculativeModel = sharedSyntaxContextsWithSpeculativeModel; #pragma warning disable RS0030 // Do not used banned APIs - Options = OptionValueSet.Empty; + Options = OptionSet.Empty; #pragma warning restore } diff --git a/src/Features/Core/Portable/Completion/CompletionService.cs b/src/Features/Core/Portable/Completion/CompletionService.cs index 221d80d26e1ce..17b74df48ecde 100644 --- a/src/Features/Core/Portable/Completion/CompletionService.cs +++ b/src/Features/Core/Portable/Completion/CompletionService.cs @@ -103,7 +103,7 @@ public bool ShouldTriggerCompletion( // Publicly available options do not affect this API. var completionOptions = CompletionOptions.Default; - var passThroughOptions = options ?? document?.Project.Solution.Options ?? OptionValueSet.Empty; + var passThroughOptions = options ?? document?.Project.Solution.Options ?? OptionSet.Empty; return ShouldTriggerCompletion(document?.Project, languageServices, text, caretPosition, trigger, completionOptions, passThroughOptions, roles); } diff --git a/src/Features/LanguageServer/Protocol/ExternalAccess/VSCode/API/VSCodeAnalyzerLoader.cs b/src/Features/LanguageServer/Protocol/ExternalAccess/VSCode/API/VSCodeAnalyzerLoader.cs index 3d22abfadcac6..d4b31cfce7cb6 100644 --- a/src/Features/LanguageServer/Protocol/ExternalAccess/VSCode/API/VSCodeAnalyzerLoader.cs +++ b/src/Features/LanguageServer/Protocol/ExternalAccess/VSCode/API/VSCodeAnalyzerLoader.cs @@ -29,7 +29,7 @@ public VSCodeAnalyzerLoader(IDiagnosticAnalyzerService analyzerService, IDiagnos public void InitializeDiagnosticsServices(Workspace workspace) { - _globalOptionService.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), DiagnosticMode.LspPull); + _globalOptionService.SetGlobalOption(InternalDiagnosticsOptions.NormalDiagnosticMode, DiagnosticMode.LspPull); _ = ((IIncrementalAnalyzerProvider)_analyzerService).CreateIncrementalAnalyzer(workspace); _diagnosticService.Register((IDiagnosticUpdateSource)_analyzerService); } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 8406732d9f5a6..a9bf3d36ac9f1 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; diff --git a/src/Features/LanguageServer/Protocol/Features/Options/InternalDiagnosticsOptions.cs b/src/Features/LanguageServer/Protocol/Features/Options/InternalDiagnosticsOptions.cs index 2be5ca07a6cf4..9cce993c30fbb 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/InternalDiagnosticsOptions.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/InternalDiagnosticsOptions.cs @@ -8,6 +8,18 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal static class InternalDiagnosticsOptions { + /// + /// Diagnostic mode setting for Razor. This should always be as there is no push support in Razor. + /// This option is only for passing to the diagnostics service and can be removed when we switch all of Roslyn to LSP pull. + /// + public static readonly Option2 RazorDiagnosticMode = new(nameof(InternalDiagnosticsOptions), "RazorDiagnosticMode", defaultValue: DiagnosticMode.LspPull); + + /// + /// Diagnostic mode setting for Live Share. This should always be as there is no push support in Live Share. + /// This option is only for passing to the diagnostics service and can be removed when we switch all of Roslyn to LSP pull. + /// + public static readonly Option2 LiveShareDiagnosticMode = new(nameof(InternalDiagnosticsOptions), "LiveShareDiagnosticMode", defaultValue: DiagnosticMode.LspPull); + public static readonly Option2 NormalDiagnosticMode = new("InternalDiagnosticsOptions", "NormalDiagnosticMode", defaultValue: DiagnosticMode.Default, storageLocation: new LocalUserProfileStorageLocation(@"Roslyn\Internal\Diagnostics\NormalDiagnosticMode")); } diff --git a/src/Features/LanguageServer/Protocol/Features/Options/QuickInfoOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/QuickInfoOptionsStorage.cs index 7dc83fc753593..6ecf25fed574a 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/QuickInfoOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/QuickInfoOptionsStorage.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.QuickInfo { internal static class QuickInfoOptionsStorage { - public static QuickInfoOptions GetQuickInfoOptions(this IGlobalOptionService globalOptions, string? language) + public static QuickInfoOptions GetQuickInfoOptions(this IGlobalOptionService globalOptions, string language) => new() { ShowRemarksInQuickInfo = globalOptions.GetOption(ShowRemarksInQuickInfo, language), diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index e04356bd71a98..4500e0ddf16be 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -42,18 +42,6 @@ public AbstractDocumentPullDiagnosticHandler( internal abstract partial class AbstractPullDiagnosticHandler : ILspServiceRequestHandler where TDiagnosticsParams : IPartialResultParams { - /// - /// Diagnostic mode setting for Razor. This should always be as there is no push support in Razor. - /// This option is only for passing to the diagnostics service and can be removed when we switch all of Roslyn to LSP pull. - /// - private static readonly Option2 s_razorDiagnosticMode = new(nameof(InternalDiagnosticsOptions), "RazorDiagnosticMode", defaultValue: DiagnosticMode.LspPull); - - /// - /// Diagnostic mode setting for Live Share. This should always be as there is no push support in Live Share. - /// This option is only for passing to the diagnostics service and can be removed when we switch all of Roslyn to LSP pull. - /// - private static readonly Option2 s_liveShareDiagnosticMode = new(nameof(InternalDiagnosticsOptions), "LiveShareDiagnosticMode", defaultValue: DiagnosticMode.LspPull); - /// /// Special value we use to designate workspace diagnostics vs document diagnostics. Document diagnostics /// should always a workspace diagnostic as the former are 'live' @@ -256,8 +244,8 @@ private DiagnosticMode GetDiagnosticMode(RequestContext context) { var diagnosticModeOption = context.ServerKind switch { - WellKnownLspServerKinds.LiveShareLspServer => s_liveShareDiagnosticMode, - WellKnownLspServerKinds.RazorLspServer => s_razorDiagnosticMode, + WellKnownLspServerKinds.LiveShareLspServer => InternalDiagnosticsOptions.LiveShareDiagnosticMode, + WellKnownLspServerKinds.RazorLspServer => InternalDiagnosticsOptions.RazorDiagnosticMode, _ => InternalDiagnosticsOptions.NormalDiagnosticMode, }; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index ccc21b29886c9..de96b9f4a5a3b 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -210,7 +210,7 @@ void M() var solution = testLspServer.TestWorkspace.CurrentSolution; // Make sure the unimported types option is on by default. - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp), true); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, true); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), @@ -233,8 +233,7 @@ public async Task TestGetCompletionsUsesSnippetOptionAsync() await using var testLspServer = await CreateTestLspServerAsync(markup, s_vsCompletionCapabilities); - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption( - new OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp), SnippetsRule.NeverInclude); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.CSharp, SnippetsRule.NeverInclude); var completionParams = CreateCompletionParams( testLspServer.GetLocations("caret").Single(), @@ -1290,7 +1289,7 @@ void M() var globalOptions = testLspServer.TestWorkspace.GetService(); var listMaxSize = 1; - globalOptions.SetGlobalOption(new OptionKey(LspOptions.MaxCompletionListSize), listMaxSize); + globalOptions.SetGlobalOption(LspOptions.MaxCompletionListSize, listMaxSize); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); Assert.True(results.IsIncomplete); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 81b3d728baf06..1b6a7d4881114 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -60,7 +60,7 @@ private protected static async Task> RunGet string? category = null) { var optionService = testLspServer.TestWorkspace.GetService(); - optionService.SetGlobalOption(new OptionKey(TaskListOptionsStorage.ComputeTaskListItemsForClosedFiles), includeTaskListItems); + optionService.SetGlobalOption(TaskListOptionsStorage.ComputeTaskListItemsForClosedFiles, includeTaskListItems); await testLspServer.WaitForDiagnosticsAsync(); if (useVSDiagnostics) @@ -284,11 +284,11 @@ private protected static InitializationOptions GetInitializationOptions( ClientCapabilities = useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), OptionUpdater = (globalOptions) => { - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), scope); - globalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), mode); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles), true); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, scope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, scope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript, scope); + globalOptions.SetGlobalOption(InternalDiagnosticsOptions.NormalDiagnosticMode, mode); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, true); }, ServerKind = serverKind, SourceGeneratedMarkups = sourceGeneratedMarkups ?? Array.Empty() diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index ce2730069e1da..ef0139da7c19d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -213,7 +213,7 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOf await OpenDocumentAsync(testLspServer, document); // Ensure we get no diagnostics when feature flag is off. - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(DiagnosticOptionsStorage.LspPullDiagnosticsFeatureFlag), false); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.LspPullDiagnosticsFeatureFlag, false); await Assert.ThrowsAsync(async () => await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics)); } @@ -231,7 +231,7 @@ public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(b var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); await OpenDocumentAsync(testLspServer, document); - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(DiagnosticOptionsStorage.LspPullDiagnosticsFeatureFlag), true); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(DiagnosticOptionsStorage.LspPullDiagnosticsFeatureFlag, true); var results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics); Assert.Equal("CS1513", results.Single().Diagnostics.Single().Code); @@ -699,7 +699,7 @@ class A }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var firstLocation = testLspServer.GetLocations("first").Single().Range; - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp), true); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, true); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); @@ -738,7 +738,7 @@ class A }"; await using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); var firstLocation = testLspServer.GetLocations("first").Single().Range; - testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp), false); + testLspServer.TestWorkspace.GlobalOptions.SetGlobalOption(FadingOptions.FadeOutUnusedImports, LanguageNames.CSharp, false); var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs index f2144c37197dc..1e3d8caf143f2 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/VSTypeScriptHandlerTests.cs @@ -65,7 +65,7 @@ public async Task TestRoslynTypeScriptHandlerInvoked() var options = new InitializationOptions() { - OptionUpdater = globalOptions => globalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), DiagnosticMode.LspPull) + OptionUpdater = globalOptions => globalOptions.SetGlobalOption(InternalDiagnosticsOptions.NormalDiagnosticMode, DiagnosticMode.LspPull) }; await using var testLspServer = await CreateTsTestLspServerAsync(workspaceXml, options); diff --git a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs index 513279608e0e9..76be36386db05 100644 --- a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs @@ -43,8 +43,8 @@ public async Task RunAsync(CancellationToken cancellationToken) var exportProvider = _workspace.Services.SolutionServices.ExportProvider; var globalOptions = exportProvider.GetExports().Single().Value; - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), _options.AnalysisScope); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), _options.AnalysisScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, _options.AnalysisScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, _options.AnalysisScope); var workspaceConfigurationService = (AnalyzerRunnerWorkspaceConfigurationService)_workspace.Services.GetRequiredService(); workspaceConfigurationService.Options = new(CacheStorage: usePersistentStorage ? StorageDatabase.SQLite : StorageDatabase.None); diff --git a/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs b/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs index 8fa131829ffbb..5cc39b805f5d3 100644 --- a/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs +++ b/src/Tools/ExternalAccess/FSharp/FSharpGlobalOptions.cs @@ -28,13 +28,13 @@ public FSharpGlobalOptions(IGlobalOptionService globalOptions) public bool BlockForCompletionItems { get => _globalOptions.GetOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.FSharp); - set => _globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.BlockForCompletionItems, LanguageNames.FSharp), value); + set => _globalOptions.SetGlobalOption(CompletionViewOptions.BlockForCompletionItems, LanguageNames.FSharp, value); } public void SetBackgroundAnalysisScope(bool openFilesOnly) { _globalOptions.SetGlobalOption( - new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.FSharp), + SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.FSharp, openFilesOnly ? BackgroundAnalysisScope.OpenFiles : BackgroundAnalysisScope.FullSolution); } } diff --git a/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs b/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs index 62534a071b68c..bdbfe4afa5b39 100644 --- a/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs +++ b/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs @@ -32,13 +32,13 @@ public RazorAutoFormattingOptions GetAutoFormattingOptions() public bool UseTabs { get => _globalOptions.GetOption(RazorLineFormattingOptionsStorage.UseTabs); - set => _globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.UseTabs), value); + set => _globalOptions.SetGlobalOption(RazorLineFormattingOptionsStorage.UseTabs, value); } public int TabSize { get => _globalOptions.GetOption(RazorLineFormattingOptionsStorage.TabSize); - set => _globalOptions.SetGlobalOption(new OptionKey(RazorLineFormattingOptionsStorage.TabSize), value); + set => _globalOptions.SetGlobalOption(RazorLineFormattingOptionsStorage.TabSize, value); } #pragma warning disable IDE0060 // Remove unused parameter @@ -55,20 +55,18 @@ private sealed class TestGlobalOptionService : IGlobalOptionService public event EventHandler? OptionChanged; #pragma warning restore - public T GetOption(PerLanguageOption2 option, string? languageName) + public T GetOption(PerLanguageOption2 option, string languageName) => default!; public T GetOption(Option2 option) => throw new NotImplementedException(); - public object? GetOption(OptionKey optionKey) => throw new NotImplementedException(); + public T GetOption(OptionKey2 optionKey) => throw new NotImplementedException(); public ImmutableArray GetOptions(ImmutableArray optionKeys) => throw new NotImplementedException(); - public IEnumerable GetRegisteredOptions() => throw new NotImplementedException(); - public ImmutableHashSet GetRegisteredSerializableOptions(ImmutableHashSet languages) => throw new NotImplementedException(); - public void RefreshOption(OptionKey optionKey, object? newValue) => throw new NotImplementedException(); - public void RegisterWorkspace(Workspace workspace) => throw new NotImplementedException(); - public void SetGlobalOption(OptionKey optionKey, object? value) => throw new NotImplementedException(); - public void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values) => throw new NotImplementedException(); - public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) => throw new NotImplementedException(); - public void UnregisterWorkspace(Workspace workspace) => throw new NotImplementedException(); + public bool RefreshOption(OptionKey2 optionKey, object? newValue) => throw new NotImplementedException(); + public ImmutableArray GetOptions(ImmutableArray optionKeys) => throw new NotImplementedException(); + public void SetGlobalOption(Option2 option, T value) => throw new NotImplementedException(); + public void SetGlobalOption(PerLanguageOption2 option, string language, T value) => throw new NotImplementedException(); + public void SetGlobalOption(OptionKey2 optionKey, object? value) => throw new NotImplementedException(); + public bool SetGlobalOptions(ImmutableArray> options) => throw new NotImplementedException(); } } } diff --git a/src/VisualStudio/CSharp/Test/Options/OptionViewModelTests.cs b/src/VisualStudio/CSharp/Test/Options/OptionViewModelTests.cs index 4e98b5623bcd9..d3939dccca21e 100644 --- a/src/VisualStudio/CSharp/Test/Options/OptionViewModelTests.cs +++ b/src/VisualStudio/CSharp/Test/Options/OptionViewModelTests.cs @@ -75,7 +75,7 @@ public void TestCheckBox() public void TestOptionLoading() { using var workspace = TestWorkspace.CreateCSharp(""); - var optionSet = workspace.Options.WithChangedOption(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName, true); + var optionSet = workspace.Options.WithChangedOption(new OptionKey(CSharpFormattingOptions2.SpacingAfterMethodDeclarationName), true); var optionStore = new OptionStore(optionSet); var serviceProvider = new MockServiceProvider(workspace.ExportProvider); diff --git a/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.Settings.cs b/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.Settings.cs index 5921cade18fb7..ccbe220840bfc 100644 --- a/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.Settings.cs +++ b/src/VisualStudio/Core/Def/ColorSchemes/ColorSchemeApplier.Settings.cs @@ -135,8 +135,8 @@ public void MigrateToColorSchemeSetting() ? ColorSchemeName.VisualStudio2017 : ColorSchemeName.VisualStudio2019; - _globalOptions.SetGlobalOption(new OptionKey(ColorSchemeOptions.ColorScheme), colorScheme); - _globalOptions.SetGlobalOption(new OptionKey(ColorSchemeOptions.LegacyUseEnhancedColors), ColorSchemeOptions.UseEnhancedColors.Migrated); + _globalOptions.SetGlobalOption(ColorSchemeOptions.ColorScheme, colorScheme); + _globalOptions.SetGlobalOption(ColorSchemeOptions.LegacyUseEnhancedColors, ColorSchemeOptions.UseEnhancedColors.Migrated); } } } diff --git a/src/VisualStudio/Core/Def/ExternalAccess/Pythia/PythiaGlobalOptions.cs b/src/VisualStudio/Core/Def/ExternalAccess/Pythia/PythiaGlobalOptions.cs index 5b0e9ba0d7a45..52034bec1775e 100644 --- a/src/VisualStudio/Core/Def/ExternalAccess/Pythia/PythiaGlobalOptions.cs +++ b/src/VisualStudio/Core/Def/ExternalAccess/Pythia/PythiaGlobalOptions.cs @@ -24,13 +24,13 @@ public PythiaGlobalOptions(IGlobalOptionService globalOptions) public bool ShowDebugInfo { get => _globalOptions.GetOption(s_showDebugInfoOption); - set => _globalOptions.SetGlobalOption(new OptionKey(s_showDebugInfoOption), value); + set => _globalOptions.SetGlobalOption(s_showDebugInfoOption, value); } public bool RemoveRecommendationLimit { get => _globalOptions.GetOption(s_removeRecommendationLimitOption); - set => _globalOptions.SetGlobalOption(new OptionKey(s_removeRecommendationLimitOption), value); + set => _globalOptions.SetGlobalOption(s_removeRecommendationLimitOption, value); } public const string LocalRegistryPath = @"Roslyn\Internal\OnOff\Features\"; diff --git a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs index d260cf44ec2fc..5327da33a2092 100644 --- a/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/FindReferences/StreamingFindUsagesPresenter.cs @@ -243,7 +243,7 @@ private AbstractTableDataSourceFindUsagesContext StartSearchWithoutReferences( private void StoreCurrentGroupingPriority(IFindAllReferencesWindow window) { - _globalOptions.SetGlobalOption(new OptionKey(FindUsagesPresentationOptionsStorage.DefinitionGroupingPriority), window.GetDefinitionColumn().GroupingPriority); + _globalOptions.SetGlobalOption(FindUsagesPresentationOptionsStorage.DefinitionGroupingPriority, window.GetDefinitionColumn().GroupingPriority); } private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int priority) diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index 871f0f2839974..433d6c1226693 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -59,9 +59,9 @@ internal sealed class KeybindingResetDetector : ForegroundThreadAffinitizedObjec private static readonly Guid s_resharperPackageGuid = new("0C6E6407-13FC-4878-869A-C8B4016C57FE"); private static readonly Guid s_resharperCommandGroup = new("47F03277-5055-4922-899C-0F7F30D26BF1"); - private static readonly ImmutableArray s_statusOptions = ImmutableArray.Create( - new OptionKey(KeybindingResetOptions.ReSharperStatus), - new OptionKey(KeybindingResetOptions.NeedsReset)); + private static readonly ImmutableArray s_statusOptions = ImmutableArray.Create( + new OptionKey2(KeybindingResetOptions.ReSharperStatus), + new OptionKey2(KeybindingResetOptions.NeedsReset)); private readonly IGlobalOptionService _globalOptions; private readonly System.IServiceProvider _serviceProvider; @@ -217,7 +217,9 @@ private async Task UpdateStateMachineWorkerAsync(CancellationToken cancellationT break; } - _globalOptions.SetGlobalOptions(s_statusOptions, ImmutableArray.Create(currentStatus, needsReset)); + _globalOptions.SetGlobalOptions(ImmutableArray.Create( + KeyValuePairUtil.Create(new OptionKey2(KeybindingResetOptions.ReSharperStatus), (object)currentStatus), + KeyValuePairUtil.Create(new OptionKey2(KeybindingResetOptions.NeedsReset), (object)needsReset))); if (needsReset) { @@ -351,7 +353,7 @@ private void RestoreVsKeybindings() KeybindingsResetLogger.Log("KeybindingsReset"); - _globalOptions.SetGlobalOption(new OptionKey(KeybindingResetOptions.NeedsReset), false); + _globalOptions.SetGlobalOption(KeybindingResetOptions.NeedsReset, false); } private void OpenExtensionsHyperlink() @@ -361,13 +363,13 @@ private void OpenExtensionsHyperlink() VisualStudioNavigateToLinkService.StartBrowser(KeybindingsFwLink); KeybindingsResetLogger.Log("ExtensionsLink"); - _globalOptions.SetGlobalOption(new OptionKey(KeybindingResetOptions.NeedsReset), false); + _globalOptions.SetGlobalOption(KeybindingResetOptions.NeedsReset, false); } private void NeverShowAgain() { - _globalOptions.SetGlobalOption(new OptionKey(KeybindingResetOptions.NeverShowAgain), true); - _globalOptions.SetGlobalOption(new OptionKey(KeybindingResetOptions.NeedsReset), false); + _globalOptions.SetGlobalOption(KeybindingResetOptions.NeverShowAgain, true); + _globalOptions.SetGlobalOption(KeybindingResetOptions.NeedsReset, false); KeybindingsResetLogger.Log("NeverShowAgain"); // The only external references to this object are as callbacks, which are removed by the Shutdown method. diff --git a/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs b/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs index 6e25e473ed4ea..9762593b6e4f5 100644 --- a/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs +++ b/src/VisualStudio/Core/Def/Options/FeatureFlagPersister.cs @@ -24,7 +24,7 @@ public FeatureFlagPersister(IVsFeatureFlags? featureFlags) _featureFlags = featureFlags; } - public bool TryFetch(OptionKey optionKey, [NotNullWhen(true)] out object? value) + public bool TryFetch(OptionKey2 optionKey, [NotNullWhen(true)] out object? value) { if (_featureFlags == null) { @@ -56,7 +56,7 @@ public bool TryFetch(OptionKey optionKey, [NotNullWhen(true)] out object? value) return true; } - public bool TryPersist(OptionKey optionKey, object? value) + public bool TryPersist(OptionKey2 optionKey, object? value) { if (_featureFlags == null) { diff --git a/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs b/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs index 551c102147a9a..953943aa48a0c 100644 --- a/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Options/LocalUserRegistryOptionPersister.cs @@ -68,7 +68,7 @@ private static bool TryGetKeyPathAndName(IOption option, [NotNullWhen(true)] out } } - bool IOptionPersister.TryFetch(OptionKey optionKey, out object? value) + bool IOptionPersister.TryFetch(OptionKey2 optionKey, out object? value) { if (!TryGetKeyPathAndName(optionKey.Option, out var path, out var key)) { @@ -141,7 +141,7 @@ bool IOptionPersister.TryFetch(OptionKey optionKey, out object? value) return false; } - bool IOptionPersister.TryPersist(OptionKey optionKey, object? value) + bool IOptionPersister.TryPersist(OptionKey2 optionKey, object? value) { if (_registryKey == null) { diff --git a/src/VisualStudio/Core/Def/Options/PackageSettingsPersister.cs b/src/VisualStudio/Core/Def/Options/PackageSettingsPersister.cs index 66f0f5b8de6ac..84cbb3da7df79 100644 --- a/src/VisualStudio/Core/Def/Options/PackageSettingsPersister.cs +++ b/src/VisualStudio/Core/Def/Options/PackageSettingsPersister.cs @@ -43,17 +43,17 @@ private async Task InitializeAsync(CancellationToken cancellationToken) _lazyRoslynPackage = await RoslynPackage.GetOrLoadAsync(_threadingContext, _serviceProvider, cancellationToken).ConfigureAwait(true); Assumes.Present(_lazyRoslynPackage); - _optionService.RefreshOption(new OptionKey(SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption), _lazyRoslynPackage.AnalysisScope); + _optionService.RefreshOption(new OptionKey2(SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption), _lazyRoslynPackage.AnalysisScope); _lazyRoslynPackage.AnalysisScopeChanged += OnAnalysisScopeChanged; } private void OnAnalysisScopeChanged(object? sender, EventArgs e) { Assumes.Present(_lazyRoslynPackage); - _optionService.RefreshOption(new OptionKey(SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption), _lazyRoslynPackage.AnalysisScope); + _optionService.RefreshOption(new OptionKey2(SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption), _lazyRoslynPackage.AnalysisScope); } - public bool TryFetch(OptionKey optionKey, out object? value) + public bool TryFetch(OptionKey2 optionKey, out object? value) { // This option is refreshed via the constructor to avoid UI dependencies when retrieving option values. If // we happen to reach this point before the value is available, try to obtain it without blocking, and @@ -76,7 +76,7 @@ public bool TryFetch(OptionKey optionKey, out object? value) return false; } - public bool TryPersist(OptionKey optionKey, object? value) + public bool TryPersist(OptionKey2 optionKey, object? value) { if (!Equals(optionKey.Option, SolutionCrawlerOptionsStorage.SolutionBackgroundAnalysisScopeOption)) return false; diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs index c444acee8fac4..3d745c4f9ff46 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersister.cs @@ -30,25 +30,25 @@ internal sealed class VisualStudioSettingsOptionPersister : IOptionPersister private class SVsSettingsPersistenceManager { }; private readonly ISettingsManager? _settingManager; - private readonly IGlobalOptionService _globalOptionService; + private readonly ILegacyGlobalOptionService _legacyGlobalOptions; /// /// The list of options that have been been fetched from , by key. We track this so /// if a later change happens, we know to refresh that value. This is synchronized with monitor locks on /// . /// - private readonly Dictionary> _optionsToMonitorForChanges = new(); + private readonly Dictionary> _optionsToMonitorForChanges = new(); private readonly object _optionsToMonitorForChangesGate = new(); /// /// We make sure this code is from the UI by asking for all in /// - public VisualStudioSettingsOptionPersister(IGlobalOptionService globalOptionService, ISettingsManager? settingsManager) + public VisualStudioSettingsOptionPersister(ILegacyGlobalOptionService globalOptionService, ISettingsManager? settingsManager) { Contract.ThrowIfNull(globalOptionService); _settingManager = settingsManager; - _globalOptionService = globalOptionService; + _legacyGlobalOptions = globalOptionService; // While the settings persistence service should be available in all SKUs it is possible an ISO shell author has undefined the // contributing package. In that case persistence of settings won't work (we don't bother with a backup solution for persistence @@ -62,7 +62,7 @@ public VisualStudioSettingsOptionPersister(IGlobalOptionService globalOptionServ private System.Threading.Tasks.Task OnSettingChangedAsync(object sender, PropertyChangedEventArgs args) { - List? optionsToRefresh = null; + List? optionsToRefresh = null; lock (_optionsToMonitorForChangesGate) { @@ -80,19 +80,27 @@ private System.Threading.Tasks.Task OnSettingChangedAsync(object sender, Propert // while the setting was changed we might not refresh it. Why? We call RecordObservedValueToWatchForChanges before we fetch the value // and since this event is raised after the setting is modified, any new setting would have already been observed in GetFirstOrDefaultValue. // And if it wasn't, this event will then refresh it. + var anyOptionChanged = false; foreach (var optionToRefresh in optionsToRefresh) { if (TryFetch(optionToRefresh, out var optionValue)) { - _globalOptionService.RefreshOption(optionToRefresh, optionValue); + anyOptionChanged |= _legacyGlobalOptions.GlobalOptions.RefreshOption(optionToRefresh, optionValue); } } + + // We may be updating the values of internally defined public options. + // Update solution snapshots of all workspaces to reflect the new values. + if (anyOptionChanged) + { + _legacyGlobalOptions.UpdateRegisteredWorkspaces(); + } } return System.Threading.Tasks.Task.CompletedTask; } - private object? GetFirstOrDefaultValue(OptionKey optionKey, IEnumerable storageLocations) + private object? GetFirstOrDefaultValue(OptionKey2 optionKey, IEnumerable storageLocations) { Contract.ThrowIfNull(_settingManager); @@ -131,7 +139,7 @@ private System.Threading.Tasks.Task OnSettingChangedAsync(object sender, Propert return optionKey.Option.DefaultValue; } - public bool TryFetch(OptionKey optionKey, out object? value) + public bool TryFetch(OptionKey2 optionKey, out object? value) { if (_settingManager == null) { @@ -238,12 +246,12 @@ private static bool DeserializeCodeStyleOption(ref object? value, Type type) return false; } - private void RecordObservedValueToWatchForChanges(OptionKey optionKey, string storageKey) + private void RecordObservedValueToWatchForChanges(OptionKey2 optionKey, string storageKey) { // We're about to fetch the value, so make sure that if it changes we'll know about it lock (_optionsToMonitorForChangesGate) { - var optionKeysToMonitor = _optionsToMonitorForChanges.GetOrAdd(storageKey, _ => new List()); + var optionKeysToMonitor = _optionsToMonitorForChanges.GetOrAdd(storageKey, _ => new List()); if (!optionKeysToMonitor.Contains(optionKey)) { @@ -252,7 +260,7 @@ private void RecordObservedValueToWatchForChanges(OptionKey optionKey, string st } } - public bool TryPersist(OptionKey optionKey, object? value) + public bool TryPersist(OptionKey2 optionKey, object? value) { if (_settingManager == null) { diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersisterProvider.cs b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersisterProvider.cs index 79970bbae5ce3..b0dd6db082efe 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersisterProvider.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioSettingsOptionPersisterProvider.cs @@ -20,14 +20,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options internal sealed class VisualStudioSettingsOptionPersisterProvider : IOptionPersisterProvider { private readonly IAsyncServiceProvider _serviceProvider; - private readonly IGlobalOptionService _optionService; + private readonly ILegacyGlobalOptionService _optionService; private VisualStudioSettingsOptionPersister? _lazyPersister; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioSettingsOptionPersisterProvider( [Import(typeof(SAsyncServiceProvider))] IAsyncServiceProvider serviceProvider, - IGlobalOptionService optionService) + ILegacyGlobalOptionService optionService) { _serviceProvider = serviceProvider; _optionService = optionService; diff --git a/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs b/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs index 093df9df5d846..940dd15c66475 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractAutomationObject.cs @@ -18,18 +18,18 @@ protected AbstractAutomationObject(Workspace workspace, string languageName) => (_workspace, _languageName) = (workspace, languageName); private protected T GetOption(PerLanguageOption2 key) - => _workspace.Options.GetOption(key, _languageName); + => (T)_workspace.Options.GetOption(new OptionKey(key, _languageName))!; private protected void SetOption(PerLanguageOption2 key, T value) => _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options .WithChangedOption(key, _languageName, value))); private protected T GetOption(Option2 key) - => _workspace.Options.GetOption(key); + => (T)_workspace.Options.GetOption(new OptionKey(key))!; private protected void SetOption(Option2 key, T value) => _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options - .WithChangedOption(key, value))); + .WithChangedOption(new OptionKey(key), value))); private protected int GetBooleanOption(PerLanguageOption2 key) => NullableBooleanToInteger(GetOption(key)); diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs index beb80cf8653dc..a7819194d2061 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs @@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options [System.ComponentModel.DesignerCategory("code")] // this must be fully qualified internal abstract class AbstractOptionPage : UIElementDialogPage { - private static ILegacyWorkspaceOptionService s_optionService; + private static ILegacyGlobalOptionService s_optionService; private static OptionStore s_optionStore; private static bool s_needsToUpdateOptionStore = true; @@ -30,8 +30,7 @@ private void EnsureOptionPageCreated() if (s_optionStore == null) { var componentModel = (IComponentModel)this.Site.GetService(typeof(SComponentModel)); - var workspace = componentModel.GetService(); - s_optionService = workspace.Services.GetService(); + s_optionService = componentModel.GetService(); s_optionStore = new OptionStore(new SolutionOptionSet(s_optionService)); } @@ -114,7 +113,8 @@ public override void SaveSettingsToStorage() // Must log the option change before setting the new option values via s_optionService, // otherwise oldOptions and newOptions would be identical and nothing will be logged. OptionLogger.Log(oldOptions, newOptions); - s_optionService.SetOptions(newOptions, newOptions.GetChangedOptions()); + var changedOptions = newOptions.GetChangedOptions(); + s_optionService.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined); // Make sure we load the next time a page is activated, in case that options changed // programmatically between now and the next time the page is activated diff --git a/src/VisualStudio/Core/Impl/Options/OptionStore.cs b/src/VisualStudio/Core/Impl/Options/OptionStore.cs index 91499c927f183..f269c319e44b4 100644 --- a/src/VisualStudio/Core/Impl/Options/OptionStore.cs +++ b/src/VisualStudio/Core/Impl/Options/OptionStore.cs @@ -27,8 +27,8 @@ public OptionStore(OptionSet optionSet) public object GetOption(OptionKey optionKey) => _optionSet.GetOption(optionKey); public T GetOption(OptionKey optionKey) => _optionSet.GetOption(optionKey); - internal T GetOption(Option2 option) => _optionSet.GetOption(option); - internal T GetOption(PerLanguageOption2 option, string language) => _optionSet.GetOption(option, language); + internal T GetOption(Option2 option) => _optionSet.GetOption((Option)option); + internal T GetOption(PerLanguageOption2 option, string language) => _optionSet.GetOption((PerLanguageOption)option, language); public OptionSet GetOptions() => _optionSet; public void SetOption(OptionKey optionKey, object value) diff --git a/src/VisualStudio/Core/Test.Next/Options/AllOptionsTests.cs b/src/VisualStudio/Core/Test.Next/Options/AllOptionsTests.cs new file mode 100644 index 0000000000000..511e5dfdbd742 --- /dev/null +++ b/src/VisualStudio/Core/Test.Next/Options/AllOptionsTests.cs @@ -0,0 +1,17 @@ +// 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.IO; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests; + +public class AllOptionsTests +{ + [Fact] + public void TestOptions() + { + OptionsTestInfo.CollectOptions(Path.GetDirectoryName(typeof(AllOptionsTests).Assembly.Location)); + } +} diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index a783aa82f028f..2d911c0de6015 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -183,7 +183,7 @@ void Method() workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); var ideAnalyzerOptions = IdeAnalyzerOptions.GetDefault(workspace.Services.SolutionServices.GetLanguageServices(LanguageNames.CSharp)); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(CSharpCodeStyleOptions.VarWhenTypeIsApparent), new CodeStyleOption(false, NotificationOption.Suggestion)); + workspace.GlobalOptions.SetGlobalOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent, new CodeStyleOption(false, NotificationOption.Suggestion)); // run analysis var project = workspace.CurrentSolution.Projects.First(); @@ -274,8 +274,8 @@ private static TestWorkspace CreateWorkspace(string language, string code, Parse ? TestWorkspace.CreateCSharp(code, parseOptions: options, composition: composition) : TestWorkspace.CreateVisualBasic(code, parseOptions: options, composition: composition); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution); - workspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), BackgroundAnalysisScope.FullSolution); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, BackgroundAnalysisScope.FullSolution); return workspace; } diff --git a/src/VisualStudio/Core/Test/Completion/CSharpCompletionSnippetNoteTests.vb b/src/VisualStudio/Core/Test/Completion/CSharpCompletionSnippetNoteTests.vb index e74a25fd3ce79..f76ed6b65c526 100644 --- a/src/VisualStudio/Core/Test/Completion/CSharpCompletionSnippetNoteTests.vb +++ b/src/VisualStudio/Core/Test/Completion/CSharpCompletionSnippetNoteTests.vb @@ -119,7 +119,7 @@ class C Dim testSnippetInfoService = DirectCast(state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService(Of ISnippetInfoService)(), TestCSharpSnippetInfoService) testSnippetInfoService.SetSnippetShortcuts({"for"}) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(InternalFeatureOnOffOptions.Snippets), False) + state.Workspace.GlobalOptions.SetGlobalOption(InternalFeatureOnOffOptions.Snippets, False) state.SendTypeChars("for") Await state.AssertCompletionSession() @@ -136,7 +136,7 @@ class C Dim testSnippetInfoService = DirectCast(state.Workspace.Services.GetLanguageServices(LanguageNames.CSharp).GetService(Of ISnippetInfoService)(), TestCSharpSnippetInfoService) testSnippetInfoService.SetSnippetShortcuts(snippetShortcuts) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp), False) + state.Workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.ShowNewSnippetExperienceUserOption, LanguageNames.CSharp, False) Return state End Function End Class diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 6ae8da303b8d5..0e360711c1322 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -362,7 +362,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Async Function TestExternalDiagnostics_CompilationAnalyzerWithFSAOn() As Task Using workspace = TestWorkspace.CreateCSharp(String.Empty, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) ' turn on FSA - workspace.GlobalOptions.SetGlobalOption(New OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), BackgroundAnalysisScope.FullSolution) + workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution) Dim analyzer = New CompilationAnalyzer() Dim compiler = DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp) diff --git a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb index 9afcdf922acb1..4da908bfe9c08 100644 --- a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb @@ -155,7 +155,7 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case Public Sub TestEditorConfigGeneratorToggleOptions() Using workspace = TestWorkspace.CreateVisualBasic("") - Dim changedOptions = workspace.Options.WithChangedOption(New OptionKey2(CodeStyleOptions2.PreferExplicitTupleNames, LanguageNames.VisualBasic), + Dim changedOptions = workspace.Options.WithChangedOption(CodeStyleOptions2.PreferExplicitTupleNames, LanguageNames.VisualBasic, New CodeStyleOption2(Of Boolean)(False, NotificationOption2.[Error])) Dim expectedText = "# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 05f4d30c33fea..9b7f345a84f2f 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -255,7 +255,7 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case Public Sub TestEditorConfigGeneratorToggleOptions() Using workspace = TestWorkspace.CreateCSharp("") - Dim changedOptions = workspace.Options.WithChangedOption(New OptionKey2(CodeStyleOptions2.PreferExplicitTupleNames, LanguageNames.CSharp), + Dim changedOptions = workspace.Options.WithChangedOption(CodeStyleOptions2.PreferExplicitTupleNames, LanguageNames.CSharp, New CodeStyleOption2(Of Boolean)(False, NotificationOption2.[Error])) Dim expectedText = "# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true diff --git a/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb b/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb index 46f5f9a8353f3..75ea24690f3b7 100644 --- a/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb +++ b/src/VisualStudio/Core/Test/Snippets/SnippetCompletionProviderTests.vb @@ -83,7 +83,7 @@ End Class.Value Dim testState = SnippetTestState.CreateTestState(markup, LanguageNames.VisualBasic, extraParts:={GetType(MockSnippetInfoService)}) Using testState Dim workspace = testState.Workspace - workspace.GlobalOptions.SetGlobalOption(New Options.OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) testState.SendTypeChars("'T") Await testState.AssertNoCompletionSession() End Using @@ -100,7 +100,7 @@ End Class.Value Dim testState = SnippetTestState.CreateTestState(markup, LanguageNames.VisualBasic, extraParts:={GetType(MockSnippetInfoService)}) Using testState Dim workspace = testState.Workspace - workspace.GlobalOptions.SetGlobalOption(New Options.OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) testState.SendTypeChars("'''T") Await testState.AssertNoCompletionSession() End Using @@ -116,7 +116,7 @@ End Class.Value Dim testState = SnippetTestState.CreateTestState(markup, LanguageNames.VisualBasic, extraParts:={GetType(MockSnippetInfoService)}) Using testState Dim workspace = testState.Workspace - workspace.GlobalOptions.SetGlobalOption(New Options.OptionKey(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic), SnippetsRule.AlwaysInclude) + workspace.GlobalOptions.SetGlobalOption(CompletionOptionsStorage.SnippetsBehavior, LanguageNames.VisualBasic, SnippetsRule.AlwaysInclude) testState.SendTypeChars("Shortcut") Await testState.AssertSelectedCompletionItem(displayText:="Shortcut") End Using diff --git a/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb b/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb index b94fc5cfead78..0d9233e5bde4e 100644 --- a/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb +++ b/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb @@ -42,7 +42,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Snippets excludedTypes:={GetType(IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession)), GetType(FormatCommandHandler)}.Concat(If(excludedTypes, {})).ToList(), includeFormatCommandHandler:=False) - Workspace.GlobalOptions.SetGlobalOption(New OptionKey(InternalFeatureOnOffOptions.Snippets), True) + Workspace.GlobalOptions.SetGlobalOption(InternalFeatureOnOffOptions.Snippets, True) Dim mockSVsServiceProvider = New Mock(Of SVsServiceProvider)(MockBehavior.Strict) mockSVsServiceProvider.Setup(Function(s) s.GetService(GetType(SVsTextManager))).Returns(Nothing) @@ -103,7 +103,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Snippets Dim state = New SnippetTestState(workspaceXml, languageName, startActiveSession, extraParts, excludedTypes:=Enumerable.Empty(Of Type), WorkspaceKind.Interactive) - state.Workspace.GlobalOptions.SetGlobalOption(New OptionKey(InternalFeatureOnOffOptions.Snippets), False) + state.Workspace.GlobalOptions.SetGlobalOption(InternalFeatureOnOffOptions.Snippets, False) Return state End Function diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpAddMissingUsingsOnPaste.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpAddMissingUsingsOnPaste.cs index b5a43beff355b..16a047fbacb8d 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpAddMissingUsingsOnPaste.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpAddMissingUsingsOnPaste.cs @@ -50,7 +50,7 @@ static void Main(string[] args) }", HangMitigatingCancellationToken); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp), false); + globalOptions.SetGlobalOption(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp, false); await PasteAsync(@"Task DoThingAsync() => Task.CompletedTask;", HangMitigatingCancellationToken); @@ -98,7 +98,7 @@ static void Main(string[] args) await using var telemetry = await TestServices.Telemetry.EnableTestTelemetryChannelAsync(HangMitigatingCancellationToken); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp), true); + globalOptions.SetGlobalOption(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp, true); await PasteAsync(@"Task DoThingAsync() => Task.CompletedTask;", HangMitigatingCancellationToken); @@ -145,7 +145,7 @@ static void Main(string[] args) }", HangMitigatingCancellationToken); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp), true); + globalOptions.SetGlobalOption(FeatureOnOffOptions.AddImportsOnPaste, LanguageNames.CSharp, true); await PasteAsync(@"Task DoThingAsync() => Task.CompletedTask;", HangMitigatingCancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpArgumentProvider.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpArgumentProvider.cs index dd6cda2d72026..22a836ac4c687 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpArgumentProvider.cs @@ -29,8 +29,8 @@ public override async Task InitializeAsync() await base.InitializeAsync().ConfigureAwait(true); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp), true); - globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.VisualBasic), true); + globalOptions.SetGlobalOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp, true); + globalOptions.SetGlobalOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.VisualBasic, true); } [IdeFact] diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpGoToDefinition.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpGoToDefinition.cs index 5450cc1fe636f..181ac950c2402 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpGoToDefinition.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpGoToDefinition.cs @@ -111,7 +111,7 @@ partial class PartialClass { int i = 0; }", HangMitigatingCancellationToken); public async Task GoToDefinitionFromMetadataCollapsed() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened, language: LanguageName), true); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened, language: LanguageName, true); await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "C.cs", cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "C.cs", HangMitigatingCancellationToken); @@ -142,7 +142,7 @@ public async Task GoToDefinitionFromMetadataNotCollapsed() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(BlockStructureOptionsStorage.CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened, language: LanguageName), false); + globalOptions.SetGlobalOption(BlockStructureOptionsStorage.CollapseSourceLinkEmbeddedDecompiledFilesWhenFirstOpened, language: LanguageName, false); await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "C.cs", cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "C.cs", HangMitigatingCancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpNavigationBar.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpNavigationBar.cs index 2bc6205268b82..28a8c45b932aa 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpNavigationBar.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpNavigationBar.cs @@ -128,10 +128,10 @@ void Bar() { } public async Task VerifyOption() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.CSharp), false); + globalOptions.SetGlobalOption(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.CSharp, false); Assert.False(await TestServices.Editor.IsNavigationBarEnabledAsync(HangMitigatingCancellationToken)); - globalOptions.SetGlobalOption(new OptionKey(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.CSharp), true); + globalOptions.SetGlobalOption(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.CSharp, true); Assert.True(await TestServices.Editor.IsNavigationBarEnabledAsync(HangMitigatingCancellationToken)); } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs index 2393ee22354cb..d8d5430912df5 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpSourceGenerators.cs @@ -86,7 +86,7 @@ public static void Main() configurationService.Clear(); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, language: null), true); + globalOptions.SetGlobalOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, true); await TestServices.Editor.GoToDefinitionAsync(HangMitigatingCancellationToken); Assert.Equal($"{HelloWorldGenerator.GeneratedEnglishClassName}.cs {ServicesVSResources.generated_suffix}", await TestServices.Shell.GetActiveWindowCaptionAsync(HangMitigatingCancellationToken)); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InheritanceMarginInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InheritanceMarginInProcess.cs index db6846d5f38d8..46d9ceb2302bf 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InheritanceMarginInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/InheritanceMarginInProcess.cs @@ -33,18 +33,18 @@ public async Task EnableOptionsAsync(string languageName, CancellationToken canc if (showInheritanceMargin != true) { - optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.ShowInheritanceMargin, languageName), true); + optionService.SetGlobalOption(FeatureOnOffOptions.ShowInheritanceMargin, languageName, true); } if (!showGlobalUsings) { - optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.InheritanceMarginIncludeGlobalImports, languageName), true); + optionService.SetGlobalOption(FeatureOnOffOptions.InheritanceMarginIncludeGlobalImports, languageName, true); } if (combinedWithIndicatorMargin) { // Glyphs in Indicator margin are owned by editor, and we don't know when the glyphs would be added/removed. - optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.InheritanceMarginCombinedWithIndicatorMargin), false); + optionService.SetGlobalOption(FeatureOnOffOptions.InheritanceMarginCombinedWithIndicatorMargin, false); } } @@ -56,12 +56,12 @@ public async Task DisableOptionsAsync(string languageName, CancellationToken can if (showInheritanceMargin != false) { - optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.ShowInheritanceMargin, languageName), false); + optionService.SetGlobalOption(FeatureOnOffOptions.ShowInheritanceMargin, languageName, false); } if (showGlobalUsings) { - optionService.SetGlobalOption(new OptionKey(FeatureOnOffOptions.InheritanceMarginIncludeGlobalImports, languageName), false); + optionService.SetGlobalOption(FeatureOnOffOptions.InheritanceMarginIncludeGlobalImports, languageName, false); } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs index 9e746fb0ef35f..9813069e60ada 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/StateResetInProcess.cs @@ -53,13 +53,13 @@ public async Task ResetGlobalOptionsAsync(CancellationToken cancellationToken) static void ResetOption(IGlobalOptionService globalOptions, Option2 option) { - globalOptions.SetGlobalOption(new OptionKey(option, language: null), option.DefaultValue); + globalOptions.SetGlobalOption(option, option.DefaultValue); } static void ResetPerLanguageOption(IGlobalOptionService globalOptions, PerLanguageOption2 option) { - globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.CSharp), option.DefaultValue); - globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.VisualBasic), option.DefaultValue); + globalOptions.SetGlobalOption(option, LanguageNames.CSharp, option.DefaultValue); + globalOptions.SetGlobalOption(option, LanguageNames.VisualBasic, option.DefaultValue); } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs index d1183cf1d32d8..c61313cb8abc2 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/WorkspaceInProcess.cs @@ -64,15 +64,14 @@ public async Task SetPrettyListingAsync(string languageName, bool value, Cancell await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var globalOptions = await GetComponentModelServiceAsync(cancellationToken); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, languageName), value); + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, languageName, value); } public async Task SetFileScopedNamespaceAsync(bool value, CancellationToken cancellationToken) { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(cancellationToken); - globalOptions.SetGlobalOption( - new OptionKey(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations), + globalOptions.SetGlobalOption(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2(value ? NamespaceDeclarationPreference.FileScoped : NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Suggestion)); } @@ -82,12 +81,12 @@ public async Task SetFullSolutionAnalysisAsync(bool value, CancellationToken can var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(cancellationToken); var scope = value ? BackgroundAnalysisScope.FullSolution : BackgroundAnalysisScope.Default; - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, scope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, scope); var compilerScope = value ? CompilerDiagnosticsScope.FullSolution : CompilerDiagnosticsScope.OpenFiles; - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp), compilerScope); - globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.VisualBasic), compilerScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp, compilerScope); + globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.VisualBasic, compilerScope); } public Task WaitForAsyncOperationsAsync(string featuresToWaitFor, CancellationToken cancellationToken) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj index 2ee0e8902a2e1..43eb3d7562387 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Microsoft.VisualStudio.LanguageServices.New.IntegrationTests.csproj @@ -12,7 +12,6 @@ - @@ -44,6 +43,7 @@ + diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/Options/GlobalOptionsTest.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Options/GlobalOptionsTest.cs new file mode 100644 index 0000000000000..62641fdba168c --- /dev/null +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/Options/GlobalOptionsTest.cs @@ -0,0 +1,68 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.VisualStudio.IntegrationTest.Utilities; +using Roslyn.Test.Utilities; +using Roslyn.VisualStudio.IntegrationTests; +using Xunit; + +namespace Roslyn.VisualStudio.NewIntegrationTests.Options; + +public sealed class GlobalOptionsTest : AbstractIntegrationTest +{ + public GlobalOptionsTest() + { + } + + [IdeFact] + public async Task ValidateAllOptions() + { + var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); + + var optionsInfo = OptionsTestInfo.CollectOptions(Path.GetDirectoryName(typeof(GlobalOptionsTest).Assembly.Location!)); + var allLanguages = new[] { LanguageNames.CSharp, LanguageNames.VisualBasic }; + var noLanguages = new[] { (string?)null }; + + foreach (var (option, _, _, _) in optionsInfo.GlobalOptions.Values) + { + foreach (var language in option.IsPerLanguage ? allLanguages : noLanguages) + { + var key = new OptionKey2(option, language); + var currentValue = globalOptions.GetOption(key); + + // do not attempt to update feature flags + if (option.StorageLocations.Any(s => s is FeatureFlagStorageLocation)) + { + Assert.True(currentValue is bool); + continue; + } + + var differentValue = OptionsTestHelpers.GetDifferentValue(option.Type, currentValue); + globalOptions.SetGlobalOption(key, differentValue); + + object? updatedValue; + + try + { + updatedValue = globalOptions.GetOption(key); + } + finally + { + globalOptions.SetGlobalOption(key, currentValue); + } + + Assert.Equal(differentValue, updatedValue); + } + } + } +} diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicArgumentProvider.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicArgumentProvider.cs index 6e5e3dea0265f..0afebe77b9e46 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicArgumentProvider.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicArgumentProvider.cs @@ -28,8 +28,8 @@ public override async Task InitializeAsync() await base.InitializeAsync().ConfigureAwait(true); var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp), true); - globalOptions.SetGlobalOption(new OptionKey(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.VisualBasic), true); + globalOptions.SetGlobalOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.CSharp, true); + globalOptions.SetGlobalOption(CompletionViewOptions.EnableArgumentCompletionSnippets, LanguageNames.VisualBasic, true); } [IdeFact] diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicGoToDefinition.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicGoToDefinition.cs index ffe487f0de7d4..884459c6d047f 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicGoToDefinition.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicGoToDefinition.cs @@ -56,13 +56,13 @@ await SetUpEditorAsync( @"Class C Dim i As Integer$$ End Class", HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(VisualStudioNavigationOptions.NavigateToObjectBrowser, LanguageNames.VisualBasic), true); + globalOptions.SetGlobalOption(VisualStudioNavigationOptions.NavigateToObjectBrowser, LanguageNames.VisualBasic, true); await TestServices.Editor.GoToDefinitionAsync(HangMitigatingCancellationToken); Assert.Equal("Object Browser", await TestServices.Shell.GetActiveWindowCaptionAsync(HangMitigatingCancellationToken)); - globalOptions.SetGlobalOption(new OptionKey(VisualStudioNavigationOptions.NavigateToObjectBrowser, LanguageNames.VisualBasic), false); - globalOptions.SetGlobalOption(new OptionKey(MetadataAsSourceOptionsStorage.NavigateToDecompiledSources, language: null), false); + globalOptions.SetGlobalOption(VisualStudioNavigationOptions.NavigateToObjectBrowser, LanguageNames.VisualBasic, false); + globalOptions.SetGlobalOption(MetadataAsSourceOptionsStorage.NavigateToDecompiledSources, false); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class1.vb", HangMitigatingCancellationToken); await TestServices.Editor.GoToDefinitionAsync(HangMitigatingCancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicLineCommit.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicLineCommit.cs index 2abad245e6987..7b1032dbd0a4c 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicLineCommit.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicLineCommit.cs @@ -146,7 +146,7 @@ End Sub public async Task CommitOnFocusLostDoesNotFormatWithPrettyListingOff() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic), false); + globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, LanguageNames.VisualBasic, false); await TestServices.Editor.SetTextAsync(@"Module M Sub M() diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs index 6b584a5795d36..a9c04f5549a5b 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicNavigationBar.cs @@ -104,10 +104,10 @@ public async Task VerifyOption() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); - globalOptions.SetGlobalOption(new OptionKey(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.VisualBasic), false); + globalOptions.SetGlobalOption(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.VisualBasic, false); Assert.False(await TestServices.Editor.IsNavigationBarEnabledAsync(HangMitigatingCancellationToken)); - globalOptions.SetGlobalOption(new OptionKey(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.VisualBasic), true); + globalOptions.SetGlobalOption(NavigationBarViewOptionsStorage.ShowNavigationBar, LanguageNames.VisualBasic, true); Assert.True(await TestServices.Editor.IsNavigationBarEnabledAsync(HangMitigatingCancellationToken)); } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs index e28a45d8ac2b9..47c55cc3afa7d 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs @@ -47,11 +47,10 @@ public bool IsPrettyListingOn(string languageName) => _globalOptions.GetOption(FeatureOnOffOptions.PrettyListing, languageName); public void SetPrettyListing(string languageName, bool value) - => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, languageName), value)); + => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption(FeatureOnOffOptions.PrettyListing, languageName, value)); public void SetFileScopedNamespaces(bool value) - => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption( - new OptionKey(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations), + => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations, new CodeStyleOption2(value ? NamespaceDeclarationPreference.FileScoped : NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Suggestion))); public void SetGlobalOption(WellKnownGlobalOption option, string? language, object? value) @@ -133,16 +132,16 @@ public void ResetOptions() return; // Local function - void ResetOption(IOption option) + void ResetOption(IOption2 option) { - if (option is IPerLanguageValuedOption) + if (option.IsPerLanguage) { - _globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.CSharp), option.DefaultValue); - _globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.VisualBasic), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey2(option, LanguageNames.CSharp), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey2(option, LanguageNames.VisualBasic), option.DefaultValue); } else { - _globalOptions.SetGlobalOption(new OptionKey(option), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey2(option, language: null), option.DefaultValue); } } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownGlobalOptions.cs b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownGlobalOptions.cs index 1ff64f52b6c2b..239ebc0865ee7 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownGlobalOptions.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/WellKnownGlobalOptions.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.IntegrationTest.Utilities /// /// Options settable by integration tests. /// - /// TODO: Options are currently explicitly listed since is not serializable. + /// TODO: Options are currently explicitly listed since is not serializable. /// https://github.com/dotnet/roslyn/issues/59267 /// public enum WellKnownGlobalOption @@ -38,9 +38,9 @@ public enum WellKnownGlobalOption InlineRenameSessionOptions_UseNewUI, } - public static class WellKnownGlobalOptions + internal static class WellKnownGlobalOptions { - public static IOption GetOption(this WellKnownGlobalOption option) + public static IOption2 GetOption(this WellKnownGlobalOption option) => option switch { WellKnownGlobalOption.CompletionOptions_ShowItemsFromUnimportedNamespaces => CompletionOptionsStorage.ShowItemsFromUnimportedNamespaces, @@ -59,7 +59,7 @@ public static IOption GetOption(this WellKnownGlobalOption option) _ => throw ExceptionUtilities.Unreachable() }; - public static OptionKey GetKey(this WellKnownGlobalOption option, string? language) - => new OptionKey(GetOption(option), language); + public static OptionKey2 GetKey(this WellKnownGlobalOption option, string? language) + => new OptionKey2(GetOption(option), language); } } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/IUnitTestingIncrementalAnalyzerImplementation.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/IUnitTestingIncrementalAnalyzerImplementation.cs index 701cc62a1bfbc..9b9b23b2df17c 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/IUnitTestingIncrementalAnalyzerImplementation.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/IUnitTestingIncrementalAnalyzerImplementation.cs @@ -21,8 +21,5 @@ internal interface IUnitTestingIncrementalAnalyzerImplementation Task AnalyzeProjectAsync(Project project, bool semanticsChanged, UnitTestingInvocationReasonsWrapper reasons, CancellationToken cancellationToken); void RemoveDocument(DocumentId documentId); void RemoveProject(ProjectId projectId); - - [Obsolete] - bool NeedsReanalysisOnOptionChanged(object sender, UnitTestingOptionChangedEventArgsWrapper e); } } diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingOptionChangedEventArgsWrapper.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingOptionChangedEventArgsWrapper.cs deleted file mode 100644 index a19019f6a2dc1..0000000000000 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingOptionChangedEventArgsWrapper.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.Api -{ - [Obsolete] - internal readonly struct UnitTestingOptionChangedEventArgsWrapper - { - internal OptionChangedEventArgs UnderlyingObject { get; } - - public UnitTestingOptionChangedEventArgsWrapper(OptionChangedEventArgs underlyingObject) - => UnderlyingObject = underlyingObject ?? throw new ArgumentNullException(nameof(underlyingObject)); - } -} diff --git a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs index cd4869c371d6c..af463cc9a4893 100644 --- a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs @@ -90,9 +90,6 @@ private bool TryGetAnalyzerConfigOption(OptionKey option, out object? value) public T GetOption(PerLanguageOption option) => GetOption(option, _language); - internal T GetOption(PerLanguageOption2 option) - => GetOption(option, _language); - public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) => new DocumentOptionSet(_configOptions, _underlyingOptions, _language, _values.SetItem(optionAndLanguage, value)); @@ -102,12 +99,6 @@ public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? public DocumentOptionSet WithChangedOption(PerLanguageOption option, T value) => (DocumentOptionSet)WithChangedOption(option, _language, value); - /// - /// Creates a new that contains the changed value. - /// - internal DocumentOptionSet WithChangedOption(PerLanguageOption2 option, T value) - => (DocumentOptionSet)WithChangedOption(option, _language, value); - private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IEditorConfigOptionMappingService optionService, string? language) { Debug.Assert((language ?? _language) == _language, $"Use of a {nameof(DocumentOptionSet)} is not expected to differ from the language it was constructed with."); diff --git a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigFileGenerator.cs b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigFileGenerator.cs index 7a69ccbf0e57a..c2e7e000d986a 100644 --- a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigFileGenerator.cs +++ b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigFileGenerator.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.Options @@ -43,7 +44,7 @@ public static string Generate( AppendOptionsToEditorConfig(optionSet, feature, options, language, editorconfig); } - var namingStylePreferences = optionSet.GetOption(NamingStyleOptions.NamingPreferences, language); + var namingStylePreferences = optionSet.GetOption((PerLanguageOption)NamingStyleOptions.NamingPreferences, language); AppendNamingStylePreferencesToEditorConfig(namingStylePreferences, language, editorconfig); return editorconfig.ToString(); diff --git a/src/Workspaces/Core/Portable/Options/EmptyOptionSet.cs b/src/Workspaces/Core/Portable/Options/EmptyOptionSet.cs new file mode 100644 index 0000000000000..1ec1f34a10787 --- /dev/null +++ b/src/Workspaces/Core/Portable/Options/EmptyOptionSet.cs @@ -0,0 +1,23 @@ +// 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.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Options; + +public abstract partial class OptionSet +{ + private sealed class EmptyOptionSet : OptionSet + { + private protected override object? GetOptionCore(OptionKey optionKey) + => optionKey.Option.DefaultValue; + + public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) + => throw new NotSupportedException(); + + internal override IEnumerable GetChangedOptions(OptionSet optionSet) + => Array.Empty(); + } +} diff --git a/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs b/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs index f76b3860aaa02..5bd03fb1061fe 100644 --- a/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs @@ -8,15 +8,9 @@ using System.Composition; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -28,35 +22,27 @@ internal sealed class GlobalOptionService : IGlobalOptionService private readonly IWorkspaceThreadingService? _workspaceThreadingService; private readonly ImmutableArray> _optionPersisterProviders; - // access is interlocked - private ImmutableArray _registeredWorkspaces; - private readonly object _gate = new(); - private readonly Func _getOption; #region Guarded by _gate private ImmutableArray _lazyOptionPersisters; - - private ImmutableDictionary _currentValues; - private ImmutableHashSet _changedOptionKeys; + private ImmutableDictionary _currentValues; #endregion + public event EventHandler? OptionChanged; + [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public GlobalOptionService( [Import(AllowDefault = true)] IWorkspaceThreadingService? workspaceThreadingService, [ImportMany] IEnumerable> optionPersisters) { - _getOption = GetOption; - _workspaceThreadingService = workspaceThreadingService; _optionPersisterProviders = optionPersisters.ToImmutableArray(); - _registeredWorkspaces = ImmutableArray.Empty; - _currentValues = ImmutableDictionary.Create(); - _changedOptionKeys = ImmutableHashSet.Empty; + _currentValues = ImmutableDictionary.Create(); } private ImmutableArray GetOptionPersisters() @@ -100,7 +86,7 @@ static async Task> GetOptionPersistersAsync( } } - private static object? LoadOptionFromPersisterOrGetDefault(OptionKey optionKey, ImmutableArray persisters) + private static object? LoadOptionFromPersisterOrGetDefault(OptionKey2 optionKey, ImmutableArray persisters) { foreach (var persister in persisters) { @@ -116,23 +102,23 @@ static async Task> GetOptionPersistersAsync( } public T GetOption(Option2 option) - => OptionsHelpers.GetOption(option, _getOption); + => GetOption(new OptionKey2(option)); - public T GetOption(PerLanguageOption2 option, string? language) - => OptionsHelpers.GetOption(option, language, _getOption); + public T GetOption(PerLanguageOption2 option, string language) + => GetOption(new OptionKey2(option, language)); - public object? GetOption(OptionKey optionKey) + public T GetOption(OptionKey2 optionKey) { // Ensure the option persisters are available before taking the global lock var persisters = GetOptionPersisters(); lock (_gate) { - return GetOption_NoLock(optionKey, persisters); + return (T)GetOption_NoLock(optionKey, persisters)!; } } - public ImmutableArray GetOptions(ImmutableArray optionKeys) + public ImmutableArray GetOptions(ImmutableArray optionKeys) { // Ensure the option persisters are available before taking the global lock var persisters = GetOptionPersisters(); @@ -149,7 +135,7 @@ public T GetOption(PerLanguageOption2 option, string? language) return values.ToImmutableAndClear(); } - private object? GetOption_NoLock(OptionKey optionKey, ImmutableArray persisters) + private object? GetOption_NoLock(OptionKey2 optionKey, ImmutableArray persisters) { if (_currentValues.TryGetValue(optionKey, out var value)) { @@ -160,93 +146,56 @@ public T GetOption(PerLanguageOption2 option, string? language) _currentValues = _currentValues.Add(optionKey, value); - // Track options with non-default values from serializers as changed options. - if (!object.Equals(value, optionKey.Option.DefaultValue)) - { - _changedOptionKeys = _changedOptionKeys.Add(optionKey); - } - return value; } - private void SetOptionCore(OptionKey optionKey, object? newValue) - { - _currentValues = _currentValues.SetItem(optionKey, newValue); - _changedOptionKeys = _changedOptionKeys.Add(optionKey); - } + public void SetGlobalOption(Option2 option, T value) + => SetGlobalOption(new OptionKey2(option), value); - public void SetGlobalOption(OptionKey optionKey, object? value) - => SetGlobalOptions(ImmutableArray.Create(optionKey), ImmutableArray.Create(value)); + public void SetGlobalOption(PerLanguageOption2 option, string language, T value) + => SetGlobalOption(new OptionKey2(option, language), value); - public void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values) - { - Contract.ThrowIfFalse(optionKeys.Length == values.Length); + public void SetGlobalOption(OptionKey2 optionKey, object? value) + => SetGlobalOptions(OneOrMany.Create(KeyValuePairUtil.Create(optionKey, value))); + + public bool SetGlobalOptions(ImmutableArray> options) + => SetGlobalOptions(OneOrMany.Create(options)); + private bool SetGlobalOptions(OneOrMany> options) + { var changedOptions = new List(); var persisters = GetOptionPersisters(); lock (_gate) { - for (var i = 0; i < optionKeys.Length; i++) + foreach (var (optionKey, value) in options) { - var optionKey = optionKeys[i]; - var value = values[i]; - var existingValue = GetOption_NoLock(optionKey, persisters); if (Equals(value, existingValue)) { continue; } - // not updating _changedOptionKeys since that's only relevant for serializable options, not global ones _currentValues = _currentValues.SetItem(optionKey, value); changedOptions.Add(new OptionChangedEventArgs(optionKey, value)); } } - for (var i = 0; i < optionKeys.Length; i++) + if (changedOptions.Count == 0) { - PersistOption(persisters, optionKeys[i], values[i]); + return false; } - RaiseOptionChangedEvent(changedOptions); - } - - public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) - { - var changedOptions = new List(); - - lock (_gate) - { - foreach (var optionKey in optionKeys) - { - var newValue = optionSet.GetOption(optionKey); - var currentValue = GetOption(optionKey); - - if (Equals(currentValue, newValue)) - { - // Identical, so nothing is changing - continue; - } - - // The value is actually changing, so update - changedOptions.Add(new OptionChangedEventArgs(optionKey, newValue)); - - SetOptionCore(optionKey, newValue); - } - } - - var persisters = GetOptionPersisters(); foreach (var changedOption in changedOptions) { PersistOption(persisters, changedOption.OptionKey, changedOption.Value); } - // Outside of the lock, raise the events on our task queue. - UpdateRegisteredWorkspacesAndRaiseEvents(changedOptions); + RaiseOptionChangedEvent(changedOptions); + return true; } - private static void PersistOption(ImmutableArray persisters, OptionKey optionKey, object? value) + private static void PersistOption(ImmutableArray persisters, OptionKey2 optionKey, object? value) { foreach (var persister in persisters) { @@ -257,44 +206,31 @@ private static void PersistOption(ImmutableArray persisters, O } } - public void RefreshOption(OptionKey optionKey, object? newValue) + public bool RefreshOption(OptionKey2 optionKey, object? newValue) { lock (_gate) { if (_currentValues.TryGetValue(optionKey, out var oldValue)) { - if (object.Equals(oldValue, newValue)) + if (Equals(oldValue, newValue)) { // Value is still the same, no reason to raise events - return; + return false; } } - SetOptionCore(optionKey, newValue); - } - - UpdateRegisteredWorkspacesAndRaiseEvents(new List { new OptionChangedEventArgs(optionKey, newValue) }); - } - - private void UpdateRegisteredWorkspacesAndRaiseEvents(List changedOptions) - { - if (changedOptions.Count == 0) - { - return; - } - - // Ensure that the Workspace's CurrentSolution snapshot is updated with new options for all registered workspaces - // prior to raising option changed event handlers. - foreach (var workspace in _registeredWorkspaces) - { - workspace.UpdateCurrentSolutionOnOptionsChanged(); + _currentValues = _currentValues.SetItem(optionKey, newValue); } + var changedOptions = new List { new OptionChangedEventArgs(optionKey, newValue) }; RaiseOptionChangedEvent(changedOptions); + return true; } private void RaiseOptionChangedEvent(List changedOptions) { + Debug.Assert(changedOptions.Count > 0); + // Raise option changed events. var optionChanged = OptionChanged; if (optionChanged != null) @@ -305,13 +241,5 @@ private void RaiseOptionChangedEvent(List changedOptions } } } - - public void RegisterWorkspace(Workspace workspace) - => ImmutableInterlocked.Update(ref _registeredWorkspaces, (workspaces, workspace) => workspaces.Add(workspace), workspace); - - public void UnregisterWorkspace(Workspace workspace) - => ImmutableInterlocked.Update(ref _registeredWorkspaces, (workspaces, workspace) => workspaces.Remove(workspace), workspace); - - public event EventHandler? OptionChanged; } } diff --git a/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs b/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs index 1b38f4379bcf2..1af3ed131849d 100644 --- a/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs @@ -25,56 +25,49 @@ internal interface IGlobalOptionService /// /// Gets the current value of the specific option. /// - T GetOption(PerLanguageOption2 option, string? languageName); + T GetOption(PerLanguageOption2 option, string language); /// /// Gets the current value of the specific option. /// - object? GetOption(OptionKey optionKey); + T GetOption(OptionKey2 optionKey); /// /// Gets the current values of specified options. /// All options are read atomically. /// - ImmutableArray GetOptions(ImmutableArray optionKeys); + ImmutableArray GetOptions(ImmutableArray optionKeys); - /// - /// Applies a set of options. - /// If any option changed its value invokes registered option persisters, updates current solutions of all registered workspaces and triggers . - /// - void SetOptions(OptionSet optionSet, IEnumerable optionKeys); + void SetGlobalOption(Option2 option, T value); + + void SetGlobalOption(PerLanguageOption2 option, string language, T value); /// /// Sets and persists the value of a global option. /// Sets the value of a global option. /// Invokes registered option persisters. /// Triggers . - /// Does not update any workspace (since this option is not a solution option). /// - void SetGlobalOption(OptionKey optionKey, object? value); + void SetGlobalOption(OptionKey2 optionKey, object? value); /// /// Atomically sets the values of specified global options. The option values are persisted. /// Triggers . - /// Does not update any workspace (since this option is not a solution option). /// - void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values); + /// + /// Returns true if any option changed its value stored in the global options. + /// + bool SetGlobalOptions(ImmutableArray> options); event EventHandler? OptionChanged; /// - /// Refreshes the stored value of a serialized option. This should only be called from serializers. - /// - void RefreshOption(OptionKey optionKey, object? newValue); - - /// - /// Registers a workspace with the option service. - /// - void RegisterWorkspace(Workspace workspace); - - /// - /// Unregisters a workspace from the option service. + /// Refreshes the stored value of an option. This should only be called from persisters. + /// Does not persist the new option value. /// - void UnregisterWorkspace(Workspace workspace); + /// + /// Returns true if the option changed its value stored in the global options. + /// + bool RefreshOption(OptionKey2 optionKey, object? newValue); } } diff --git a/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs b/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs index 3714664667bd4..5545fdc9aad74 100644 --- a/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs @@ -2,12 +2,8 @@ // 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.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis.Options; @@ -17,12 +13,21 @@ namespace Microsoft.CodeAnalysis.Options; /// and . /// internal interface ILegacyWorkspaceOptionService : IWorkspaceService +{ + ILegacyGlobalOptionService LegacyGlobalOptions { get; } +} + +internal interface ILegacyGlobalOptionService { IGlobalOptionService GlobalOptions { get; } void RegisterWorkspace(Workspace workspace); void UnregisterWorkspace(Workspace workspace); + void UpdateRegisteredWorkspaces(); object? GetOption(OptionKey key); - void SetOptions(OptionSet optionSet, IEnumerable optionKeys); + + void SetOptions( + ImmutableArray> internallyDefinedOptions, + ImmutableArray> externallyDefinedOptions); } diff --git a/src/Workspaces/Core/Portable/Options/IOptionPersister.cs b/src/Workspaces/Core/Portable/Options/IOptionPersister.cs index 9f227e941f3af..7e1319f9dd3dc 100644 --- a/src/Workspaces/Core/Portable/Options/IOptionPersister.cs +++ b/src/Workspaces/Core/Portable/Options/IOptionPersister.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Options /// internal interface IOptionPersister { - bool TryFetch(OptionKey optionKey, out object? value); - bool TryPersist(OptionKey optionKey, object? value); + bool TryFetch(OptionKey2 optionKey, out object? value); + bool TryPersist(OptionKey2 optionKey, object? value); } } diff --git a/src/Workspaces/Core/Portable/Options/LegacyWorkspaceOptionService.cs b/src/Workspaces/Core/Portable/Options/LegacyWorkspaceOptionService.cs new file mode 100644 index 0000000000000..3d77fe2f2c7a9 --- /dev/null +++ b/src/Workspaces/Core/Portable/Options/LegacyWorkspaceOptionService.cs @@ -0,0 +1,123 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.Host.Mef; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Options; + +[Export(typeof(ILegacyGlobalOptionService)), Shared] +internal sealed class LegacyGlobalOptionService : ILegacyGlobalOptionService +{ + [ExportWorkspaceService(typeof(ILegacyWorkspaceOptionService)), Shared] + internal sealed class WorkspaceService : ILegacyWorkspaceOptionService + { + public ILegacyGlobalOptionService LegacyGlobalOptions { get; } + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public WorkspaceService(ILegacyGlobalOptionService legacyGlobalOptions) + => LegacyGlobalOptions = legacyGlobalOptions; + } + + public IGlobalOptionService GlobalOptions { get; } + + // access is interlocked + private ImmutableArray _registeredWorkspaces; + + /// + /// Stores options that are not defined by Roslyn and do not implement . + /// + private ImmutableDictionary _currentExternallyDefinedOptionValues; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public LegacyGlobalOptionService(IGlobalOptionService globalOptionService) + { + GlobalOptions = globalOptionService; + _registeredWorkspaces = ImmutableArray.Empty; + _currentExternallyDefinedOptionValues = ImmutableDictionary.Create(); + } + + public object? GetOption(OptionKey key) + { + if (key.Option is IOption2 internallyDefinedOption) + { + return GlobalOptions.GetOption(new OptionKey2(internallyDefinedOption, key.Language)); + } + + if (_currentExternallyDefinedOptionValues.TryGetValue(key, out var value)) + { + return value; + } + + return key.Option.DefaultValue; + } + + /// + /// Sets values of options that may be stored in (public options). + /// Clears of registered workspaces so that next time + /// are queried for the options new values are fetched from + /// . + /// + public void SetOptions( + ImmutableArray> internallyDefinedOptions, + ImmutableArray> externallyDefinedOptions) + { + var anyExternallyDefinedOptionChanged = false; + foreach (var (optionKey, value) in externallyDefinedOptions) + { + var existingValue = GetOption(optionKey); + if (Equals(value, existingValue)) + { + continue; + } + + anyExternallyDefinedOptionChanged = true; + + ImmutableInterlocked.Update( + ref _currentExternallyDefinedOptionValues, + static (options, arg) => options.SetItem(arg.optionKey, arg.value), + (optionKey, value)); + } + + // Update workspaces even when value of public internally defined options have not actually changed. + // This is necessary since these options may have been changed previously directly via IGlobalOptionService, + // without updating the workspaces and thus the values stored in IGlobalOptionService may not match the values + // stored on current solution snapshots. + // + // Updating workspaces more often than strictly needed is not a functional issue - + // it's just adding a bit of extra overhead since the options need to be re-read from global options. + if (!internallyDefinedOptions.IsEmpty || anyExternallyDefinedOptionChanged) + { + UpdateRegisteredWorkspaces(); + } + + // Update global options after updating registered workspaces, + // so that the handler of the changed event has access to the updated values through the current solution. + GlobalOptions.SetGlobalOptions(internallyDefinedOptions); + } + + public void UpdateRegisteredWorkspaces() + { + // Ensure that the Workspace's CurrentSolution snapshot is updated with new options for all registered workspaces + // prior to raising option changed event handlers. + foreach (var workspace in _registeredWorkspaces) + { + workspace.UpdateCurrentSolutionOnOptionsChanged(); + } + } + + public void RegisterWorkspace(Workspace workspace) + => ImmutableInterlocked.Update(ref _registeredWorkspaces, (workspaces, workspace) => workspaces.Add(workspace), workspace); + + public void UnregisterWorkspace(Workspace workspace) + => ImmutableInterlocked.Update(ref _registeredWorkspaces, (workspaces, workspace) => workspaces.Remove(workspace), workspace); + +} diff --git a/src/Workspaces/Core/Portable/Options/OptionChangedEventArgs.cs b/src/Workspaces/Core/Portable/Options/OptionChangedEventArgs.cs index b967e4964066f..b4f3dc098a5e5 100644 --- a/src/Workspaces/Core/Portable/Options/OptionChangedEventArgs.cs +++ b/src/Workspaces/Core/Portable/Options/OptionChangedEventArgs.cs @@ -8,10 +8,10 @@ namespace Microsoft.CodeAnalysis.Options { internal sealed class OptionChangedEventArgs : EventArgs { - public OptionKey OptionKey { get; } + public OptionKey2 OptionKey { get; } public object? Value { get; } - internal OptionChangedEventArgs(OptionKey optionKey, object? value) + internal OptionChangedEventArgs(OptionKey2 optionKey, object? value) { OptionKey = optionKey; Value = value; diff --git a/src/Workspaces/Core/Portable/Options/OptionKey.cs b/src/Workspaces/Core/Portable/Options/OptionKey.cs index 7a810101412e3..8a5738e228a00 100644 --- a/src/Workspaces/Core/Portable/Options/OptionKey.cs +++ b/src/Workspaces/Core/Portable/Options/OptionKey.cs @@ -19,17 +19,23 @@ namespace Microsoft.CodeAnalysis.Options public OptionKey(IOption option, string? language = null) { + if (option is null) + { + throw new ArgumentNullException(nameof(option)); + } + if (language != null && !option.IsPerLanguage) { throw new ArgumentException(WorkspacesResources.A_language_name_cannot_be_specified_for_this_option); } - else if (language == null && option.IsPerLanguage) + + if (language == null && option.IsPerLanguage) { throw new ArgumentNullException(WorkspacesResources.A_language_name_must_be_specified_for_this_option); } - this.Option = option ?? throw new ArgumentNullException(nameof(option)); - this.Language = language; + Option = option; + Language = language; } public override bool Equals(object? obj) diff --git a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs deleted file mode 100644 index 074db2ad944ad..0000000000000 --- a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.Collections.Generic; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Options; - -[ExportWorkspaceService(typeof(ILegacyWorkspaceOptionService)), Shared] -internal sealed class LegacyWorkspaceOptionService : ILegacyWorkspaceOptionService -{ - public IGlobalOptionService GlobalOptions { get; } - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public LegacyWorkspaceOptionService(IGlobalOptionService globalOptionService) - => GlobalOptions = globalOptionService; - - public void RegisterWorkspace(Workspace workspace) - => GlobalOptions.RegisterWorkspace(workspace); - - public void UnregisterWorkspace(Workspace workspace) - => GlobalOptions.UnregisterWorkspace(workspace); - - public object? GetOption(OptionKey key) - => GlobalOptions.GetOption(key); - - public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) - => GlobalOptions.SetOptions(optionSet, optionKeys); -} diff --git a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs index 85ae75e0479e5..25009b1bc2095 100644 --- a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs +++ b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs @@ -19,9 +19,9 @@ private sealed class AnalyzerConfigOptionsImpl : StructuredAnalyzerConfigOptions { private readonly OptionSet _optionSet; private readonly IEditorConfigOptionMappingService _optionMappingService; - private readonly string? _language; + private readonly string _language; - public AnalyzerConfigOptionsImpl(OptionSet optionSet, IEditorConfigOptionMappingService optionMappingService, string? language) + public AnalyzerConfigOptionsImpl(OptionSet optionSet, IEditorConfigOptionMappingService optionMappingService, string language) { _optionSet = optionSet; _optionMappingService = optionMappingService; @@ -29,7 +29,7 @@ public AnalyzerConfigOptionsImpl(OptionSet optionSet, IEditorConfigOptionMapping } public override NamingStylePreferences GetNamingStylePreferences() - => _optionSet.GetOption(NamingStyleOptions.NamingPreferences, _language); + => _optionSet.GetOption((PerLanguageOption)NamingStyleOptions.NamingPreferences, _language); public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { diff --git a/src/Workspaces/Core/Portable/Options/OptionSet.cs b/src/Workspaces/Core/Portable/Options/OptionSet.cs index 21d985d95a34f..59a8777d6881b 100644 --- a/src/Workspaces/Core/Portable/Options/OptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/OptionSet.cs @@ -11,7 +11,8 @@ namespace Microsoft.CodeAnalysis.Options { public abstract partial class OptionSet { - private const string NoLanguageSentinel = "\0"; + internal static readonly OptionSet Empty = new EmptyOptionSet(); + private static readonly ImmutableDictionary s_emptyAnalyzerConfigOptions = ImmutableDictionary.Create(StringComparer.Ordinal); @@ -20,11 +21,8 @@ public abstract partial class OptionSet /// private ImmutableDictionary _lazyAnalyzerConfigOptions = s_emptyAnalyzerConfigOptions; - private readonly Func _getOptionCore; - protected OptionSet() { - _getOptionCore = GetOptionCore; } private protected abstract object? GetOptionCore(OptionKey optionKey); @@ -33,44 +31,20 @@ protected OptionSet() /// Gets the value of the option, or the default value if not otherwise set. /// public object? GetOption(OptionKey optionKey) - => OptionsHelpers.GetPublicOption(optionKey, _getOptionCore); - - /// - /// Gets the value of the option cast to type , or the default value if not otherwise set. - /// - public T GetOption(OptionKey optionKey) - => OptionsHelpers.GetOption(optionKey, _getOptionCore); - - /// - /// Gets the value of the option, or the default value if not otherwise set. - /// - internal object? GetOption(OptionKey2 optionKey) - => OptionsHelpers.GetOption(optionKey, _getOptionCore); - - /// - /// Gets the value of the option cast to type , or the default value if not otherwise set. - /// - internal T GetOption(OptionKey2 optionKey) - => OptionsHelpers.GetOption(optionKey, _getOptionCore); + => GetOptionCore(optionKey); /// /// Gets the value of the option, or the default value if not otherwise set. /// - internal T GetOption(Option2 option) - => OptionsHelpers.GetOption(option, _getOptionCore); - - /// - /// Gets the value of the option, or the default value if not otherwise set. - /// - internal T GetOption(PerLanguageOption2 option, string? language) - => OptionsHelpers.GetOption(option, language, _getOptionCore); + public T GetOption(OptionKey optionKey) + => (T)GetOptionCore(optionKey)!; #pragma warning disable RS0030 // Do not used banned APIs: PerLanguageOption /// /// Gets the value of the option, or the default value if not otherwise set. /// public T GetOption(Option option) - => OptionsHelpers.GetOption(new OptionKey(option), _getOptionCore); + => GetOption(new OptionKey(option)); /// /// Creates a new that contains the changed value. @@ -82,7 +56,7 @@ public OptionSet WithChangedOption(Option option, T value) /// Gets the value of the option, or the default value if not otherwise set. /// public T GetOption(PerLanguageOption option, string? language) - => OptionsHelpers.GetOption(new OptionKey(option, language), _getOptionCore); + => GetOption(new OptionKey(option, language)); /// /// Creates a new that contains the changed value. @@ -96,36 +70,24 @@ public OptionSet WithChangedOption(PerLanguageOption option, string? langu /// public abstract OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value); - /// - /// Creates a new that contains the changed value. - /// - internal OptionSet WithChangedOption(OptionKey2 optionAndLanguage, object? value) - => WithChangedOption((OptionKey)optionAndLanguage, value); - - /// - /// Creates a new that contains the changed value. - /// - internal OptionSet WithChangedOption(Option2 option, T value) - => WithChangedOption(new OptionKey(option), value); - /// /// Creates a new that contains the changed value. /// internal OptionSet WithChangedOption(PerLanguageOption2 option, string? language, T value) => WithChangedOption(new OptionKey(option, language), value); - internal AnalyzerConfigOptions AsAnalyzerConfigOptions(IEditorConfigOptionMappingService optionMappingService, string? language) + internal AnalyzerConfigOptions AsAnalyzerConfigOptions(IEditorConfigOptionMappingService optionMappingService, string language) { return ImmutableInterlocked.GetOrAdd( ref _lazyAnalyzerConfigOptions, - language ?? NoLanguageSentinel, - (string language, (OptionSet self, IEditorConfigOptionMappingService mapping) arg) => arg.self.CreateAnalyzerConfigOptions(arg.mapping, (object)language == NoLanguageSentinel ? null : language), + language, + (string language, (OptionSet self, IEditorConfigOptionMappingService mapping) arg) => arg.self.CreateAnalyzerConfigOptions(arg.mapping, language), (this, optionMappingService)); } internal abstract IEnumerable GetChangedOptions(OptionSet optionSet); - private protected virtual AnalyzerConfigOptions CreateAnalyzerConfigOptions(IEditorConfigOptionMappingService optionMappingService, string? language) + private protected virtual AnalyzerConfigOptions CreateAnalyzerConfigOptions(IEditorConfigOptionMappingService optionMappingService, string language) => new AnalyzerConfigOptionsImpl(this, optionMappingService, language); } } diff --git a/src/Workspaces/Core/Portable/Options/OptionValueSet.cs b/src/Workspaces/Core/Portable/Options/OptionValueSet.cs deleted file mode 100644 index 83a0dda17bed3..0000000000000 --- a/src/Workspaces/Core/Portable/Options/OptionValueSet.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Collections.Generic; -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.Options -{ - internal sealed class OptionValueSet : OptionSet - { - public static readonly OptionValueSet Empty = new(ImmutableDictionary.Empty); - - private readonly ImmutableDictionary _values; - - public OptionValueSet(ImmutableDictionary values) - => _values = values; - - private protected override object? GetOptionCore(OptionKey optionKey) - => _values.TryGetValue(optionKey, out var value) ? value : optionKey.Option.DefaultValue; - - public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) - => new OptionValueSet(_values.SetItem(optionAndLanguage, value)); - - internal override IEnumerable GetChangedOptions(OptionSet optionSet) - => throw new NotSupportedException(); - } -} diff --git a/src/Workspaces/Core/Portable/Options/OptionsHelpers.cs b/src/Workspaces/Core/Portable/Options/OptionsHelpers.cs index 0d42129d2f2a6..f2b0328c94120 100644 --- a/src/Workspaces/Core/Portable/Options/OptionsHelpers.cs +++ b/src/Workspaces/Core/Portable/Options/OptionsHelpers.cs @@ -9,16 +9,13 @@ namespace Microsoft.CodeAnalysis.Options { internal static class OptionsHelpers { - public static T GetOption(Option2 option, Func getOption) - => GetOption(new OptionKey(option), getOption); + public static T GetOption(Option2 option, Func getOption) + => GetOption(new OptionKey2(option), getOption); - public static T GetOption(PerLanguageOption2 option, string? language, Func getOption) - => GetOption(new OptionKey(option, language), getOption); + public static T GetOption(PerLanguageOption2 option, string language, Func getOption) + => GetOption(new OptionKey2(option, language), getOption); - public static T GetOption(OptionKey2 optionKey, Func getOption) - => GetOption(new OptionKey(optionKey.Option, optionKey.Language), getOption); - - public static T GetOption(OptionKey optionKey, Func getOption) + public static T GetOption(OptionKey2 optionKey, Func getOption) { var value = getOption(optionKey); if (value is ICodeStyleOption codeStyleOption) @@ -28,16 +25,5 @@ public static T GetOption(OptionKey optionKey, Func getOp return (T)value!; } - - public static object? GetPublicOption(OptionKey optionKey, Func getOption) - { - var value = getOption(optionKey); - if (value is ICodeStyleOption codeStyleOption) - { - return codeStyleOption.AsPublicCodeStyleOption(); - } - - return value; - } } } diff --git a/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs index 78c461b8b7ca8..336d155de05f8 100644 --- a/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs @@ -2,17 +2,10 @@ // 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.Collections; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Options @@ -24,10 +17,10 @@ namespace Microsoft.CodeAnalysis.Options /// internal sealed class SolutionOptionSet : OptionSet { - private readonly ILegacyWorkspaceOptionService _globalOptions; + private readonly ILegacyGlobalOptionService _globalOptions; /// - /// Cached values read from global options. + /// Cached values read from global options translated to public values. /// private ImmutableDictionary _values; @@ -37,7 +30,7 @@ internal sealed class SolutionOptionSet : OptionSet private readonly ImmutableHashSet _changedOptionKeys; private SolutionOptionSet( - ILegacyWorkspaceOptionService globalOptions, + ILegacyGlobalOptionService globalOptions, ImmutableDictionary values, ImmutableHashSet changedOptionKeys) { @@ -46,7 +39,7 @@ private SolutionOptionSet( _changedOptionKeys = changedOptionKeys; } - internal SolutionOptionSet(ILegacyWorkspaceOptionService globalOptions) + internal SolutionOptionSet(ILegacyGlobalOptionService globalOptions) : this(globalOptions, values: ImmutableDictionary.Empty, changedOptionKeys: ImmutableHashSet.Empty) { } @@ -56,15 +49,24 @@ internal SolutionOptionSet(ILegacyWorkspaceOptionService globalOptions) { if (_values.TryGetValue(optionKey, out var value)) { - return value is ICodeStyleOption codeStyleOption ? codeStyleOption.AsPublicCodeStyleOption() : value; + return value; } - value = _globalOptions.GetOption(optionKey); + // Global options store internal representation of code style options. Translate to public representation. + var internalValue = _globalOptions.GetOption(optionKey); + value = internalValue is ICodeStyleOption codeStyleOption ? codeStyleOption.AsPublicCodeStyleOption() : internalValue; + return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value); } public override OptionSet WithChangedOption(OptionKey optionKey, object? value) { + // translate possibly internal value to public value: + if (value is ICodeStyleOption codeStyleOption) + { + value = codeStyleOption.AsPublicCodeStyleOption(); + } + // Make sure we first load this in current optionset var currentValue = GetOption(optionKey); @@ -84,9 +86,16 @@ public override OptionSet WithChangedOption(OptionKey optionKey, object? value) /// /// Gets a list of all the options that were changed. /// - internal IEnumerable GetChangedOptions() + internal IEnumerable GetChangedOptionKeys() => _changedOptionKeys; + internal (ImmutableArray> internallyDefined, ImmutableArray> externallyDefined) GetChangedOptions() + { + var internallyDefined = _changedOptionKeys.Where(key => key.Option is IOption2).SelectAsArray(key => KeyValuePairUtil.Create(new OptionKey2((IOption2)key.Option, key.Language), GetOption(key))); + var externallyDefined = _changedOptionKeys.Where(key => key.Option is not IOption2).SelectAsArray(key => KeyValuePairUtil.Create(key, GetOption(key))); + return (internallyDefined, externallyDefined); + } + internal override IEnumerable GetChangedOptions(OptionSet? optionSet) { if (optionSet == this) @@ -94,7 +103,7 @@ internal override IEnumerable GetChangedOptions(OptionSet? optionSet) yield break; } - foreach (var key in GetChangedOptions()) + foreach (var key in GetChangedOptionKeys()) { var currentValue = optionSet?.GetOption(key); var changedValue = this.GetOption(key); diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index e1f3670e80995..e0b276ae003e2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -37,7 +37,7 @@ public abstract partial class Workspace : IDisposable private readonly string? _workspaceKind; private readonly HostWorkspaceServices _services; - private readonly ILegacyWorkspaceOptionService _legacyOptions; + private readonly ILegacyGlobalOptionService _legacyOptions; // forces serialization of mutation calls from host (OnXXX methods). Must take this lock before taking stateLock. private readonly SemaphoreSlim _serializationLock = new(initialCount: 1); @@ -73,7 +73,7 @@ protected Workspace(HostServices host, string? workspaceKind) _services = host.CreateWorkspaceServices(this); - _legacyOptions = _services.GetRequiredService(); + _legacyOptions = _services.GetRequiredService().LegacyGlobalOptions; _legacyOptions.RegisterWorkspace(this); // queue used for sending events @@ -397,14 +397,14 @@ public OptionSet Options [Obsolete(@"Workspace options should be set by invoking 'workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet))'")] set { - var changedOptionKeys = value switch + var changedOptions = value switch { null => throw new ArgumentNullException(nameof(value)), - SolutionOptionSet serializableOptionSet => serializableOptionSet.GetChangedOptions(), + SolutionOptionSet solutionOptionSet => solutionOptionSet.GetChangedOptions(), _ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(value)) }; - _legacyOptions.SetOptions(value, changedOptionKeys); + _legacyOptions.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined); } } @@ -1341,7 +1341,8 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgressTracker pro if (this.CurrentSolution.Options != newSolution.Options) { - _legacyOptions.SetOptions(newSolution.State.Options, newSolution.State.Options.GetChangedOptions()); + var changedOptions = newSolution.State.Options.GetChangedOptions(); + _legacyOptions.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined); } if (!CurrentSolution.AnalyzerReferences.SequenceEqual(newSolution.AnalyzerReferences)) diff --git a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs index 759c36afa571f..88501b53c1953 100644 --- a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs +++ b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs @@ -83,7 +83,7 @@ public async Task FormatAsync_ForeignLanguageWithFormattingSupport_Options(bool Assert.Equal(7, documentOptions.GetOption(FormattingOptions.IndentationSize)); #pragma warning restore - var options = passExplicitOptions ? new OptionValueSet(ImmutableDictionary.Empty. + var options = passExplicitOptions ? new TestOptionSet(ImmutableDictionary.Empty. Add(new OptionKey(FormattingOptions.UseTabs, NoCompilationConstants.LanguageName), true). Add(new OptionKey(FormattingOptions.TabSize, NoCompilationConstants.LanguageName), 5). Add(new OptionKey(FormattingOptions.IndentationSize, NoCompilationConstants.LanguageName), 6). @@ -118,7 +118,7 @@ public async Task PublicOptions() // Validate that options are read from specified OptionSet: - var updatedOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(OptionValueSet.Empty, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues); + var updatedOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(TestOptionSet.Empty, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues); ValidateCSharpOptions((CSharpSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(csDocument, updatedOptions, CancellationToken.None)).Syntax!); ValidateVisualBasicOptions((VisualBasicSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(vbDocument, updatedOptions, CancellationToken.None)).Syntax!); diff --git a/src/Workspaces/CoreTest/Options/OptionKeyTests.cs b/src/Workspaces/CoreTest/Options/OptionKeyTests.cs index d2c109ce29f9b..4957d28f9dd9b 100644 --- a/src/Workspaces/CoreTest/Options/OptionKeyTests.cs +++ b/src/Workspaces/CoreTest/Options/OptionKeyTests.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.UnitTests; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Options @@ -28,6 +29,19 @@ public void PerLanguageOptionConstructor_Errors() Assert.Throws(() => new PerLanguageOption("X", "Test Name", false, storageLocations: null!)); } + [Fact] + public void Ctor_Language() + { + var optionKey = new OptionKey(new TestOption() { IsPerLanguage = false }); + Assert.Null(optionKey.Language); + + Assert.Throws(() => new OptionKey(null!)); + Assert.Throws(() => new OptionKey(null!, null!)); + Assert.Throws(() => new OptionKey(null!, "lang")); + Assert.Throws(() => new OptionKey(new TestOption() { IsPerLanguage = true })); + Assert.Throws(() => new OptionKey(new TestOption() { IsPerLanguage = false }, language: "lang")); + } + [Fact] [Obsolete] public void ToStringForObsoleteOption() diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index b607578374969..f15e4ad676530 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -4216,7 +4216,7 @@ public void TestOptionChangesForLanguagesNotInSolution() // Create an empty solution with no projects. using var workspace = CreateWorkspace(); var s0 = workspace.CurrentSolution; - var optionService = workspace.Services.GetRequiredService(); + var optionService = workspace.Services.GetRequiredService().LegacyGlobalOptions; // Apply an option change to a C# option. var option = FormattingOptions.UseTabs; @@ -4353,7 +4353,7 @@ public async Task EditorConfigOptions(string projectPath, string configPath, str #pragma warning disable RS0030 // Do not used banned APIs var documentOptions = await document.GetOptionsAsync(CancellationToken.None); - Assert.Equal(appliedToDocument, documentOptions.GetOption(FormattingOptions2.UseTabs)); + Assert.Equal(appliedToDocument, documentOptions.GetOption(FormattingOptions.UseTabs)); #pragma warning restore var syntaxTree = await document.GetSyntaxTreeAsync(); diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs index 4a6fdbf09d6de..24c0a18e90c2e 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs @@ -5,17 +5,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -27,77 +21,128 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices [Trait(Traits.Feature, Traits.Features.Workspace)] public class GlobalOptionServiceTests { - private static IGlobalOptionService GetGlobalOptionService(HostWorkspaceServices services, IOptionPersisterProvider? optionPersisterProvider = null) - { - var mefHostServices = services.SolutionServices.ExportProvider; - var workspaceThreadingService = mefHostServices.GetExportedValues().SingleOrDefault(); - return new GlobalOptionService( - workspaceThreadingService, - new[] - { - new Lazy(() => optionPersisterProvider ??= new TestOptionsPersisterProvider()) - }); - } + private static IGlobalOptionService GetGlobalOptionService(HostWorkspaceServices services) + => services.SolutionServices.ExportProvider.GetExportedValue(); - private static ILegacyWorkspaceOptionService GetOptionService(HostWorkspaceServices services, IOptionPersisterProvider? optionPersisterProvider = null) - => new TestService(GetGlobalOptionService(services, optionPersisterProvider)); + private static ILegacyGlobalOptionService GetLegacyGlobalOptionService(HostWorkspaceServices services) + => services.SolutionServices.ExportProvider.GetExportedValue(); - private sealed class TestService : ILegacyWorkspaceOptionService + [Fact] + public void LegacyGlobalOptions_SetGet() { - public IGlobalOptionService GlobalOptions { get; } + using var workspace = new AdhocWorkspace(); + var optionService = GetLegacyGlobalOptionService(workspace.Services); - public TestService(IGlobalOptionService globalOptions) - => GlobalOptions = globalOptions; + var optionKey = new OptionKey(new TestOption() { DefaultValue = 1 }); - public object? GetOption(OptionKey key) - => GlobalOptions.GetOption(key); + Assert.Equal(1, optionService.GetOption(optionKey)); - public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) - => GlobalOptions.SetOptions(optionSet, optionKeys); + optionService.SetOptions( + ImmutableArray>.Empty, + ImmutableArray.Create(KeyValuePairUtil.Create(optionKey, (object?)2))); - public void RegisterWorkspace(Workspace workspace) - => throw new NotImplementedException(); + Assert.Equal(2, optionService.GetOption(optionKey)); - public void UnregisterWorkspace(Workspace workspace) - => throw new NotImplementedException(); - } + optionService.SetOptions( + ImmutableArray>.Empty, + ImmutableArray.Create(KeyValuePairUtil.Create(optionKey, (object?)3))); - internal class TestOptionsProvider : IOptionProvider - { - public ImmutableArray Options { get; } = ImmutableArray.Create( - new Option("Test Feature", "Test Name", false)); + Assert.Equal(3, optionService.GetOption(optionKey)); } - internal sealed class TestOptionsPersisterProvider : IOptionPersisterProvider + [Fact] + public void ExternallyDefinedOption() { - private readonly ValueTask _optionPersisterTask; + using var workspace1 = new AdhocWorkspace(); + using var workspace2 = new AdhocWorkspace(); + var optionService = GetLegacyGlobalOptionService(workspace1.Services); + var optionSet = new SolutionOptionSet(optionService); + + var optionKey = new OptionKey(new TestOption()); + var perLanguageOptionKey = new OptionKey(new TestOption() { IsPerLanguage = true }, "lang"); + + Assert.Equal(optionKey.Option.DefaultValue, optionSet.GetOption(optionKey)); + Assert.Equal(perLanguageOptionKey.Option.DefaultValue, optionSet.GetOption(perLanguageOptionKey)); + + var newSet = optionSet.WithChangedOption(optionKey, 2).WithChangedOption(perLanguageOptionKey, 3); + Assert.Equal(2, newSet.GetOption(optionKey)); + Assert.Equal(3, newSet.GetOption(perLanguageOptionKey)); + + var newSolution1 = workspace1.CurrentSolution.WithOptions(newSet); + Assert.Equal(2, newSolution1.Options.GetOption(optionKey)); + Assert.Equal(3, newSolution1.Options.GetOption(perLanguageOptionKey)); - public TestOptionsPersisterProvider(IOptionPersister? optionPersister = null) - => _optionPersisterTask = new(optionPersister ?? new TestOptionsPersister()); + // TryApplyChanges propagates the option to all workspaces: + var oldSolution2 = workspace2.CurrentSolution; + Assert.Equal(1, oldSolution2.Options.GetOption(optionKey)); + Assert.Equal(1, oldSolution2.Options.GetOption(perLanguageOptionKey)); - public ValueTask GetOrCreatePersisterAsync(CancellationToken cancellationToken) - => _optionPersisterTask; + Assert.True(workspace1.TryApplyChanges(newSolution1)); + + var newSolution2 = workspace2.CurrentSolution; + Assert.Equal(2, newSolution2.Options.GetOption(optionKey)); + Assert.Equal(3, newSolution2.Options.GetOption(perLanguageOptionKey)); } - internal sealed class TestOptionsPersister : IOptionPersister + [Fact] + public void InternallyDefinedOption() { - private ImmutableDictionary _options = ImmutableDictionary.Empty; + using var workspace1 = new AdhocWorkspace(); + using var workspace2 = new AdhocWorkspace(); + var optionService = GetLegacyGlobalOptionService(workspace1.Services); + var optionSet = new SolutionOptionSet(optionService); - public bool TryFetch(OptionKey optionKey, out object? value) - => _options.TryGetValue(optionKey, out value); + var perLanguageOptionKey = new OptionKey(FormattingOptions.NewLine, "lang"); - public bool TryPersist(OptionKey optionKey, object? value) - { - _options = _options.SetItem(optionKey, value); - return true; - } + Assert.Equal(perLanguageOptionKey.Option.DefaultValue, optionSet.GetOption(perLanguageOptionKey)); + + var newSet = optionSet.WithChangedOption(perLanguageOptionKey, "EOLN"); + Assert.Equal("EOLN", newSet.GetOption(perLanguageOptionKey)); + + var newSolution1 = workspace1.CurrentSolution.WithOptions(newSet); + Assert.Equal("EOLN", newSolution1.Options.GetOption(perLanguageOptionKey)); + + // TryApplyChanges propagates the option to all workspaces: + var oldSolution2 = workspace2.CurrentSolution; + Assert.Equal(perLanguageOptionKey.Option.DefaultValue, oldSolution2.Options.GetOption(perLanguageOptionKey)); + + Assert.True(workspace1.TryApplyChanges(newSolution1)); + + Assert.Equal("EOLN", workspace1.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + Assert.Equal("EOLN", workspace2.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + + // Update global option directly (after the value is cached in the above current solution snapshots). + // Doing so does NOT update current solutions. + optionService.GlobalOptions.SetGlobalOption(FormattingOptions2.NewLine, "lang", "NEW_LINE"); + + Assert.Equal("EOLN", workspace1.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + Assert.Equal("EOLN", workspace2.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + + // Update global option indirectly via legacy service updates current solutions. + optionService.SetOptions( + ImmutableArray.Create(KeyValuePairUtil.Create(new OptionKey2(FormattingOptions2.NewLine, "lang"), (object?)"NEW_LINE")), + ImmutableArray>.Empty); + + Assert.Equal("NEW_LINE", workspace1.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + Assert.Equal("NEW_LINE", workspace2.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + + // Set the option directly again and trigger workspace update: + optionService.GlobalOptions.SetGlobalOption(FormattingOptions2.NewLine, "lang", "NEW_LINE2"); + + Assert.Equal("NEW_LINE", workspace1.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + Assert.Equal("NEW_LINE", workspace2.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + + optionService.UpdateRegisteredWorkspaces(); + + Assert.Equal("NEW_LINE2", workspace1.CurrentSolution.Options.GetOption(perLanguageOptionKey)); + Assert.Equal("NEW_LINE2", workspace2.CurrentSolution.Options.GetOption(perLanguageOptionKey)); } [Fact] public void OptionPerLanguageOption() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var optionSet = new SolutionOptionSet(optionService); var optionvalid = new PerLanguageOption("Test Feature", "Test Name", false); @@ -108,7 +153,7 @@ public void OptionPerLanguageOption() public void GettingOptionReturnsOption() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", false); Assert.False(optionSet.GetOption(option)); @@ -128,13 +173,14 @@ public void GlobalOptions() var handler = new EventHandler((_, e) => changedOptions.Add(e)); globalOptions.OptionChanged += handler; - var values = globalOptions.GetOptions(ImmutableArray.Create(new OptionKey(option1), new OptionKey(option2))); + var values = globalOptions.GetOptions(ImmutableArray.Create(new OptionKey2(option1), new OptionKey2(option2))); Assert.Equal(1, values[0]); Assert.Equal(2, values[1]); - globalOptions.SetGlobalOptions( - ImmutableArray.Create(new OptionKey(option1), new OptionKey(option2), new OptionKey(option3)), - ImmutableArray.Create(5, 6, 3)); + globalOptions.SetGlobalOptions(ImmutableArray.Create( + KeyValuePairUtil.Create(new OptionKey2(option1), (object?)5), + KeyValuePairUtil.Create(new OptionKey2(option2), (object?)6), + KeyValuePairUtil.Create(new OptionKey2(option3), (object?)3))); AssertEx.Equal(new[] { @@ -142,7 +188,7 @@ public void GlobalOptions() "Name2=6", }, changedOptions.Select(e => $"{e.OptionKey.Option.Name}={e.Value}")); - values = globalOptions.GetOptions(ImmutableArray.Create(new OptionKey(option1), new OptionKey(option2), new OptionKey(option3))); + values = globalOptions.GetOptions(ImmutableArray.Create(new OptionKey2(option1), new OptionKey2(option2), new OptionKey2(option3))); Assert.Equal(5, values[0]); Assert.Equal(6, values[1]); Assert.Equal(3, values[2]); @@ -158,7 +204,7 @@ public void GlobalOptions() public void GettingOptionWithChangedOption() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); OptionSet optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", false); var key = new OptionKey(option); @@ -171,7 +217,7 @@ public void GettingOptionWithChangedOption() public void GettingOptionWithoutChangedOption() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var optionSet = new SolutionOptionSet(optionService); var optionFalse = new Option("Test Feature", "Test Name", false); @@ -191,12 +237,12 @@ public void GettingOptionWithoutChangedOption() public void GetKnownOptions() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var option = new Option("Test Feature", "Test Name", defaultValue: true); optionService.GetOption(option); var optionSet = new SolutionOptionSet(optionService); - var value = optionSet.GetOption(option); + var value = optionSet.GetOption((Option)option); Assert.True(value); } @@ -204,7 +250,7 @@ public void GetKnownOptions() public void GetKnownOptionsKey() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var option = new Option("Test Feature", "Test Name", defaultValue: true); optionService.GetOption(option); @@ -218,13 +264,14 @@ public void GetKnownOptionsKey() public void SetKnownOptions() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", defaultValue: true); var optionKey = new OptionKey(option); var newOptionSet = optionSet.WithChangedOption(optionKey, false); - optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); + var changedOptions = ((SolutionOptionSet)newOptionSet).GetChangedOptions(); + optionService.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined); var isOptionSet = (bool?)new SolutionOptionSet(optionService).GetOption(optionKey); Assert.False(isOptionSet); } @@ -233,7 +280,7 @@ public void SetKnownOptions() public void OptionSetIsImmutable() { using var workspace = new AdhocWorkspace(); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", defaultValue: true); @@ -266,28 +313,8 @@ public void TestPerLanguageCodeStyleOptions() // 4. { PerLanguageOption2, CodeStyleOption2 } TestCodeStyleOptionsCommon(workspace, perLanguageOption2, LanguageNames.CSharp, newValueCodeStyleOption2); - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var originalOptionSet = new SolutionOptionSet(optionService); - - // Test "PerLanguageOption" and "PerLanguageOption2" overloads for OptionSet and OptionService. - - // 1. Verify default value. - Assert.Equal(perLanguageOption.DefaultValue, originalOptionSet.GetOption(perLanguageOption, LanguageNames.CSharp)); - Assert.Equal(perLanguageOption2.DefaultValue, originalOptionSet.GetOption(perLanguageOption2, LanguageNames.CSharp)); - - // 2. OptionSet validations. - var newOptionSet = originalOptionSet.WithChangedOption(perLanguageOption, LanguageNames.CSharp, newValueCodeStyleOption); - Assert.Equal(newValueCodeStyleOption, newOptionSet.GetOption(perLanguageOption, LanguageNames.CSharp)); - Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(perLanguageOption2, LanguageNames.CSharp)); - - newOptionSet = originalOptionSet.WithChangedOption(perLanguageOption2, LanguageNames.CSharp, newValueCodeStyleOption2); - Assert.Equal(newValueCodeStyleOption, newOptionSet.GetOption(perLanguageOption, LanguageNames.CSharp)); - Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(perLanguageOption2, LanguageNames.CSharp)); - - // 3. IOptionService validation - - optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); - Assert.Equal(newValueCodeStyleOption2, optionService.GlobalOptions.GetOption(perLanguageOption2, LanguageNames.CSharp)); } [Fact] @@ -312,44 +339,15 @@ public void TestLanguageSpecificCodeStyleOptions() // 4. { Option2, CodeStyleOption2 } TestCodeStyleOptionsCommon(workspace, option2, language: null, newValueCodeStyleOption2); - - var optionService = GetOptionService(workspace.Services); - var originalOptionSet = new SolutionOptionSet(optionService); - - // Test "Option" and "Option2" overloads for OptionSet and OptionService. - - // 1. Verify default value. - Assert.Equal(option.DefaultValue, originalOptionSet.GetOption(option)); - Assert.Equal(option2.DefaultValue, originalOptionSet.GetOption(option2)); - - // 2. OptionSet validations. - var newOptionSet = originalOptionSet.WithChangedOption(option, newValueCodeStyleOption); - Assert.Equal(newValueCodeStyleOption, newOptionSet.GetOption(option)); - Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(option2)); - - newOptionSet = originalOptionSet.WithChangedOption(option2, newValueCodeStyleOption2); - Assert.Equal(newValueCodeStyleOption, newOptionSet.GetOption(option)); - Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(option2)); - - // 3. IOptionService validation - optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); - Assert.Equal(newValueCodeStyleOption2, optionService.GlobalOptions.GetOption(option2)); } private static void TestCodeStyleOptionsCommon(Workspace workspace, IOption2 option, string? language, TCodeStyleOption newValue) where TCodeStyleOption : ICodeStyleOption { - var optionService = GetOptionService(workspace.Services); + var optionService = GetLegacyGlobalOptionService(workspace.Services); var originalOptionSet = new SolutionOptionSet(optionService); - // Test matrix using different OptionKey and OptionKey2 get/set operations. var optionKey = new OptionKey(option, language); - var optionKey2 = option switch - { - IPerLanguageValuedOption perLanguageValuedOption => new OptionKey2(perLanguageValuedOption, language!), - ISingleValuedOption singleValued => new OptionKey2(singleValued), - _ => throw ExceptionUtilities.Unreachable(), - }; // Value return from "object GetOption(OptionKey)" should always be public CodeStyleOption type. var newPublicValue = newValue.AsPublicCodeStyleOption(); @@ -358,36 +356,14 @@ private static void TestCodeStyleOptionsCommon(Workspace works var newOptionSet = originalOptionSet.WithChangedOption(optionKey, newValue); Assert.Equal(newPublicValue, newOptionSet.GetOption(optionKey)); // Value returned from public API should always be castable to public CodeStyleOption type. - Assert.NotNull((CodeStyleOption)newOptionSet.GetOption(optionKey)!); - // Verify "T GetOption(OptionKey)" works for both cases of T being a public and internal code style option type. - Assert.Equal(newPublicValue, newOptionSet.GetOption>(optionKey)); - Assert.Equal(newValue, newOptionSet.GetOption(optionKey)); - - // 2. WithChangedOption(OptionKey), GetOption(OptionKey2)/GetOption(OptionKey2) - newOptionSet = originalOptionSet.WithChangedOption(optionKey, newValue); - Assert.Equal(newPublicValue, newOptionSet.GetOption(optionKey2)); - // Verify "T GetOption(OptionKey2)" works for both cases of T being a public and internal code style option type. - Assert.Equal(newPublicValue, newOptionSet.GetOption>(optionKey2)); - Assert.Equal(newValue, newOptionSet.GetOption(optionKey2)); + Assert.NotNull((CodeStyleOption?)newOptionSet.GetOption(optionKey)); - // 3. WithChangedOption(OptionKey2), GetOption(OptionKey)/GetOption(OptionKey) - newOptionSet = originalOptionSet.WithChangedOption(optionKey2, newValue); - Assert.Equal(newPublicValue, newOptionSet.GetOption(optionKey)); - // Value returned from public API should always be castable to public CodeStyleOption type. - Assert.NotNull((CodeStyleOption)newOptionSet.GetOption(optionKey)!); - // Verify "T GetOption(OptionKey)" works for both cases of T being a public and internal code style option type. + // Verify "T GetOption(OptionKey)" works for T being a public code style option type. Assert.Equal(newPublicValue, newOptionSet.GetOption>(optionKey)); - Assert.Equal(newValue, newOptionSet.GetOption(optionKey)); - - // 4. WithChangedOption(OptionKey2), GetOption(OptionKey2)/GetOption(OptionKey2) - newOptionSet = originalOptionSet.WithChangedOption(optionKey2, newValue); - Assert.Equal(newPublicValue, newOptionSet.GetOption(optionKey2)); - // Verify "T GetOption(OptionKey2)" works for both cases of T being a public and internal code style option type. - Assert.Equal(newPublicValue, newOptionSet.GetOption>(optionKey2)); - Assert.Equal(newValue, newOptionSet.GetOption(optionKey2)); - // 5. IOptionService.GetOption(OptionKey) - optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); + // 2. IOptionService.GetOption(OptionKey) + var changedOptions = ((SolutionOptionSet)newOptionSet).GetChangedOptions(); + optionService.SetOptions(changedOptions.internallyDefined, changedOptions.externallyDefined); Assert.Equal(newPublicValue, optionService.GetOption(optionKey)); } } diff --git a/src/Workspaces/CoreTestUtilities/Fakes/TestOptionSet.cs b/src/Workspaces/CoreTestUtilities/Fakes/TestOptionSet.cs index 890895f8eda87..3cd0537915b3b 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/TestOptionSet.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/TestOptionSet.cs @@ -11,24 +11,17 @@ namespace Microsoft.CodeAnalysis.Test.Utilities { internal class TestOptionSet : OptionSet { - private readonly ImmutableDictionary _values; + public static new readonly TestOptionSet Empty = new(ImmutableDictionary.Empty); - public TestOptionSet() - { - _values = ImmutableDictionary.Empty; - } + private readonly ImmutableDictionary _values; - private TestOptionSet(ImmutableDictionary values) + public TestOptionSet(ImmutableDictionary values) { _values = values; } private protected override object? GetOptionCore(OptionKey optionKey) - { - Contract.ThrowIfFalse(_values.TryGetValue(optionKey, out var value)); - - return value; - } + => _values.TryGetValue(optionKey, out var value) ? value : optionKey.Option.DefaultValue; public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) { diff --git a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj index c9d1803318502..0eab42b807afb 100644 --- a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj @@ -53,5 +53,6 @@ + diff --git a/src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs b/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs similarity index 68% rename from src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs rename to src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs index d27bd2731b9ca..a09e4dce09844 100644 --- a/src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs +++ b/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs @@ -2,16 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma warning disable RS0030 // Do not used banned APIs + using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Utilities; +using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { @@ -109,5 +113,57 @@ public static OptionSet GetOptionSetWithChangedOptions(OptionSet options, IEnume public static IEnumerable GetApplicableLanguages(IOption option) => (option is IPerLanguageValuedOption) ? new[] { LanguageNames.CSharp, LanguageNames.VisualBasic } : new string?[] { null }; + + public static object? GetDifferentValue(Type type, object? value) + => value switch + { + _ when type == typeof(bool) => !(bool)value!, + _ when type == typeof(string) => (string?)value == "X" ? "Y" : "X", + _ when type == typeof(int) => (int)value! == 0 ? 1 : 0, + _ when type == typeof(long) => (long)value! == 0 ? 1L : 0L, + _ when type.IsEnum => GetDifferentEnumValue(type, value!), + ICodeStyleOption codeStyle => codeStyle + .WithValue(GetDifferentValue(codeStyle.GetType().GetGenericArguments()[0], codeStyle.Value!)!) + .WithNotification((codeStyle.Notification == NotificationOption2.Error) ? NotificationOption2.Warning : NotificationOption2.Error), + NamingStylePreferences namingPreference => namingPreference.IsEmpty ? NamingStylePreferences.Default : NamingStylePreferences.Empty, + _ when type == typeof(bool?) => value is null ? true : null, + _ when type == typeof(int?) => value is null ? 1 : null, + _ when type == typeof(long?) => value is null ? 1L : null, + ImmutableArray array => array.IsEmpty ? ImmutableArray.Create("X") : ImmutableArray.Empty, + ImmutableArray array => array.IsEmpty ? ImmutableArray.Create("X") : ImmutableArray.Empty, + ImmutableArray array => array.IsEmpty ? ImmutableArray.Create("X") : ImmutableArray.Empty, + ImmutableArray array => array.IsEmpty ? ImmutableArray.Create("X") : ImmutableArray.Empty, + + // Hit when a new option is introduced that uses type not handled above: + _ => throw ExceptionUtilities.UnexpectedValue(type) + }; + + private static object GetDifferentEnumValue(Type type, object defaultValue) + { + var zero = Enum.ToObject(type, 0); + return defaultValue.Equals(zero) ? Enum.ToObject(type, 1) : zero; + } + + /// + /// True if the type is a type of an option value. + /// + public static bool IsOptionValueType(Type type) + => type == typeof(bool) || + type == typeof(string) || + type == typeof(int) || + type == typeof(long) || + type == typeof(bool?) || + type == typeof(int?) || + type == typeof(long?) || + type.IsEnum || + typeof(ICodeStyleOption).IsAssignableFrom(type) || + type == typeof(NamingStylePreferences) || + type == typeof(ImmutableArray) || + type == typeof(ImmutableArray) || + type == typeof(ImmutableArray) || + type == typeof(ImmutableArray); + + public static Type GetNonNullableType(Type type) + => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ? type.GetGenericArguments()[0] : type; } } diff --git a/src/Workspaces/CoreTestUtilities/Options/OptionsTestInfo.cs b/src/Workspaces/CoreTestUtilities/Options/OptionsTestInfo.cs new file mode 100644 index 0000000000000..f5a1e61720105 --- /dev/null +++ b/src/Workspaces/CoreTestUtilities/Options/OptionsTestInfo.cs @@ -0,0 +1,140 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis.Options; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests; + +internal sealed class OptionsTestInfo +{ + public readonly HashSet Assemblies = new(); + public readonly Dictionary EditorConfig = new(); + public readonly Dictionary GlobalOptions = new(); + + public override string ToString() + { + var lines = new List(); + + lines.Add("Assemblies:"); + lines.AddRange(Assemblies.OrderBy(e => e)); + lines.Add(""); + + lines.Add(""); + lines.Add(" private static readonly Dictionary s_storages = new()"); + lines.Add(" {"); + lines.AddRange(GlobalOptions.OrderBy(e => e.Key).Select(e => $" {{\"{e.Key}\", {e.Value.storage}}},")); + lines.Add(" };"); + + lines.Add(""); + lines.AddRange(EditorConfig.OrderBy(e => (e.Value.isPublic, e.Key)).Select(e => $"\"{e.Key}\", {e.Value.accessor}{(e.Value.isPublic ? " [public]" : "")}")); + + lines.Add(""); + lines.Add(" private static readonly Dictionary s_legacyNameMap = new()"); + lines.Add(" {"); + lines.AddRange(from e in EditorConfig + where e.Value.isPublic + select $" {{\"{e.Value.option.Name}\", \"{e.Key}\"}},"); + lines.Add(" };"); + + return string.Join(Environment.NewLine, lines); + } + + public static OptionsTestInfo CollectOptions(string directory) + { + var result = new OptionsTestInfo(); + foreach (var file in Directory.EnumerateFiles(directory, "*.dll", SearchOption.TopDirectoryOnly)) + { + var fileName = Path.GetFileNameWithoutExtension(file); + if (fileName.StartsWith("Microsoft.CodeAnalysis") || fileName.StartsWith("Microsoft.VisualStudio.LanguageServices")) + { + Type[] types; + try + { + types = Assembly.Load(fileName).GetTypes(); + } + catch + { + continue; + } + + var language = file.Contains("CSharp") ? "CSharp" : file.Contains("VisualBasic") ? "VisualBasic" : null; + + foreach (var type in types) + { + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + if (typeof(IOption2).IsAssignableFrom(field.FieldType)) + { + Assert.False(type.IsGenericType, "Option should not be defined in a generic type"); + + result.Assemblies.Add(fileName); + + var option = (IOption2)field.GetValue(null)!; + Assert.NotNull(option); + + var isBackingField = field.Name.EndsWith("k__BackingField"); + var unmangledName = isBackingField ? field.Name[(field.Name.IndexOf('<') + 1)..field.Name.IndexOf('>')] : field.Name; + var accessor = type.FullName + "." + unmangledName; + var isPublic = type.IsPublic && (isBackingField ? type.GetProperty(unmangledName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!.GetMethod!.IsPublic : field.IsPublic); + + var configName = option.OptionDefinition.ConfigName; + + static string? TryGetStorage(OptionStorageLocation location) + => location switch + { + ClientSettingsStorageLocation vsSettingStorage => + (vsSettingStorage.KeyName != null) + ? $"new RoamingProfileStorage(\"{vsSettingStorage.KeyName}\")" + : $"new RoamingProfileStorage(\"{vsSettingStorage.GetKeyNameForLanguage("%LANGUAGE%")}\", \"{vsSettingStorage.GetKeyNameForLanguage(LanguageNames.VisualBasic)}\")", + FeatureFlagStorageLocation featureFlagStorage => + $"new FeatureFlagStorage(@\"{featureFlagStorage.Name}\")", + LocalUserProfileStorageLocation userProfileStorage => + $"new LocalUserProfileStorage(@\"{Path.GetDirectoryName(userProfileStorage.KeyName)}\", \"{Path.GetFileName(userProfileStorage.KeyName)}\")", + _ => null + }; + + var newStorages = (from l in option.StorageLocations let s = TryGetStorage(l) where s != null select s).ToArray(); + + var newStorage = (newStorages.Length > 1) + ? $"new CompositeStorage({string.Join(", ", newStorages)})" + : newStorages.SingleOrDefault(); + + if (newStorage != null) + { + if (result.GlobalOptions.TryGetValue(configName, out var existing)) + { + Assert.Equal(existing.storage, newStorage); + } + else + { + result.GlobalOptions.Add(configName, (option, language, accessor, newStorage)); + } + } + + var ecStorage = option.StorageLocations.OfType().SingleOrDefault(); + if (ecStorage != null) + { + if (result.EditorConfig.TryGetValue(configName, out var existing)) + { + isPublic |= existing.isPublic; + Assert.Equal(existing.option.Name, option.Name); + } + + result.EditorConfig[configName] = (option, accessor, isPublic); + } + } + } + } + } + } + + return result; + } +} diff --git a/src/Workspaces/CoreTestUtilities/Options/TestOption.cs b/src/Workspaces/CoreTestUtilities/Options/TestOption.cs new file mode 100644 index 0000000000000..9b79aee17b0e5 --- /dev/null +++ b/src/Workspaces/CoreTestUtilities/Options/TestOption.cs @@ -0,0 +1,19 @@ +// 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.Collections.Immutable; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.UnitTests; + +internal class TestOption : IOption +{ + public string Feature { get; set; } = "test"; + public string Name { get; set; } = "test"; + public Type Type { get; set; } = typeof(int); + public object? DefaultValue { get; set; } = 1; + public bool IsPerLanguage { get; set; } + public ImmutableArray StorageLocations { get; set; } +} diff --git a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs index 9866211c48681..411a24fe24f89 100644 --- a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs +++ b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs @@ -9,6 +9,8 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; +using System.Diagnostics; +using Microsoft.CodeAnalysis.Test.Utilities; #if !NETCOREAPP using System; @@ -73,7 +75,7 @@ IEnumerator IEnumerable.GetEnumerator() #if !CODE_STYLE public OptionSet ToOptionSet() - => new OptionValueSet(_options.ToImmutableDictionary(entry => new OptionKey(entry.Key.Option, entry.Key.Language), entry => entry.Value)); + => new TestOptionSet(_options.ToImmutableDictionary(entry => new OptionKey(entry.Key.Option, entry.Key.Language), entry => entry.Value)); public AnalyzerConfigOptions ToAnalyzerConfigOptions(LanguageServices languageServices) { @@ -85,7 +87,7 @@ public void SetGlobalOptions(IGlobalOptionService globalOptions) { foreach (var (optionKey, value) in _options) { - globalOptions.SetGlobalOption((OptionKey)optionKey, value); + globalOptions.SetGlobalOption(optionKey, value); } } #endif diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.cs index 3c9384aa28fb0..dc17e8e51d03b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/CSharpFormattingOptions2.cs @@ -174,9 +174,9 @@ private static Option2 CreateNewLineForBracesLegacyOption(string name, boo parseValue: list => ParseSpacingWithinParenthesesList(list), #if !CODE_STYLE getValueFromOptionSet: set => - (set.GetOption(SpaceWithinExpressionParentheses) ? SpacePlacementWithinParentheses.Expressions : 0) | - (set.GetOption(SpaceWithinCastParentheses) ? SpacePlacementWithinParentheses.TypeCasts : 0) | - (set.GetOption(SpaceWithinOtherParentheses) ? SpacePlacementWithinParentheses.ControlFlowStatements : 0), + (set.GetOption((Option)SpaceWithinExpressionParentheses) ? SpacePlacementWithinParentheses.Expressions : 0) | + (set.GetOption((Option)SpaceWithinCastParentheses) ? SpacePlacementWithinParentheses.TypeCasts : 0) | + (set.GetOption((Option)SpaceWithinOtherParentheses) ? SpacePlacementWithinParentheses.ControlFlowStatements : 0), #endif serializeValue: ToEditorConfigValue)); @@ -369,15 +369,15 @@ private static Option2 CreateNewLineForBracesLegacyOption(string name, boo parseValue: list => ParseNewLineBeforeOpenBracePlacementList(list), #if !CODE_STYLE getValueFromOptionSet: set => - (set.GetOption(NewLinesForBracesInTypes) ? NewLineBeforeOpenBracePlacement.Types : 0) | - (set.GetOption(NewLinesForBracesInAnonymousTypes) ? NewLineBeforeOpenBracePlacement.AnonymousTypes : 0) | - (set.GetOption(NewLinesForBracesInObjectCollectionArrayInitializers) ? NewLineBeforeOpenBracePlacement.ObjectCollectionArrayInitializers : 0) | - (set.GetOption(NewLinesForBracesInProperties) ? NewLineBeforeOpenBracePlacement.Properties : 0) | - (set.GetOption(NewLinesForBracesInMethods) ? NewLineBeforeOpenBracePlacement.Methods : 0) | - (set.GetOption(NewLinesForBracesInAccessors) ? NewLineBeforeOpenBracePlacement.Accessors : 0) | - (set.GetOption(NewLinesForBracesInAnonymousMethods) ? NewLineBeforeOpenBracePlacement.AnonymousMethods : 0) | - (set.GetOption(NewLinesForBracesInLambdaExpressionBody) ? NewLineBeforeOpenBracePlacement.LambdaExpressionBody : 0) | - (set.GetOption(NewLinesForBracesInControlBlocks) ? NewLineBeforeOpenBracePlacement.ControlBlocks : 0), + (set.GetOption((Option)NewLinesForBracesInTypes) ? NewLineBeforeOpenBracePlacement.Types : 0) | + (set.GetOption((Option)NewLinesForBracesInAnonymousTypes) ? NewLineBeforeOpenBracePlacement.AnonymousTypes : 0) | + (set.GetOption((Option)NewLinesForBracesInObjectCollectionArrayInitializers) ? NewLineBeforeOpenBracePlacement.ObjectCollectionArrayInitializers : 0) | + (set.GetOption((Option)NewLinesForBracesInProperties) ? NewLineBeforeOpenBracePlacement.Properties : 0) | + (set.GetOption((Option)NewLinesForBracesInMethods) ? NewLineBeforeOpenBracePlacement.Methods : 0) | + (set.GetOption((Option)NewLinesForBracesInAccessors) ? NewLineBeforeOpenBracePlacement.Accessors : 0) | + (set.GetOption((Option)NewLinesForBracesInAnonymousMethods) ? NewLineBeforeOpenBracePlacement.AnonymousMethods : 0) | + (set.GetOption((Option)NewLinesForBracesInLambdaExpressionBody) ? NewLineBeforeOpenBracePlacement.LambdaExpressionBody : 0) | + (set.GetOption((Option)NewLinesForBracesInControlBlocks) ? NewLineBeforeOpenBracePlacement.ControlBlocks : 0), #endif serializeValue: ToEditorConfigValue)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs index c23e79bbb0aee..d391be9847b6d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs @@ -24,6 +24,10 @@ internal interface ICodeStyleOption #endif } + internal interface ICodeStyleOption2 : ICodeStyleOption + { + } + /// /// Represents a code style option and an associated notification option. Supports /// being instantiated with T as a or an enum type. @@ -39,7 +43,7 @@ internal interface ICodeStyleOption /// then those values will write back as false/true. /// [DataContract] - internal sealed partial class CodeStyleOption2 : ICodeStyleOption, IEquatable?> + internal sealed partial class CodeStyleOption2 : ICodeStyleOption2, IEquatable?> { public static readonly CodeStyleOption2 Default = new(default!, NotificationOption2.Silent); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/ClientSettingsStorageLocation.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/ClientSettingsStorageLocation.cs index 3d12ba03c3427..f0921195e43a2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/ClientSettingsStorageLocation.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/ClientSettingsStorageLocation.cs @@ -17,9 +17,13 @@ namespace Microsoft.CodeAnalysis.Options; internal abstract class ClientSettingsStorageLocation : OptionStorageLocation2 { private readonly Func _keyNameFromLanguageName; + public string? KeyName { get; } public ClientSettingsStorageLocation(string keyName) - => _keyNameFromLanguageName = _ => keyName; + { + _keyNameFromLanguageName = _ => keyName; + KeyName = keyName; + } /// /// Creates a that has different key names for different languages. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs index 6ad8d4d8895d0..eac9763802df3 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs @@ -79,19 +79,7 @@ public string GetEditorConfigStringValue(T value) } string IEditorConfigStorageLocation2.GetEditorConfigStringValue(object? value) - { - T typedValue; - if (value is ICodeStyleOption codeStyleOption) - { - typedValue = (T)codeStyleOption.AsCodeStyleOption(); - } - else - { - typedValue = (T)value!; - } - - return GetEditorConfigStringValue(typedValue); - } + => GetEditorConfigStringValue((T)value!); #if !CODE_STYLE public string GetEditorConfigStringValue(OptionKey optionKey, OptionSet optionSet) @@ -103,7 +91,10 @@ public string GetEditorConfigStringValue(OptionKey optionKey, OptionSet optionSe return editorConfigStringForValue; } - return GetEditorConfigStringValue(optionSet.GetOption(optionKey)); + var publicValue = optionSet.GetOption(optionKey); + var internalValue = (publicValue is ICodeStyleOption codeStyleOption) ? codeStyleOption.AsCodeStyleOption() : publicValue; + + return GetEditorConfigStringValue((T)internalValue!); } #endif } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionKey2.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionKey2.cs index dcdf27dbff5df..3cc718626a0cc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionKey2.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionKey2.cs @@ -18,6 +18,14 @@ namespace Microsoft.CodeAnalysis.Options public IOption2 Option { get; } public string? Language { get; } + public OptionKey2(IOption2 option, string? language) + { + Debug.Assert(option.IsPerLanguage == language is not null); + + Option = option; + Language = language; + } + public OptionKey2(IPerLanguageValuedOption option, string language) { Debug.Assert(option.IsPerLanguage); From 0b03ac37d776bfaa70ed932096750a55c20c7c81 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 22 Dec 2022 14:06:47 +0000 Subject: [PATCH 142/274] Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20221221.8 Microsoft.CodeAnalysis.NetAnalyzers From Version 8.0.0-preview1.22621.3 -> To Version 8.0.0-preview1.22621.8 --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index da0010fbc6e0c..29c9dd4dcecb6 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -26,9 +26,9 @@ https://github.com/dotnet/arcade 57ba56de330e50f9012493b8ba24818e24ec7817 - + https://github.com/dotnet/roslyn-analyzers - 43348f5b4ca847a3d45f33f229356617f53fbfbf + 460a0e5d36934353e88a5e04cdb39bcc0d0b6545 diff --git a/eng/Versions.props b/eng/Versions.props index d3b03e7a034d3..50ce9ea80f8e0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -20,7 +20,7 @@ 3.3.4-beta1.22579.2 - 8.0.0-preview1.22621.3 + 8.0.0-preview1.22621.8 1.1.2-beta1.22512.1 0.1.149-beta From 1da7ebaa750d2c27f7b5de9dd883770ff599f7f6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 14:58:23 +0000 Subject: [PATCH 143/274] [main] Update dependencies from dotnet/arcade (#66092) [main] Update dependencies from dotnet/arcade --- eng/Version.Details.xml | 8 +-- eng/common/native/init-compiler.sh | 81 +++++++++++------------- eng/common/templates/job/onelocbuild.yml | 14 ++-- eng/common/tools.ps1 | 3 +- eng/common/tools.sh | 4 +- global.json | 4 +- 6 files changed, 56 insertions(+), 58 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index da0010fbc6e0c..1a9633fc8f040 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,18 +13,18 @@ - + https://github.com/dotnet/arcade - 57ba56de330e50f9012493b8ba24818e24ec7817 + 3003926e4126f827bca50d5b3ee179afc86d8a7b https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 57ba56de330e50f9012493b8ba24818e24ec7817 + 3003926e4126f827bca50d5b3ee179afc86d8a7b https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 292ebb6535868..c670cb7968921 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -1,30 +1,25 @@ -#!/usr/bin/env bash +#!/bin/sh # # This file detects the C/C++ compiler and exports it to the CC/CXX environment variables # # NOTE: some scripts source this file and rely on stdout being empty, make sure to not output anything here! -if [[ "$#" -lt 3 ]]; then +if [ -z "$build_arch" ] || [ -z "$compiler" ]; then echo "Usage..." - echo "init-compiler.sh